/**
 * Octahedrons have 8 sides. This octahedron supports subdivision.
 * 
 * Vertices have 'smooth' normals, 
 * to make a sharp edge choose a material that uses face normals instead.
 *
 * @author daniel.deady@knectar.com
 * @param radius
 * @param detail Final number of triangles = 4^detail * 8
 */

THREE.OctahedronGeometry = function ( radius, detail ) {

	THREE.Geometry.call( this );

	detail = isFinite(detail) ? detail : 3; // allow a zero value

	var that = this; // ugly scope hack

	prepare( new THREE.Vector3( +1, 0, 0 ) ); // right
	prepare( new THREE.Vector3( -1, 0, 0 ) ); // left
	prepare( new THREE.Vector3( 0, +1, 0 ) ); // up
	prepare( new THREE.Vector3( 0, -1, 0 ) ); // down
	prepare( new THREE.Vector3( 0, 0, +1 ) ); // front
	prepare( new THREE.Vector3( 0, 0, -1 ) ); // back
	var midpoints = [], p = this.vertices;

	// careful to output faces counter-clockwise, that is required for meshes
	make( p[0], p[2], p[4], detail );
	make( p[0], p[4], p[3], detail );
	make( p[0], p[3], p[5], detail );
	make( p[0], p[5], p[2], detail );
	make( p[1], p[2], p[5], detail );
	make( p[1], p[5], p[3], detail );
	make( p[1], p[3], p[4], detail );
	make( p[1], p[4], p[2], detail );

	/**
	 * Project vector onto sphere's surface
	 */
	function prepare( vector ) {

		var normal = vector.clone().normalize();
		var vertex = new THREE.Vertex( normal.clone().multiplyScalar( radius ) );
		vertex.index = that.vertices.push( vertex ) - 1;

		// Texture coords are equivalent to map coords, calculate angle and convert to fraction of a circle.
		var u = azimuth( vector ) / 2 / Math.PI + 0.5;
		var v = inclination( vector ) / Math.PI + 0.5;
		vertex.uv = new THREE.UV( u, v );

		return vertex;

	}

	/**
	 * Approximate a curved face with recursively sub-divided triangles.
	 */
	function make( v1, v2, v3, detail ) {

		if ( detail < 1 ) {

			var face = new THREE.Face3( v1.index, v2.index, v3.index, [ v1.position, v2.position, v3.position ] );
			face.centroid.addSelf( v1.position ).addSelf( v2.position ).addSelf( v3.position ).divideScalar( 3 );
			face.normal = face.centroid.clone().normalize();
			that.faces.push( face );

			var azi = azimuth( face.centroid );
			that.faceVertexUvs[ 0 ].push( [ 
				correctUV( v1.uv, v1.position, azi ),
				correctUV( v2.uv, v2.position, azi ),
				correctUV( v3.uv, v3.position, azi )
			] );

		}
		else {

			detail -= 1;
			// split triangle into 4 smaller triangles
			make( v1, midpoint( v1, v2 ), midpoint( v1, v3 ), detail ); // top quadrant
			make( midpoint( v1, v2 ), v2, midpoint( v2, v3 ), detail ); // left quadrant
			make( midpoint( v1, v3 ), midpoint( v2, v3 ), v3, detail ); // right quadrant
			make( midpoint( v1, v2 ), midpoint( v2, v3 ), midpoint( v1, v3 ), detail ); // center quadrant

		}

	}

	function midpoint( v1, v2 ) {

		if ( !midpoints[ v1.index ] ) midpoints[ v1.index ] = [];
		if ( !midpoints[ v2.index ] ) midpoints[ v2.index ] = [];
		var mid = midpoints[ v1.index ][ v2.index ];
		if ( mid === undefined ) {
			// generate mean point and project to surface with prepare()
			midpoints[ v1.index ][ v2.index ] = midpoints[ v2.index ][ v1.index ] = mid = prepare( 
				new THREE.Vector3().add( v1.position, v2.position ).divideScalar( 2 ) 
			);
		}
		return mid;

	}

	/**
	 * Angle around the Y axis, counter-clockwise when looking from above.
	 */
	function azimuth( vector ) {

		return Math.atan2( vector.z, -vector.x );

	}

	/**
	 * Angle above the XZ plane.
	 */
	function inclination( vector ) {

		return Math.atan2( -vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) );

	}

	/**
	 * Texture fixing helper. Spheres have some odd behaviours.
	 */
	function correctUV( uv, vector, azimuth ) {

		if ( (azimuth < 0) && (uv.u === 1) ) uv = new THREE.UV( uv.u - 1, uv.v );
		if ( (vector.x === 0) && (vector.z === 0) ) uv = new THREE.UV( azimuth / 2 / Math.PI + 0.5, uv.v );
		return uv;

	}

	this.boundingSphere = { radius: radius };

};

THREE.OctahedronGeometry.prototype = new THREE.Geometry();
THREE.OctahedronGeometry.prototype.constructor = THREE.OctahedronGeometry;
