diff --git a/src/extras/geometries/OctahedronGeometry.js b/src/extras/geometries/OctahedronGeometry.js new file mode 100644 index 00000000..8ed2f9b4 --- /dev/null +++ b/src/extras/geometries/OctahedronGeometry.js @@ -0,0 +1,137 @@ +/** + * 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 detail Final number of triangles = 4^detail * 8 + */ + +THREE.OctahedronGeometry = function ( 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 ); + 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: 1 }; + +}; + +THREE.OctahedronGeometry.prototype = new THREE.Geometry(); +THREE.OctahedronGeometry.prototype.constructor = THREE.OctahedronGeometry; diff --git a/src/extras/objects/Sphere.js b/src/extras/objects/Sphere.js new file mode 100644 index 00000000..702b8ccf --- /dev/null +++ b/src/extras/objects/Sphere.js @@ -0,0 +1,30 @@ +/** + * This sphere is smooth and faces are evenly spaced in all directions. + * + * To create there does not need to be a geometry, just a radius. eg. + * var sphere = new THREE.Sphere(100, new THREE.MeshBasicMaterial()); + * + * The number of faces multiplies by 4 for every 1 increase in `detail`. + * 1 detail = 32 faces + * 2 detail = 128 faces + * 3 detail = 512 faces + * + * @author daniel.deady@knectar.com + * @param radius + * @param materials + * @param detail A logarithmic value of fidelity. Defaults to 3. + */ + +THREE.Sphere = function ( radius, materials, detail ) { + + var geometry = new THREE.OctahedronGeometry( detail ); + + THREE.Mesh.call( this, geometry, materials ); + + this.scale.multiplyScalar(radius); + +} + +THREE.Sphere.prototype = new THREE.Mesh(); +THREE.Sphere.prototype.constructor = THREE.Sphere; +THREE.Sphere.prototype.supr = THREE.Mesh.prototype; diff --git a/utils/build.py b/utils/build.py index 5a9ec1cb..cd077019 100644 --- a/utils/build.py +++ b/utils/build.py @@ -104,6 +104,7 @@ EXTRAS_FILES = [ 'extras/geometries/ExtrudeGeometry.js', 'extras/geometries/IcosahedronGeometry.js', 'extras/geometries/LatheGeometry.js', +'extras/geometries/OctahedronGeometry.js', 'extras/geometries/PlaneGeometry.js', 'extras/geometries/SphereGeometry.js', 'extras/geometries/TextGeometry.js',