geometry/Cone.js

import Geometry from '../Geometry';
import BoundingBox from '../math/BoundingBox';
import vec3 from '../glmatrix/vec3';
import vec2 from '../glmatrix/vec2';

/**
 * @constructor clay.geometry.Cone
 * @extends clay.Geometry
 * @param {Object} [opt]
 * @param {number} [opt.topRadius]
 * @param {number} [opt.bottomRadius]
 * @param {number} [opt.height]
 * @param {number} [opt.capSegments]
 * @param {number} [opt.heightSegments]
 */
var Cone = Geometry.extend(/** @lends clay.geometry.Cone# */ {
    dynamic: false,
    /**
     * @type {number}
     */
    topRadius: 0,

    /**
     * @type {number}
     */
    bottomRadius: 1,

    /**
     * @type {number}
     */
    height: 2,

    /**
     * @type {number}
     */
    capSegments: 20,

    /**
     * @type {number}
     */
    heightSegments: 1
}, function() {
    this.build();
},
/** @lends clay.geometry.Cone.prototype */
{
    /**
     * Build cone geometry
     */
    build: function() {
        var positions = [];
        var texcoords = [];
        var faces = [];
        positions.length = 0;
        texcoords.length = 0;
        faces.length = 0;
        // Top cap
        var capSegRadial = Math.PI * 2 / this.capSegments;

        var topCap = [];
        var bottomCap = [];

        var r1 = this.topRadius;
        var r2 = this.bottomRadius;
        var y = this.height / 2;

        var c1 = vec3.fromValues(0, y, 0);
        var c2 = vec3.fromValues(0, -y, 0);
        for (var i = 0; i < this.capSegments; i++) {
            var theta = i * capSegRadial;
            var x = r1 * Math.sin(theta);
            var z = r1 * Math.cos(theta);
            topCap.push(vec3.fromValues(x, y, z));

            x = r2 * Math.sin(theta);
            z = r2 * Math.cos(theta);
            bottomCap.push(vec3.fromValues(x, -y, z));
        }

        // Build top cap
        positions.push(c1);
        // FIXME
        texcoords.push(vec2.fromValues(0, 1));
        var n = this.capSegments;
        for (var i = 0; i < n; i++) {
            positions.push(topCap[i]);
            // FIXME
            texcoords.push(vec2.fromValues(i / n, 0));
            faces.push([0, i+1, (i+1) % n + 1]);
        }

        // Build bottom cap
        var offset = positions.length;
        positions.push(c2);
        texcoords.push(vec2.fromValues(0, 1));
        for (var i = 0; i < n; i++) {
            positions.push(bottomCap[i]);
            // FIXME
            texcoords.push(vec2.fromValues(i / n, 0));
            faces.push([offset, offset+((i+1) % n + 1), offset+i+1]);
        }

        // Build side
        offset = positions.length;
        var n2 = this.heightSegments;
        for (var i = 0; i < n; i++) {
            for (var j = 0; j < n2+1; j++) {
                var v = j / n2;
                positions.push(vec3.lerp(vec3.create(), topCap[i], bottomCap[i], v));
                texcoords.push(vec2.fromValues(i / n, v));
            }
        }
        for (var i = 0; i < n; i++) {
            for (var j = 0; j < n2; j++) {
                var i1 = i * (n2 + 1) + j;
                var i2 = ((i + 1) % n) * (n2 + 1) + j;
                var i3 = ((i + 1) % n) * (n2 + 1) + j + 1;
                var i4 = i * (n2 + 1) + j + 1;
                faces.push([offset+i2, offset+i1, offset+i4]);
                faces.push([offset+i4, offset+i3, offset+i2]);
            }
        }

        this.attributes.position.fromArray(positions);
        this.attributes.texcoord0.fromArray(texcoords);

        this.initIndicesFromArray(faces);

        this.generateVertexNormals();

        this.boundingBox = new BoundingBox();
        var r = Math.max(this.topRadius, this.bottomRadius);
        this.boundingBox.min.set(-r, -this.height/2, -r);
        this.boundingBox.max.set(r, this.height/2, r);
    }
});

export default Cone;