├── images └── maps │ ├── iomap.jpg │ ├── marsmap.jpg │ ├── moonmap.jpg │ ├── rheamap.jpg │ ├── sunmap.jpg │ ├── sunmap2.jpg │ ├── arielmap.jpg │ ├── ceresmap.jpg │ ├── charonmap.jpg │ ├── deimosmap.jpg │ ├── dionemap.jpg │ ├── earthbump.jpg │ ├── earthmap.jpg │ ├── earthspec.jpg │ ├── europamap.jpg │ ├── marsbump.jpg │ ├── mimasmap.jpg │ ├── moonbump.jpg │ ├── oberonmap.jpg │ ├── phobosmap.jpg │ ├── phoebemap.jpg │ ├── plutomap.jpg │ ├── saturnmap.jpg │ ├── tethysmap.jpg │ ├── titanmap.jpg │ ├── tritonmap.jpg │ ├── uranusmap.jpg │ ├── venusmap.jpg │ ├── venusmap2.jpg │ ├── vestabump.jpg │ ├── vestamap.jpg │ ├── callistomap.jpg │ ├── earthclouds.png │ ├── enceladusmap.jpg │ ├── ganymedemap.jpg │ ├── hyperionmap.jpg │ ├── iapetusmap.jpg │ ├── jupitermap.jpg │ ├── jupiterrings.png │ ├── marsclouds.png │ ├── mercurybump.jpg │ ├── mercurymap.jpg │ ├── mirandamap.jpg │ ├── neptunemap.jpg │ ├── neptunerings.png │ ├── phobosbump.jpg │ ├── proteusmap.jpg │ ├── saturnrings.png │ ├── solarcorona.jpg │ ├── titanclouds.jpg │ ├── titaniamap.jpg │ ├── tycho-skymap.jpg │ ├── umbrielmap.jpg │ ├── uranusrings.png │ ├── earthmapclouds.jpg │ ├── earthclouds-fair.png │ ├── earthclouds-heavy.png │ └── galaxy_starfield.png ├── package.require.js ├── Makefile ├── threex.atmospheredatgui.js ├── threex.atmospherematerial.js ├── examples ├── planets.html ├── earthmoon.html ├── atmospherematerial.html ├── basic.html ├── earth.html ├── select.html ├── planetary-systems.html └── vendor │ └── three.js │ └── examples │ └── js │ ├── controls │ └── OrbitControls.js │ └── libs │ └── dat.gui.min.js ├── lib ├── three.css.js ├── planetary.js ├── threex.planets.js └── OrbitControls.js ├── README.md └── threex.planets.js /images/maps/iomap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/iomap.jpg -------------------------------------------------------------------------------- /images/maps/marsmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/marsmap.jpg -------------------------------------------------------------------------------- /images/maps/moonmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/moonmap.jpg -------------------------------------------------------------------------------- /images/maps/rheamap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/rheamap.jpg -------------------------------------------------------------------------------- /images/maps/sunmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/sunmap.jpg -------------------------------------------------------------------------------- /images/maps/sunmap2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/sunmap2.jpg -------------------------------------------------------------------------------- /images/maps/arielmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/arielmap.jpg -------------------------------------------------------------------------------- /images/maps/ceresmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/ceresmap.jpg -------------------------------------------------------------------------------- /images/maps/charonmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/charonmap.jpg -------------------------------------------------------------------------------- /images/maps/deimosmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/deimosmap.jpg -------------------------------------------------------------------------------- /images/maps/dionemap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/dionemap.jpg -------------------------------------------------------------------------------- /images/maps/earthbump.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/earthbump.jpg -------------------------------------------------------------------------------- /images/maps/earthmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/earthmap.jpg -------------------------------------------------------------------------------- /images/maps/earthspec.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/earthspec.jpg -------------------------------------------------------------------------------- /images/maps/europamap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/europamap.jpg -------------------------------------------------------------------------------- /images/maps/marsbump.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/marsbump.jpg -------------------------------------------------------------------------------- /images/maps/mimasmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/mimasmap.jpg -------------------------------------------------------------------------------- /images/maps/moonbump.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/moonbump.jpg -------------------------------------------------------------------------------- /images/maps/oberonmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/oberonmap.jpg -------------------------------------------------------------------------------- /images/maps/phobosmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/phobosmap.jpg -------------------------------------------------------------------------------- /images/maps/phoebemap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/phoebemap.jpg -------------------------------------------------------------------------------- /images/maps/plutomap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/plutomap.jpg -------------------------------------------------------------------------------- /images/maps/saturnmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/saturnmap.jpg -------------------------------------------------------------------------------- /images/maps/tethysmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/tethysmap.jpg -------------------------------------------------------------------------------- /images/maps/titanmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/titanmap.jpg -------------------------------------------------------------------------------- /images/maps/tritonmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/tritonmap.jpg -------------------------------------------------------------------------------- /images/maps/uranusmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/uranusmap.jpg -------------------------------------------------------------------------------- /images/maps/venusmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/venusmap.jpg -------------------------------------------------------------------------------- /images/maps/venusmap2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/venusmap2.jpg -------------------------------------------------------------------------------- /images/maps/vestabump.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/vestabump.jpg -------------------------------------------------------------------------------- /images/maps/vestamap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/vestamap.jpg -------------------------------------------------------------------------------- /images/maps/callistomap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/callistomap.jpg -------------------------------------------------------------------------------- /images/maps/earthclouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/earthclouds.png -------------------------------------------------------------------------------- /images/maps/enceladusmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/enceladusmap.jpg -------------------------------------------------------------------------------- /images/maps/ganymedemap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/ganymedemap.jpg -------------------------------------------------------------------------------- /images/maps/hyperionmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/hyperionmap.jpg -------------------------------------------------------------------------------- /images/maps/iapetusmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/iapetusmap.jpg -------------------------------------------------------------------------------- /images/maps/jupitermap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/jupitermap.jpg -------------------------------------------------------------------------------- /images/maps/jupiterrings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/jupiterrings.png -------------------------------------------------------------------------------- /images/maps/marsclouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/marsclouds.png -------------------------------------------------------------------------------- /images/maps/mercurybump.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/mercurybump.jpg -------------------------------------------------------------------------------- /images/maps/mercurymap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/mercurymap.jpg -------------------------------------------------------------------------------- /images/maps/mirandamap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/mirandamap.jpg -------------------------------------------------------------------------------- /images/maps/neptunemap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/neptunemap.jpg -------------------------------------------------------------------------------- /images/maps/neptunerings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/neptunerings.png -------------------------------------------------------------------------------- /images/maps/phobosbump.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/phobosbump.jpg -------------------------------------------------------------------------------- /images/maps/proteusmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/proteusmap.jpg -------------------------------------------------------------------------------- /images/maps/saturnrings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/saturnrings.png -------------------------------------------------------------------------------- /images/maps/solarcorona.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/solarcorona.jpg -------------------------------------------------------------------------------- /images/maps/titanclouds.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/titanclouds.jpg -------------------------------------------------------------------------------- /images/maps/titaniamap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/titaniamap.jpg -------------------------------------------------------------------------------- /images/maps/tycho-skymap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/tycho-skymap.jpg -------------------------------------------------------------------------------- /images/maps/umbrielmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/umbrielmap.jpg -------------------------------------------------------------------------------- /images/maps/uranusrings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/uranusrings.png -------------------------------------------------------------------------------- /images/maps/earthmapclouds.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/earthmapclouds.jpg -------------------------------------------------------------------------------- /images/maps/earthclouds-fair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/earthclouds-fair.png -------------------------------------------------------------------------------- /images/maps/earthclouds-heavy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/earthclouds-heavy.png -------------------------------------------------------------------------------- /images/maps/galaxy_starfield.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofrohn/threex.planets/HEAD/images/maps/galaxy_starfield.png -------------------------------------------------------------------------------- /package.require.js: -------------------------------------------------------------------------------- 1 | define( [ 'module' 2 | , './threex.planets', 3 | , './threex.atmospherematerial' 4 | , './threex.atmospheredatgui' 5 | ], function(module){ 6 | // set baseUrl for this plugin 7 | THREEx.Planets.baseURL = module.uri+'/../'; 8 | }); -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # makefile to automatize simple operations 2 | 3 | server: 4 | python -m SimpleHTTPServer 5 | 6 | deploy: 7 | # assume there is something to commit 8 | # use "git diff --exit-code HEAD" to know if there is something to commit 9 | # so two lines: one if no commit, one if something to commit 10 | git commit -a -m "New deploy" && git push -f origin HEAD:gh-pages && git reset HEAD~ 11 | 12 | install: 13 | git submodule update --init -------------------------------------------------------------------------------- /threex.atmospheredatgui.js: -------------------------------------------------------------------------------- 1 | /** 2 | * vendor.js framework definition 3 | * @type {Object} 4 | */ 5 | var THREEx = THREEx || {}; 6 | 7 | /** 8 | * add a THREEx.AtmosphereMaterial to Dat.DUI 9 | * 10 | * @param {THREE.ShaderMaterial} material the material to handle 11 | * @param {dat.GUI+} datGui the dat.GUI to which we need to add 12 | */ 13 | THREEx.addAtmosphereMaterial2DatGui = function(material, datGui){ 14 | datGui = datGui || new dat.GUI() 15 | var uniforms = material.uniforms 16 | // options 17 | var options = { 18 | coeficient : uniforms['coeficient'].value, 19 | power : uniforms['power'].value, 20 | glowColor : '#'+uniforms.glowColor.value.getHexString(), 21 | presetFront : function(){ 22 | options.coeficient = 1 23 | options.power = 2 24 | onChange() 25 | }, 26 | presetBack : function(){ 27 | options.coeficient = 0.5 28 | options.power = 4.0 29 | onChange() 30 | }, 31 | } 32 | var onChange = function(){ 33 | uniforms['coeficient'].value = options.coeficient 34 | uniforms['power'].value = options.power 35 | uniforms.glowColor.value.set( options.glowColor ); 36 | } 37 | onChange() 38 | 39 | // config datGui 40 | datGui.add( options, 'coeficient' , 0.0 , 2) 41 | .listen().onChange( onChange ) 42 | datGui.add( options, 'power' , 0.0 , 30) 43 | .listen().onChange( onChange ) 44 | datGui.addColor( options, 'glowColor' ) 45 | .listen().onChange( onChange ) 46 | datGui.add( options, 'presetFront' ) 47 | datGui.add( options, 'presetBack' ) 48 | } -------------------------------------------------------------------------------- /threex.atmospherematerial.js: -------------------------------------------------------------------------------- 1 | var THREEx = THREEx || {} 2 | 3 | /** 4 | * from http://stemkoski.blogspot.fr/2013/07/shaders-in-threejs-glow-and-halo.html 5 | * @return {[type]} [description] 6 | */ 7 | THREEx.createAtmosphereMaterial = function(){ 8 | var vertexShader = [ 9 | 'varying vec3 vVertexWorldPosition;', 10 | 'varying vec3 vVertexNormal;', 11 | 12 | 'void main(){', 13 | ' vVertexNormal = normalize(normalMatrix * normal);', 14 | 15 | ' vVertexWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;', 16 | 17 | ' // set gl_Position', 18 | ' gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);', 19 | '}', 20 | 21 | ].join('\n') 22 | var fragmentShader = [ 23 | 'uniform vec3 glowColor;', 24 | 'uniform float coeficient;', 25 | 'uniform float power;', 26 | 27 | 'varying vec3 vVertexNormal;', 28 | 'varying vec3 vVertexWorldPosition;', 29 | 30 | 'void main(){', 31 | ' vec3 worldCameraToVertex= vVertexWorldPosition - cameraPosition;', 32 | ' vec3 viewCameraToVertex = (viewMatrix * vec4(worldCameraToVertex, 0.0)).xyz;', 33 | ' viewCameraToVertex = normalize(viewCameraToVertex);', 34 | ' float intensity = pow(coeficient + dot(vVertexNormal, viewCameraToVertex), power);', 35 | ' gl_FragColor = vec4(glowColor, intensity);', 36 | '}', 37 | ].join('\n') 38 | 39 | // create custom material from the shader code above 40 | // that is within specially labeled script tags 41 | var material = new THREE.ShaderMaterial({ 42 | uniforms: { 43 | coeficient : { 44 | type : "f", 45 | value : 1.0 46 | }, 47 | power : { 48 | type : "f", 49 | value : 2 50 | }, 51 | glowColor : { 52 | type : "c", 53 | value : new THREE.Color('pink') 54 | }, 55 | }, 56 | vertexShader : vertexShader, 57 | fragmentShader : fragmentShader, 58 | //blending : THREE.AdditiveBlending, 59 | transparent : true, 60 | depthWrite : false, 61 | }); 62 | return material 63 | } -------------------------------------------------------------------------------- /examples/planets.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 100 | -------------------------------------------------------------------------------- /examples/earthmoon.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 122 | -------------------------------------------------------------------------------- /lib/three.css.js: -------------------------------------------------------------------------------- 1 | /************************************************************************/ 2 | /* Initialized some variables for CSS, and also it computes the initial 3 | 4 | position for the CSS cube based on the Three Cube */ 5 | /************************************************************************/ 6 | function initCSS3D() { 7 | screenWhalf = screenWidth / 2; 8 | screenHhalf = screenHeight / 2; 9 | 10 | divCSSWorld = document.getElementById('css-world'); 11 | divCSSCamera = document.getElementById('css-camera'); 12 | divCube = document.getElementById('shape'); 13 | 14 | fovValue = 0.5 / Math.tan(camera.fov * Math.PI / 360) * screenHeight; 15 | 16 | setDivPosition(divCube, glCube); 17 | } 18 | 19 | /************************************************************************/ 20 | /* Applies CSS3 styles to the css-world div */ 21 | /************************************************************************/ 22 | function setCSSWorld() { 23 | divCSSWorld.style.WebkitPerspective = fovValue + "px"; 24 | divCSSWorld.style.WebkitPerspectiveOrigin = "50% 50%"; 25 | divCSSWorld.style.MozPerspective = fovValue + "px"; 26 | divCSSWorld.style.MozPerspectiveOrigin = "50% 50%"; 27 | } 28 | 29 | /************************************************************************/ 30 | /* Applies CSS3 styles to css-camera div */ 31 | /************************************************************************/ 32 | function setCSSCamera(camera, fovValue) { 33 | var cameraStyle = getCSS3D_cameraStyle(camera, fovValue); 34 | divCSSCamera.style.WebkitTransform = cameraStyle; 35 | divCSSCamera.style.MozTransform = cameraStyle; 36 | } 37 | 38 | /************************************************************************/ 39 | /* Return the CSS3D transformations from the Three camera */ 40 | /************************************************************************/ 41 | function getCSS3D_cameraStyle(camera, fov) { 42 | var cssStyle = ""; 43 | cssStyle += "translate3d(0,0," + epsilon(fov) + "px) "; 44 | cssStyle += toCSSMatrix(camera.matrixWorldInverse, true); 45 | cssStyle += " translate3d(" + screenWhalf + "px," + screenHhalf + "px, 0)"; 46 | return cssStyle; 47 | } 48 | 49 | /************************************************************************/ 50 | /* Fixes the difference between WebGL coordinates to CSS coordinates */ 51 | /************************************************************************/ 52 | function toCSSMatrix(threeMat4, b) { 53 | var a = threeMat4, f; 54 | if (b) { 55 | f = [ 56 | a.elements[0], -a.elements[1], a.elements[2], a.elements[3], 57 | a.elements[4], -a.elements[5], a.elements[6], a.elements[7], 58 | a.elements[8], -a.elements[9], a.elements[10], a.elements[11], 59 | a.elements[12], -a.elements[13], a.elements[14], a.elements[15] 60 | ]; 61 | } else { 62 | f = [ 63 | a.elements[0], a.elements[1], a.elements[2], a.elements[3], 64 | a.elements[4], a.elements[5], a.elements[6], a.elements[7], 65 | a.elements[8], a.elements[9], a.elements[10], a.elements[11], 66 | a.elements[12], a.elements[13], a.elements[14], a.elements[15] 67 | ]; 68 | } 69 | for (var e in f) { 70 | f[e] = epsilon(f[e]); 71 | } 72 | return "matrix3d(" + f.join(",") + ")"; 73 | } 74 | 75 | /************************************************************************/ 76 | /* Computes CSS3D transformations based on a Three Object */ 77 | /************************************************************************/ 78 | function setDivPosition(cssObject, glObject) { 79 | var offset = 400; //value to offset the cube 80 | glObject.updateMatrix(); 81 | cssObject.style.position = "absolute"; 82 | //Webkit: 83 | cssObject.style.WebkitTransformOrigin = "50% 50%"; 84 | cssObject.style.WebkitTransform = CSStransform(200 + offset, 200, glObject.matrix); 85 | //Mozilla: 86 | cssObject.style.MozTransformOrigin = "50% 50%"; 87 | cssObject.style.MozTransform = CSStransform(200 + offset, 200, glObject.matrix); 88 | } 89 | 90 | /************************************************************************/ 91 | /* Helper function to convert to CSS3D transformations */ 92 | /************************************************************************/ 93 | function CSStransform(width, height, matrix) { 94 | var scale = 1.0; 95 | return [toCSSMatrix(matrix, false), 96 | "scale3d(" + scale + ", -" + scale + ", " + scale + ")", 97 | "translate3d(" + epsilon(-0.5 * width) + "px," + epsilon(-0.5 * height) + "px,0)"].join(" "); 98 | } 99 | 100 | /************************************************************************/ 101 | /* Rounding error */ 102 | /************************************************************************/ 103 | function epsilon(a) { 104 | if (Math.abs(a) < 0.000001) { 105 | return 0 106 | } 107 | return a; 108 | } 109 | -------------------------------------------------------------------------------- /examples/atmospherematerial.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 124 | -------------------------------------------------------------------------------- /examples/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 131 | -------------------------------------------------------------------------------- /examples/earth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | threex.planets.js 2 | ================= 3 | 4 | It is a three.js extension to display planets 5 | based on the data from 6 | * [planetpixelemporium](http://planetpixelemporium.com/planets.html) (Sun*, Earth DEM, Uranus) 7 | * [USGS Astrogeology](http://astrogeology.usgs.gov/solar-system) (Mercury DEM, Moon*, Mars, Jupiter Moons, Uranus Moons) 8 | * [NASA/JHUAPL/Carnegie Institution of Washington](http://messenger.jhuapl.edu/Explore/Images.html#global-mosaics) (Mercury Map*) 9 | * [NASA/JHUAPL/SWRI](http://pluto.jhuapl.edu/Multimedia/Science-Photos/image.php?gallery_id=2&image_id=252) (Pluto*, Charon) 10 | * [NASA/JPL-Caltech/Space Science Institute](http://www.jpl.nasa.gov/spaceimages/searchwp.php?category=saturn) (Saturn Rings*, Titan, Triton*) 11 | * [NASA/JPL-Caltech/Space Science Institute/LPI](http://www.lpi.usra.edu/icy_moons/) (Saturn Icy Moons*) 12 | * [NASA/JPL-Caltech](http://voyager.jpl.nasa.gov/gallery/uranus.html) (Uranus Rings*) 13 | * [NASA/JPL-Caltech](http://maps.jpl.nasa.gov/) (Stars) 14 | * [NASA Visible Earth](http://visibleearth.nasa.gov/view_cat.php?categoryID=1484) (Earth, Earth Clouds*) 15 | * [ESO](http://www.eso.org/public/usa/images/eso0127a/) (Solar Corona*) 16 | * [Björn Jónsson](http://bjj.mmedia.is/data/planetary_maps.html) (Venus*, Jupiter, Neptune) 17 | * [Solar System Scope](http://www.solarsystemscope.com/nexus/textures/planet_textures/) (Saturn, Titan Clouds) 18 | * [Philip Stooke](http://solarviews.com/cap/index/maps-cylindrical1.html) (Deimos, Proteus) 19 | \* Hand adapted by ofrohn, also Jupiter & Neptune ring maps 20 | 21 | Here is some demos to show off 22 | 23 | * [planetary systems](http://ofrohn.github.io/threex-planets-demo/planetary-systems.html) 24 | and its 25 | [source](https://github.com/ofrohn/threex.planets/blob/master/examples/planetary-systems.html). Simulation of planets with their moons. 26 | * [all the planets](http://ofrohn.github.io/threex-planets-demo/planets.html) 27 | and its 28 | [source](https://github.com/ofrohn/threex.planets/blob/master/examples/planets.html). All the implemented objects dancing in a circle. 29 | * [earth and moon](http://ofrohn.github.io/threex-planets-demo/eartmoon.html) 30 | and its 31 | [source](https://github.com/ofrohn/threex.planets/blob/master/examples/earthmoon.html). The Earth spinning and the Moon orbiting it. 32 | _Older examples_ 33 | * [earth demo](http://jeromeetienne.github.io/threex.planets/examples/earth.html) 34 | and check its 35 | [source](https://github.com/jeromeetienne/threex.planets/blob/master/examples/earth.html). 36 | It displays a nice earth with cloud and even the moon. 37 | * [select demo](http://jeromeetienne.github.io/threex.planets/examples/select.html) 38 | and check its 39 | [source](https://github.com/jeromeetienne/threex.planets/blob/master/examples/select.html). 40 | Display all the planets available 41 | * [atmospherematerial demo](http://jeromeetienne.github.io/threex.planets/examples/atmospherematerial.html) 42 | and check its 43 | [source](https://github.com/jeromeetienne/threex.planets/blob/master/examples/atmospherematerial.html). a simple demo to show ```THREEx.createAtmosphereMaterial()``` 44 | * [basic demo](http://jeromeetienne.github.io/threex.planets/examples/basic.html) 45 | and check its 46 | [source](https://github.com/jeromeetienne/threex.planets/blob/master/examples/basic.html). 47 | Good for educational purpose 48 | 49 | ## How To install it 50 | 51 | You can install it manually or with 52 | [bower](http://bower.io/). 53 | for the manual version, first include ```threex.planets.js``` with the usual 54 | 55 | ```html 56 | 57 | ``` 58 | 59 | or with 60 | [bower](http://bower.io/) 61 | you type the following to install the package. 62 | 63 | ```bash 64 | bower install -s threex.planets=https://github.com/jeromeetienne/threex.planets/archive/master.zip 65 | ``` 66 | 67 | then you add that in your html 68 | 69 | ```html 70 | 71 | ``` 72 | 73 | 74 | ## Usage 75 | 76 | to create uranus with its ring 77 | 78 | ```javascript 79 | var mesh = THREEx.Planets.createUranus() 80 | scene.add(mesh) 81 | var mesh = THREEx.Planets.createUranusRing() 82 | scene.add(mesh) 83 | ``` 84 | 85 | to create the earth plus the clouds moving around 86 | 87 | ```javascript 88 | var mesh = THREEx.Planets.createEarth() 89 | scene.add(mesh) 90 | var mesh = THREEx.Planets.createEarthCloud() 91 | scene.add(mesh) 92 | updateFcts.push(function(delta, now){ 93 | mesh.rotation.y += 1/8 * delta; 94 | }) 95 | ``` 96 | 97 | new simpler form: 98 | 99 | ```javascript 100 | var mesh = THREEx.Planets.create("Uranus"); // ring included 101 | scene.add(mesh); 102 | 103 | var mesh = THREEx.Planets.create("Earth"); // clouds included 104 | scene.add(mesh); 105 | updateFcts.push(function(delta, now){ 106 | mesh.traverse(function(child) { 107 | if (child.name.search("cloud") !== -1) child.rotation.y += 1/8 * delta; 108 | }); 109 | }); 110 | 111 | var mesh = THREEx.Planets.create("Earth", true); // no clouds 112 | ``` 113 | 114 | ## API 115 | 116 | Here is the list of all the functions. 117 | They all return a ```THREE.Object3d```. 118 | You can tune it to fit your need 119 | 120 | * ```THREEx.Planets.create(body, skipextras)``` return the mesh of any supported body 121 | body: Sun|Mercury|Venus|Earth|Moon|Mars|Vesta|Ceres|Jupiter|Saturn|Uranus|Neptune|Pluto 122 | skipextras: No ring, cloud, corona or other extra elements 123 | * ```THREEx.Planets.createRings(body)``` return the ring mesh of any supported body 124 | body: Jupiter|Saturn|Uranus|Neptune 125 | * ```THREEx.Planets.createClouds(body)``` return the cloud mesh of any supported body 126 | body: Earth|Mars 127 | 128 | * ```THREEx.Planets.createSun()``` return the mesh of the Sun 129 | * ```THREEx.Planets.createMercury()``` return the mesh of Mercury 130 | * ```THREEx.Planets.createVenus()``` return the mesh of Venus 131 | * ```THREEx.Planets.createMoon()``` return the mesh of the Moon 132 | * ```THREEx.Planets.createEarth()``` return the mesh of the Earth 133 | * ```THREEx.Planets.createEarthCloud()``` return the mesh of the Earth Cloud 134 | * ```THREEx.Planets.createMars()``` return the mesh of Mars 135 | * ```THREEx.Planets.createJupiter()``` return the mesh of Jupiter 136 | * ```THREEx.Planets.createSaturn()``` return the mesh of Saturn 137 | * ```THREEx.Planets.createSaturnRing()``` return the mesh of Saturn's ring 138 | * ```THREEx.Planets.createUranus()``` return the mesh of Uranus 139 | * ```THREEx.Planets.createUranusRing()``` return the mesh of Uranus's ring 140 | * ```THREEx.Planets.createNeptune()``` return the mesh of Neptune 141 | * ```THREEx.Planets.createPluto()``` return the mesh of Pluto 142 | * ```THREEx.Planets.createStarfield()``` return the mesh of a starfield environmental sphere 143 | 144 | -------------------------------------------------------------------------------- /examples/select.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 189 | -------------------------------------------------------------------------------- /examples/planetary-systems.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Planetary Systems 5 | 6 | 7 | 8 | 9 | 10 | 16 | 296 | -------------------------------------------------------------------------------- /lib/planetary.js: -------------------------------------------------------------------------------- 1 | var deg2rad = Math.PI / 180, 2 | τ = 2 * Math.PI, 3 | halfπ = Math.PI / 2, 4 | ε = 23.43928 5 | sinε = Math.sin(ε), 6 | cosε = Math.sin(ε); 7 | 8 | function $(id) { return document.getElementById(id); } 9 | function px(n) { return n + "px"; } 10 | function Round(x, dg) { return(Math.round(Math.pow(10,dg)*x)/Math.pow(10,dg)); } 11 | function sign(x) { return x ? x < 0 ? -1 : 1 : 0; } 12 | 13 | function has(o, key) { return o !== null && hasOwnProperty.call(o, key); } 14 | function isNumber(n) { return !isNaN(parseFloat(n)) && isFinite(n); } 15 | function isArray(o) { return Object.prototype.toString.call(o) === "[object Array]"; } 16 | function isObject(o) { var type = typeof o; return type === 'function' || type === 'object' && !!o; } 17 | function isFunction(o) { return typeof o == 'function' || false; } 18 | /* 19 | function dist(p1, p2){ 20 | var θ1 = p1.θ * deg2rad, ϕ1 = p1.ϕ * deg2rad, 21 | θ2 = p2.θ * deg2rad, ϕ2 = p2.ϕ * deg2rad; 22 | 23 | return Math.sqrt(p1.r*p1.r + p2.r*p2.r - 2*p1.r*p2.r * (Math.sin(θ1) * Math.sin(θ2) * Math.cos(ϕ1-ϕ2) + Math.cos(θ1) * Math.cos(θ2))); 24 | } 25 | */ 26 | 27 | function dateParse(s) { 28 | if (!s) return; 29 | var t = s.split("."); 30 | if (t.length < 1) return; 31 | t = t[0].split("-"); 32 | t[0] = t[0].replace(/\D/g, ""); 33 | if (!t[0]) return; 34 | t[1] = t[1]?t[1].replace(/\D/g, ""):"1"; 35 | t[2] = t[2]?t[2].replace(/\D/g, ""):"1"; 36 | 37 | return new Date(t[0], t[1]-1, t[2]); 38 | } 39 | 40 | function dateAdd(dt, val, type) { 41 | var t, ldt = dt.valueOf(); 42 | if (!val) return new Date(ldt); 43 | t = type || "d"; 44 | switch (t) { 45 | case 'y': case 'yr': ldt += 31556926080*val; break; 46 | case 'm': case 'mo': ldt += 2629800000*val; break; 47 | case 'd': case 'dy': ldt += 86400000*val; break; 48 | case 'h': case 'hr': ldt += 3600000*val; break; 49 | case 'n': case 'mn': ldt += 60000*val; break; 50 | case 's': case 'sec': ldt += 1000*val; break; 51 | case 'ms': ldt += val; break; 52 | } 53 | return new Date(ldt); 54 | } 55 | 56 | 57 | function dateDiff(dt1, dt2, type) { 58 | if (!dt2 || !dt1) return; 59 | var diff = dt2.valueOf() - dt1.valueOf(), 60 | tp = type || "d"; 61 | switch (tp) { 62 | case 'y': case 'yr': diff /= 31556926080; break; 63 | case 'm': case 'mo': diff /= 2629800000; break; 64 | case 'd': case 'dy': diff /= 86400000; break; 65 | case 'h': case 'hr': diff /= 3600000; break; 66 | case 'n': case 'mn': diff /= 60000; break; 67 | case 's': case 'sec': diff /= 1000; break; 68 | case 'ms': break; 69 | } 70 | if (type) return Math.floor(diff); 71 | return diff; 72 | } 73 | 74 | 75 | var Trig = { 76 | sinh: function (val) { return (Math.pow(Math.E, val)-Math.pow(Math.E, -val))/2; }, 77 | cosh: function (val) { return (Math.pow(Math.E, val)+Math.pow(Math.E, -val))/2; }, 78 | tanh: function (val) { return 2.0 / (1.0 + Math.exp(-2.0 * val)) - 1.0; }, 79 | asinh: function (val) { return Math.log(val + Math.sqrt(val * val + 1)); }, 80 | acosh: function (val) { return Math.log(val + Math.sqrt(val * val - 1)); }, 81 | normalize0: function(val) { return ((val + Math.PI*3) % (Math.PI*2)) - Math.PI; }, 82 | normalize: function(val) { return ((val + Math.PI*2) % (Math.PI*2)); }, 83 | cartesian: function(p) { 84 | var ϕ = p[0], θ = halfπ - p[1], r = p[2]; 85 | return {"x": r * Math.sin(θ) * Math.cos(ϕ), "y": r * Math.sin(θ) * Math.sin(ϕ), "z": r * Math.cos(θ)}; 86 | }, 87 | spherical: function(p) { 88 | var r = Math.sqrt(p.x * p.x + p.y * p.y + p.z * p.z), 89 | θ = Math.atan(p.y / p.x), 90 | ϕ = Math.acos(p.z / r); 91 | return [θ / deg2rad, ϕ / deg2rad, r]; 92 | } 93 | }; 94 | 95 | function dist(p1, p2) { 96 | var ϕ1 = p1[0], 97 | θ1 = p1[1], 98 | ϕ2 = p2[0], 99 | θ2 = p2[1], 100 | dθ = θ2 - θ1, 101 | dϕ = ϕ2 - ϕ1; 102 | 103 | //return Math.acos( Math.sin(θ1) * Math.sin(θ2) + Math.cos(θ1) * Math.cos(θ2) * Math.cos(dϕ) ); 104 | var a = Math.sin(dθ / 2) * Math.sin(dθ / 2) + Math.cos(θ1) * Math.cos(θ2) * Math.sin(dϕ / 2) * Math.sin(dϕ / 2); 105 | var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 106 | return c; 107 | } 108 | 109 | var gmdat = { 110 | "sol": 0.0002959122082855911025, // AU^3/d^2 111 | "mer": 164468599544771, //km^3/d^2 112 | "ven": 2425056445892137, 113 | "ter": 2975536307796296, 114 | "lun": 36599199229256, 115 | "mar": 319711652803400, 116 | "cer": 467549107200, 117 | "ves": 129071530155, 118 | "jup": 945905743547733000, 119 | "sat": 283225255921345000, 120 | "ura": 43256076555832200, 121 | "nep": 51034453325494200, 122 | "plu": 7327611364884, 123 | "eri": 8271175680000 124 | } 125 | 126 | 127 | var transform = function(item, date, parentBody) { 128 | var dt, i, key, dat = {}, elms = ["a","e","i","w","M","L","W","N","n","ref","lecl","becl","Tilt"]; 129 | /* 130 | ep = epoch (dt) 131 | N = longitude of the ascending node (deg) Ω 132 | i = inclination to the refrence plane, default:ecliptic (deg) 133 | w = argument of periapsis (deg) ω 134 | a = semi-major axis, or mean distance from parent body (AU,km) 135 | e = eccentricity (0=circle, 0-1=ellipse, 1=parabola, >1=hyperbola ) (-) 136 | M = mean anomaly (0 at periapsis; increases uniformly with time) (deg) 137 | n = mean daily motion = 360/P (deg/day) 138 | 139 | W = N + w = longitude of periapsis ϖ 140 | L = M + W = mean longitude 141 | q = a*(1-e) = periapsis distance 142 | Q = a*(1+e) = apoapsis distance 143 | P = 2π * sqrt(a^3/GM) = orbital period (years) 144 | T = Epoch_of_M - (M(deg)/360_deg) / P = time of periapsis 145 | v = true anomaly (angle between position and periapsis) ν 146 | E = eccentric anomaly 147 | 148 | Mandatory: a, e, i, N, w|W, M|L, dM|n 149 | */ 150 | 151 | if (parentBody) gm = gmdat[parentBody]; 152 | else gm = gmdat["sol"]; 153 | 154 | if (date) { 155 | if (date instanceof Date) { dt = date; } 156 | else { dt = dateParse(date); } 157 | } 158 | if (!dt) { dt = new Date(); } 159 | dat.jd = JD(dt); 160 | 161 | dt = dateParse(item.ep); 162 | dat.jd0 = JD(dt); 163 | dat.d = dat.jd - dat.jd0; 164 | dat.cy = dat.d / 36525; 165 | 166 | for (i=0; i 1.0 ? E*E : -E*E, 196 | term = e * anom2 * E / 6.0, 197 | rval = (1.0 - e) * E - term, 198 | n = 4; 199 | 200 | while(Math.abs(term) > 1e-15) { 201 | term *= anom2 / (n * (n + 1)); 202 | rval -= term; 203 | n += 2; 204 | } 205 | return(rval); 206 | } 207 | 208 | function kepler(dat) { 209 | var curr, err, trial, tmod, 210 | e = dat.e, M = dat.M, 211 | thresh = 1e-8, 212 | offset = 0.0, 213 | delta_curr = 1.9, 214 | is_negative = false, 215 | n_iter = 0; 216 | 217 | if (!M) return(0.0); 218 | 219 | if (e < 1.0) { 220 | if (M < -Math.PI || M > Math.PI) { 221 | tmod = Trig.normalize0(M); 222 | offset = M - tmod; 223 | M = tmod; 224 | } 225 | 226 | if (e < 0.9) { 227 | curr = Math.atan2(Math.sin(M), Math.cos(M) - e); 228 | do { 229 | err = (curr - e * Math.sin(curr) - M) / (1.0 - e * Math.cos(curr)); 230 | curr -= err; 231 | } while (Math.abs(err) > thresh); 232 | return curr; // + offset; 233 | } 234 | } 235 | 236 | if ( M < 0.0) { 237 | M = -M; 238 | is_negative = true; 239 | } 240 | 241 | curr = M; 242 | thresh = thresh * Math.abs(1.0 - e); 243 | /* Due to roundoff error, there's no way we can hope to */ 244 | /* get below a certain minimum threshhold anyway: */ 245 | if ( thresh < 1e-15) { thresh = 1e-15; } 246 | if ( (e > 0.8 && M < Math.PI / 3.0) || e > 1.0) { /* up to 60 degrees */ 247 | trial = M / Math.abs( 1.0 - e); 248 | 249 | if (trial * trial > 6.0 * Math.abs(1.0 - e)) { /* cubic term is dominant */ 250 | if (M < Math.PI) { 251 | trial = Math.pow(6.0 * M, 1/3); 252 | } else { /* hyperbolic w/ 5th & higher-order terms predominant */ 253 | trial = Trig.asinh( M / e); 254 | } 255 | } 256 | curr = trial; 257 | } 258 | if (e > 1.0 && M > 4.0) { /* hyperbolic, large-mean-anomaly case */ 259 | curr = Math.log(M); 260 | } 261 | if (e < 1.0) { 262 | while(Math.abs(delta_curr) > thresh) { 263 | if ( n_iter++ > 8) { 264 | err = near_parabolic(curr, e) - M; 265 | } else { 266 | err = curr - e * Math.sin(curr) - M; 267 | } 268 | delta_curr = -err / (1.0 - e * Math.cos(curr)); 269 | curr += delta_curr; 270 | } 271 | } else { 272 | while (Math.abs(delta_curr) > thresh) { 273 | if (n_iter++ > 7) { 274 | err = -near_parabolic(curr, e) - M; 275 | } else { 276 | err = e * Trig.sinh(curr) - curr - M; 277 | } 278 | delta_curr = -err / (e * Trig.cosh(curr) - 1.0); 279 | curr += delta_curr; 280 | } 281 | } 282 | return( is_negative ? offset - curr : offset + curr); 283 | } 284 | 285 | function trueAnomaly(dat) { 286 | var v, r, x, y, r0, g, t; 287 | 288 | if (dat.e === 1.0) { /* parabolic */ 289 | t = dat.jd0 - dat.T; 290 | g = dat.w0 * t * 0.5; 291 | 292 | y = Math.pow(g + Math.sqrt(g * g + 1.0), 1/3); 293 | dat.v = 2.0 * Math.atan(y - 1.0 / y); 294 | } else { /* got the mean anomaly; compute eccentric, then true */ 295 | dat.E = kepler(dat); 296 | if (dat.e > 1.0) { /* hyperbolic case */ 297 | x = (dat.e - Trig.cosh(dat.E)); 298 | y = Trig.sinh(dat.E); 299 | } else { /* elliptical case */ 300 | x = (Math.cos(dat.E) - dat.e); 301 | y = Math.sin(dat.E); 302 | } 303 | y *= Math.sqrt(Math.abs(1.0 - dat.e * dat.e)); 304 | dat.v = Math.atan2(y, x); 305 | } 306 | 307 | r0 = dat.q * (1.0 + dat.e); 308 | dat.r = r0 / (1.0 + dat.e * Math.cos(dat.v)); 309 | } 310 | 311 | function derive(dat) { 312 | if (!dat.hasOwnProperty("w")) { 313 | dat.w = dat.W - dat.N; 314 | } 315 | if (!dat.hasOwnProperty("M")) { 316 | dat.M = dat.L - dat.W; 317 | } 318 | if (dat.e < 1.0) { dat.M = Trig.normalize0(dat.M); } 319 | //dat.P = Math.pow(Math.abs(dat.a), 1.5); 320 | dat.P = τ * Math.sqrt(Math.pow(dat.a, 3) / gm) / 365.25; 321 | dat.T = dat.jd0 - (dat.M / halfπ) / dat.P; 322 | 323 | if (dat.e !== 1.0) { /* for non-parabolic orbits: */ 324 | dat.q = dat.a * (1.0 - dat.e); 325 | dat.t0 = dat.a * Math.sqrt(Math.abs(dat.a) / gm); 326 | } else { 327 | dat.w0 = (3.0 / Math.sqrt(2)) / (dat.q * Math.sqrt(dat.q / gm)); 328 | dat.a = 0.0; 329 | dat.t0 = 0.0; 330 | } 331 | dat.am = Math.sqrt(gm * dat.q * (1.0 + dat.e)); 332 | } 333 | 334 | function transpose(dat) { 335 | if (!dat.ref || dat.ref === "ecl") { 336 | dat.tx = dat.x; 337 | dat.ty = dat.y; 338 | dat.tz = dat.z; 339 | return; 340 | } 341 | var a0 = dat.lecl,// - Math.PI/2, 342 | a1 = Math.PI/2 - dat.becl, 343 | angles = [0, a1, -a0]; 344 | euler(dat, angles); 345 | var tp = Trig.cartesian([dat.tl, dat.tb, dat.r]); 346 | dat.tx = tp.x; 347 | dat.ty = tp.y; 348 | dat.tz = tp.z; 349 | } 350 | 351 | function equatorial(dat) { 352 | dat.xeq = dat.x; 353 | dat.yeq = cosε * dat.y - sinε * dat.z; 354 | dat.zeq = sinε * dat.y + cosε * dat.z; 355 | dat.ra = Math.atan2(dat.yeq, dat.xeq); 356 | dat.dec = Math.atan2(dat.zeq, Math.sqrt(dat.xeq*dat.xeq + dat.yeq*dat.yeq)); 357 | } 358 | 359 | function cartesian(dat) { 360 | var x, y, z, u = dat.v + dat.w; 361 | x = dat.r * (Math.cos(dat.N) * Math.cos(u) - Math.sin(dat.N) * Math.sin(u) * Math.cos(dat.i)); 362 | y = dat.r * (Math.sin(dat.N) * Math.cos(u) + Math.cos(dat.N) * Math.sin(u) * Math.cos(dat.i)); 363 | z = dat.r * (Math.sin(u) * Math.sin(dat.i)); 364 | dat.x = x; 365 | dat.y = y; 366 | dat.z = z; 367 | return {x:x, y:y, z:z}; 368 | } 369 | 370 | function spherical(dat) { 371 | var lon, lat; 372 | lon = Math.atan2(dat.y, dat.x); 373 | lat = Math.atan2(dat.z, Math.sqrt(dat.x*dat.x + dat.y*dat.y)); 374 | dat.l = Trig.normalize(lon); 375 | dat.b = lat; 376 | return {l:lon, b:lat}; 377 | } 378 | 379 | function JD(dt) { 380 | var yr = dt.getUTCFullYear(), 381 | mo = dt.getUTCMonth() + 1, 382 | dy = dt.getUTCDate(), 383 | frac = (dt.getUTCHours() - 12 + dt.getUTCMinutes()/60.0 + dt.getUTCSeconds()/3600.0) / 24, 384 | IYMIN = -4799; /* Earliest year allowed (4800BC) */ 385 | 386 | if (yr < IYMIN) return -1; 387 | var a = Math.floor((14 - mo) / 12), 388 | y = yr + 4800 - a, 389 | m = mo + (12 * a) - 3; 390 | var jdn = dy + Math.floor((153 * m + 2)/5) + (365 * y) + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) - 32045; 391 | return jdn + frac; 392 | } 393 | 394 | //Transform coordinates according to euler angles 395 | function euler(dat, angles) { 396 | var x, y, z, β, γ, λ, φ, dψ, ψ, θ, 397 | ε = 1.0e-5; 398 | 399 | if (!angles) return [dat.l, dat.b]; 400 | 401 | λ = dat.l; // longitude 0..2 Pi 402 | if (λ < 0) λ += τ; 403 | φ = dat.b; // latitude -Pi/2..Pi/2 404 | 405 | λ -= angles[0]; // celestial longitude of the native pole 406 | β = angles[1]; // inclination between the poles (colatitude) 407 | γ = angles[2]; // native longitude of the celestial pole 408 | 409 | x = Math.sin(φ) * Math.sin(β) - Math.cos(φ) * Math.cos(β) * Math.cos(λ); 410 | if (Math.abs(x) < ε) { 411 | x = -Math.cos(φ + β) + Math.cos(φ) * Math.cos(β) * (1 - Math.cos(λ)); 412 | } 413 | y = -Math.cos(φ) * Math.sin(λ); 414 | 415 | if (x !== 0 || y !== 0) { 416 | dψ = Math.atan2(y, x); 417 | } else { 418 | dψ = λ - Math.PI; 419 | } 420 | ψ = (γ + dψ); 421 | if (ψ < 0) ψ += τ; 422 | 423 | if (λ % Math.PI === 0) { 424 | θ = φ + Math.cos(λ) * β; 425 | if (θ > halfπ) θ = Math.PI - θ; 426 | if (θ < -halfπ) θ = -Math.PI - θ; 427 | } else { 428 | z = Math.sin(φ) * Math.cos(β) + Math.cos(φ) * Math.sin(β) * Math.cos(λ); 429 | if (Math.abs(z) > 0.99) { 430 | θ = Math.abs(Math.acos(Math.sqrt(x*x+y*y))); 431 | if (z < 0) θ *= -1; 432 | } else { 433 | θ = Math.asin(z); 434 | } 435 | } 436 | 437 | dat.tl = ψ; 438 | dat.tb = θ; 439 | return [ψ, θ]; 440 | } 441 | -------------------------------------------------------------------------------- /threex.planets.js: -------------------------------------------------------------------------------- 1 | var THREEx = THREEx || {}; 2 | 3 | THREEx.Planets = {}; 4 | 5 | THREEx.Planets.baseURL = "../images/maps/"; 6 | // Proportional scaling of planetary spheres 7 | THREEx.Planets.scale = 1.0; 8 | 9 | // maps from http://planetpixelemporium.com/ and others (see readme) 10 | 11 | THREEx.Planets.params = { 12 | // texture map, bump map, cloud map, arbitrary radius, axis tilt in degrees, rotation period in days, 13 | // rotation: axis pole long/lat in eclipic coordinate in degrees, angular velocity in degrees/day 14 | // ring: texture map, outer radius rel. to planet, opacity 15 | "sol": {map: "sunmap.jpg", radius: 1.2, tilt: 7.25, rot: 1.0438, rotation:[286.13, 63.87, 14.1844], 16 | corona: {map: "solarcorona.jpg", radius:5.1} }, 17 | "mer": {map: "mercurymap.jpg", bump:"mercurybump.jpg", radius: 0.3, tilt: 0, rot: 58.646, rotation:[318.2274, 82.9623, 6.1385]}, 18 | "ven": {map: "venusmap2.jpg", radius: 0.4, tilt: 177.3, rot: -4.05, rotation:[30.1871, 88.761, -1.481]}, 19 | "ter": {map: "earthmap.jpg", bump:"earthbump.jpg", clouds:"earthclouds.png", radius: 0.4, tilt: 23.45, rot: 0.9973, rotation:[90, 66.5607, 360.9856]}, 20 | "lun": {map: "moonmap.jpg", bump:"moonbump.jpg", radius: 0.25, tilt: 1.54, rot: 27.3217, rotation:[264.6051, 89.9784, 13.1763]}, 21 | "mar": {map: "marsmap.jpg", bump:"marsbump.jpg", clouds:"marsclouds.png", radius: 0.35, tilt: 25.19, rot: 1.026, rotation:[352.9076, 63.2821, 350.8919] }, 22 | "phob": {map: "phobosmap.jpg", bump: "phobosbump.jpg", radius: 0.05, tilt: 0.009, rot: 0.3189, rotation:[352.9259, 63.2928, 1128.8445]}, 23 | "deim": {map: "deimosmap.jpg", radius: 0.04, tilt: 0.889, rot: 1.2624, rotation:[352.801, 64.1656, 285.1618]}, 24 | "vest": {map: "vestamap.jpg", bump: "vestabump.jpg", radius: 0.1, tilt: 29.0, rot: 0.223, rotation:[330.8257, 57.7239, 1617.3329]}, 25 | "cer": {map: "ceresmap.jpg", radius: 0.16, tilt: 4.0, rot: 0.378, rotation:[331.5058, 77.8769, 952.1532]}, 26 | "jup": {map: "jupitermap.jpg", radius: 1.2, tilt: 3.12, rot: 0.414, rotation:[247.8167, 87.7835, 870.5360], 27 | ring: {map: "jupiterrings.png", radius: 2.7, opacity: 0.5} }, 28 | "io": {map: "iomap.jpg", radius: 0.25, tilt: 0.0, rot: 1.7691, rotation:[247.7063, 87.7869, 203.4889]}, 29 | "euro": {map: "europamap.jpg", radius: 0.25, tilt: 0.016, rot: 3.5512, rotation:[247.93, 87.8008, 101.3747]}, 30 | "gany": {map: "ganymedemap.jpg", radius: 0.3, tilt: 0.068, rot: 7.1546, rotation:[248.6707, 87.8748, 50.3176]}, 31 | "call": {map: "callistomap.jpg", radius: 0.3, tilt: 0.356, rot: 16.689, rotation:[252.4849, 88.191, 21.5710]}, 32 | "sat": {map: "saturnmap.jpg", radius: 1.2, tilt: 26.73, rot: 0.444, rotation:[79.5275, 61.9478, 810.7939], 33 | ring: {map: "saturnrings.png", radius: 2.6, opacity: 1.0} }, 34 | "mima": {map: "mimasmap.jpg", radius: 0.1, tilt: 0.002, rot: 0.942, rotation:[79.5174, 61.9296, 381.9945]}, 35 | "ence": {map: "enceladusmap.jpg", radius: 0.1, tilt: 0.002, rot: 1.37, rotation:[79.5174, 61.9296, 262.7318]}, 36 | "teth": {map: "tethysmap.jpg", radius: 0.15, tilt: 0.001, rot: 1.888, rotation:[79.5174, 61.9296, 190.6979]}, 37 | "dion": {map: "dionemap.jpg", radius: 0.15, tilt: 0.005, rot: 2.737, rotation:[79.5174, 61.9296, 131.5349]}, 38 | "rhea": {map: "rheamap.jpg", radius: 0.2, tilt: 0.036, rot: 4.518, rotation:[79.507, 61.9729, 79.690]}, 39 | "tita": {map: "titanclouds.jpg", radius: 0.35, tilt: 0.629, rot: 15.95, rotation:[79.1738, 61.946, 22.577]}, 40 | "hype": {map: "hyperionmap.jpg", radius: 0.08, tilt: 0.564, rot: 21.28, rotation:[79.3687, 61.9625, 16.917]}, 41 | "iape": {map: "iapetusmap.jpg", radius: 0.15, tilt: 15.21, rot: 79.33, rotation:[49.608, 72.7238, 4.5379]}, 42 | "phoe": {map: "phoebemap.jpg", radius: 0.08, tilt: 26.723, rot: 0.4, rotation:[60.8478, 64.3305, 931.639]}, 43 | "ura": {map: "uranusmap.jpg", radius: 1.0, tilt: 97.86, rot: 0.718, rotation:[77.6467, -7.7218, 501.1601], 44 | ring: {map: "uranusrings.png", radius: 2.0, opacity: 0.5} }, 45 | "arie": {map: "arielmap.jpg", radius: 0.15, tilt: 0.0, rot: -2.520, rotation:[77.7555, -7.8066, -142.8357]}, 46 | "umbr": {map: "umbrielmap.jpg", radius: 0.15, tilt: 0.0, rot: -4.144, rotation:[77.7555, -7.8066, -86.8689]}, 47 | "titan": {map: "titaniamap.jpg", radius: 0.2, tilt: 0.0, rot: -8.706, rotation:[77.7555, -7.8066, -41.3514]}, 48 | "ober": {map: "oberonmap.jpg", radius: 0.2, tilt: 0.0, rot: -13.46, rotation:[77.7555, -7.8066, -26.7394]}, 49 | "mira": {map: "mirandamap.jpg", radius: 0.1, tilt: 0.0, rot: -1.413, rotation:[77.7538, -7.8265, -254.6906]}, 50 | "nep": {map: "neptunemap.jpg", radius: 1.0, tilt: 29.56, rot: 0.671, rotation:[319.2351, 61.9736, 536.3128], 51 | ring: {map: "neptunerings.png", radius: 2.5, opacity: 0.8} }, 52 | "trit": {map: "tritonmap.jpg", radius: 0.2, tilt: 0.01, rot: -5.877, rotation:[317.3413, 59.8764, -61.2573]}, 53 | "prot": {map: "proteusmap.jpg", radius: 0.1, tilt: 0.974, rot: 1.122, rotation:[318.631, 61.4993, 320.7654]}, 54 | "plu": {map: "plutomap.jpg", radius: 0.2, tilt: 122.53, rot: 6.387, rotation:[133.6817, -10.9977, 56.3625]}, 55 | "cha": {map: "charonmap.jpg", radius: 0.1, tilt: 0.0, rot: 6.387, rotation:[133.6817, -10.9977, 56.3625]} 56 | }; 57 | 58 | 59 | // Friendly names 60 | var substitutes = { 61 | "Sun": "sol", 62 | "Mercury": "mer", 63 | "Venus": "ven", 64 | "Earth": "ter", 65 | "Moon": "lun", 66 | "Mars": "mar", 67 | "Phobos": "phob", 68 | "Deimos": "deim", 69 | "Vesta": "vest", 70 | "Ceres": "cer", 71 | "Jupiter": "jup", 72 | "Io": "io", 73 | "Europa": "euro", 74 | "Ganymede": "gany", 75 | "Callisto": "call", 76 | "Saturn": "sat", 77 | "Mimas": "mima", 78 | "Enceladus": "ence", 79 | "Tethys": "teth", 80 | "Dione": "dion", 81 | "Rhea": "rhea", 82 | "Titan": "tita", 83 | "Iapetus": "iape", 84 | "Phoebe": "phoe", 85 | "Uranus": "ura", 86 | "Ariel": "arie", 87 | "Umbriel": "umbr", 88 | "Titania": "titan", 89 | "Oberon": "ober", 90 | "Miranda": "mira", 91 | "Neptune": "nep", 92 | "Triton": "trit", 93 | "Pluto": "plu", 94 | "Charon": "cha", 95 | }; 96 | 97 | THREEx.Planets.createSun = function() { return THREEx.Planets.create("sol"); }; 98 | THREEx.Planets.createMercury = function() { return THREEx.Planets.create("mer"); }; 99 | THREEx.Planets.createVenus = function() { return THREEx.Planets.create("ven"); }; 100 | THREEx.Planets.createEarth = function() { return THREEx.Planets.create("ter", true); }; 101 | THREEx.Planets.createMoon = function() { return THREEx.Planets.create("lun"); }; 102 | THREEx.Planets.createMars = function() { return THREEx.Planets.create("mar"); }; 103 | THREEx.Planets.createJupiter = function() { return THREEx.Planets.create("jup"); }; 104 | THREEx.Planets.createJupiterRing = function() { return THREEx.Planets.createRing("jup"); }; 105 | THREEx.Planets.createSaturn = function() { return THREEx.Planets.create("sat", true); }; 106 | THREEx.Planets.createSaturnRing = function() { return THREEx.Planets.createRing("sat"); }; 107 | THREEx.Planets.createUranus = function() { return THREEx.Planets.create("ura", true); }; 108 | THREEx.Planets.createUranusRing = function() { return THREEx.Planets.createRing("ura"); }; 109 | THREEx.Planets.createNeptune = function() { return THREEx.Planets.create("nepe"); }; 110 | THREEx.Planets.createNeptuneRing = function() { return THREEx.Planets.createRing("nep"); }; 111 | THREEx.Planets.createPluto = function() { return THREEx.Planets.create("plu"); }; 112 | 113 | THREEx.Planets.createStarfield = function() { 114 | var loader = new THREE.TextureLoader(); 115 | var texture = loader.load(THREEx.Planets.baseURL + "tycho-skymap.jpg"); 116 | var material = new THREE.MeshBasicMaterial({ 117 | map : texture, 118 | side : THREE.BackSide 119 | }) 120 | var geometry = new THREE.SphereGeometry(100000, 32, 32) 121 | var mesh = new THREE.Mesh(geometry, material) 122 | return mesh 123 | } 124 | 125 | // Create body, skipextras true -> don't create cloud, ring etc. 126 | THREEx.Planets.create = function(body, skipextras) { 127 | if (!THREEx.Planets.params.hasOwnProperty(body)) { 128 | if (substitutes.hasOwnProperty(body)) body = substitutes[body]; 129 | else { 130 | console.log("Object not found: " + body); 131 | return null; 132 | } 133 | } 134 | var p = THREEx.Planets.params[body], arg = {}; 135 | var loader = new THREE.TextureLoader(); 136 | 137 | var geometry = new THREE.SphereGeometry(p.radius * THREEx.Planets.scale, 32, 32); 138 | 139 | arg.map = loader.load(THREEx.Planets.baseURL + p.map); 140 | 141 | if (p.hasOwnProperty("bump")) { 142 | arg.bumpMap = loader.load(THREEx.Planets.baseURL + p.bump); 143 | arg.bumpScale = 0.001; 144 | } 145 | if (p.hasOwnProperty("spec")) { 146 | arg.specularMap = loader.load(THREEx.Planets.baseURL + p.spec); 147 | } 148 | 149 | if (body === "sol") { //ommmmmmm 150 | var material = new THREE.MeshBasicMaterial(arg); 151 | } else { 152 | var material = new THREE.MeshPhongMaterial(arg); 153 | arg.specular = new THREE.Color( 0x333333 ); 154 | arg.shininess = 0.1; 155 | } 156 | var mesh = new THREE.Mesh(geometry, material); 157 | 158 | if (!skipextras && p.hasOwnProperty("ring")) { 159 | mesh.receiveShadow = true; 160 | mesh.castShadow = true; 161 | var ring = THREEx.Planets.createRings(body); 162 | ring.receiveShadow = true; 163 | ring.castShadow = true; 164 | mesh.add(ring); 165 | }; 166 | 167 | if (!skipextras && p.hasOwnProperty("clouds")) { 168 | mesh.add(THREEx.Planets.createClouds(body)); 169 | } 170 | 171 | if (!skipextras && body === "sol") { 172 | mesh.add(THREEx.Planets.createCorona()); 173 | } 174 | mesh.rotateY(THREE.Math.degToRad(p.rotation[0]-180)); 175 | mesh.rotateZ(THREE.Math.degToRad(90-p.rotation[1])); 176 | return mesh; 177 | } 178 | 179 | 180 | // Planetary rings 181 | THREEx.Planets.createRings = function(body) { 182 | if (!THREEx.Planets.params.hasOwnProperty(body)) { 183 | if (substitutes.hasOwnProperty(body)) body = substitutes[body]; 184 | else { 185 | console.log("Object not found: " + body); 186 | return null; 187 | } 188 | } 189 | if (!THREEx.Planets.params[body].hasOwnProperty("ring")) { 190 | console.log("Rings not found: " + body); 191 | return null; 192 | } 193 | var p = THREEx.Planets.params[body], map = THREEx.Planets.baseURL + p.ring.map, 194 | loader = new THREE.TextureLoader(); 195 | 196 | var geometry = new THREEx.Planets.RingGeometry(p.radius * THREEx.Planets.scale * 1.05, p.ring.radius * THREEx.Planets.scale, 64, 64); 197 | var material = new THREE.MeshPhongMaterial({ 198 | map: loader.load(map), 199 | side: THREE.DoubleSide, 200 | transparent: true, 201 | opacity: p.ring.opacity 202 | }); 203 | var mesh = new THREE.Mesh(geometry, material); 204 | mesh.lookAt(new THREE.Vector3(0, 1, 0)); 205 | mesh.name = body + "rings"; 206 | return mesh; 207 | } 208 | 209 | // Cloud layer from transparent png, see http://blog.thematicmapping.org/2013/09/creating-webgl-earth-with-threejs.html 210 | THREEx.Planets.createClouds = function(body) { 211 | if (!THREEx.Planets.params.hasOwnProperty(body)) { 212 | if (substitutes.hasOwnProperty(body)) body = substitutes[body]; 213 | else { 214 | console.log("Object not found: " + body); 215 | return null; 216 | } 217 | } 218 | if (!THREEx.Planets.params[body].hasOwnProperty("clouds")) { 219 | console.log("Clouds not found: " + body); 220 | return null; 221 | } 222 | var p = THREEx.Planets.params[body], map = THREEx.Planets.baseURL + p.clouds, 223 | loader = new THREE.TextureLoader(); 224 | 225 | var mesh = new THREE.Mesh( 226 | new THREE.SphereGeometry(p.radius * THREEx.Planets.scale * 1.01, 32, 32), 227 | new THREE.MeshPhongMaterial({ 228 | map: loader.load(map), 229 | transparent: true 230 | }) 231 | ); 232 | mesh.name = body + "clouds"; 233 | return mesh; 234 | } 235 | 236 | // Solar corona, based on Lee Stemkoski's https://github.com/stemkoski/stemkoski.github.com/blob/master/Three.js/Simple-Glow.html 237 | THREEx.Planets.createCorona = function() { 238 | var p = THREEx.Planets.params.sol, map = THREEx.Planets.baseURL + p.corona.map; 239 | 240 | var material = new THREE.SpriteMaterial({ 241 | map: new THREE.TextureLoader().load(map), 242 | color: 0xffff33, 243 | transparent: false, 244 | blending: THREE.AdditiveBlending 245 | }); 246 | var mesh = new THREE.Sprite(material); 247 | mesh.scale.multiplyScalar(p.corona.radius * THREEx.Planets.scale); 248 | mesh.name = "solcorona"; 249 | return mesh; 250 | }; 251 | 252 | 253 | 254 | /** 255 | * change the original from three.js because i needed different UV 256 | * 257 | * @author Kaleb Murphy 258 | * @author Olaf Frohn 259 | */ 260 | THREEx.Planets.RingGeometry = function (innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength) { 261 | 262 | THREE.Geometry.call( this ); 263 | 264 | this.type = 'RingGeometry'; 265 | 266 | this.parameters = { 267 | innerRadius: innerRadius, 268 | outerRadius: outerRadius, 269 | thetaSegments: thetaSegments, 270 | phiSegments: phiSegments, 271 | thetaStart: thetaStart, 272 | thetaLength: thetaLength 273 | }; 274 | 275 | innerRadius = innerRadius || 0; 276 | outerRadius = outerRadius || 50; 277 | 278 | thetaStart = thetaStart !== undefined ? thetaStart : 0; 279 | thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2; 280 | 281 | thetaSegments = thetaSegments !== undefined ? Math.max( 3, thetaSegments ) : 8; 282 | phiSegments = phiSegments !== undefined ? Math.max( 1, phiSegments ) : 8; 283 | 284 | var i, o, uvs = [], radius = innerRadius, radiusStep = ( ( outerRadius - innerRadius ) / phiSegments ); 285 | 286 | for ( i = 0; i < phiSegments + 1; i ++ ) { 287 | 288 | // concentric circles inside ring 289 | 290 | for ( o = 0; o < thetaSegments + 1; o ++ ) { 291 | 292 | // number of segments per circle 293 | 294 | var vertex = new THREE.Vector3(); 295 | var segment = thetaStart + o / thetaSegments * thetaLength; 296 | vertex.x = radius * Math.cos( segment ); 297 | vertex.y = radius * Math.sin( segment ); 298 | 299 | this.vertices.push( vertex ); 300 | //uvs.push( new THREE.Vector2( ( vertex.x / outerRadius + 1 ) / 2, ( vertex.y / outerRadius + 1 ) / 2 ) ); 301 | uvs.push( new THREE.Vector2( i/(thetaSegments-1), o/ (phiSegments-1) ) ); 302 | } 303 | 304 | radius += radiusStep; 305 | 306 | } 307 | 308 | var n = new THREE.Vector3( 0, 0, 1 ); 309 | 310 | for ( i = 0; i < phiSegments; i ++ ) { 311 | 312 | // concentric circles inside ring 313 | 314 | var thetaSegment = i * ( thetaSegments + 1 ); 315 | 316 | for ( o = 0; o < thetaSegments ; o ++ ) { 317 | 318 | // number of segments per circle 319 | 320 | var segment = o + thetaSegment; 321 | 322 | var v1 = segment; 323 | var v2 = segment + thetaSegments + 1; 324 | var v3 = segment + thetaSegments + 2; 325 | 326 | this.faces.push( new THREE.Face3( v1, v2, v3, [ n.clone(), n.clone(), n.clone() ] ) ); 327 | this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ].clone(), uvs[ v2 ].clone(), uvs[ v3 ].clone() ] ); 328 | 329 | v1 = segment; 330 | v2 = segment + thetaSegments + 2; 331 | v3 = segment + 1; 332 | 333 | this.faces.push( new THREE.Face3( v1, v2, v3, [ n.clone(), n.clone(), n.clone() ] ) ); 334 | this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ].clone(), uvs[ v2 ].clone(), uvs[ v3 ].clone() ] ); 335 | 336 | } 337 | 338 | } 339 | 340 | this.computeFaceNormals(); 341 | 342 | this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); 343 | 344 | }; 345 | 346 | THREEx.Planets.RingGeometry.prototype = Object.create( THREE.Geometry.prototype ); 347 | THREEx.Planets.RingGeometry.prototype.constructor = THREEx.Planets.RingGeometry; 348 | -------------------------------------------------------------------------------- /lib/threex.planets.js: -------------------------------------------------------------------------------- 1 | var THREEx = THREEx || {}; 2 | 3 | THREEx.Planets = {}; 4 | 5 | THREEx.Planets.baseURL = "../images/maps/"; 6 | // Proportional scaling of planetary spheres 7 | THREEx.Planets.scale = 1.0; 8 | 9 | // maps from http://planetpixelemporium.com/ and others (see readme) 10 | 11 | THREEx.Planets.params = { 12 | // texture map, bump map, cloud map, arbitrary radius, axis tilt in degrees, rotation period in days, 13 | // rotation: axis pole long/lat in eclipic coordinate in degrees, angular velocity in degrees/day 14 | // ring: texture map, outer radius rel. to planet, opacity 15 | "sol": {map: "sunmap.jpg", radius: 1.2, tilt: 7.25, rot: 1.0438, rotation:[286.13, 63.87, 14.1844], 16 | corona: {map: "solarcorona.jpg", radius:5.1} }, 17 | "mer": {map: "mercurymap.jpg", bump:"mercurybump.jpg", radius: 0.3, tilt: 0, rot: 58.646, rotation:[318.2274, 82.9623, 6.1385]}, 18 | "ven": {map: "venusmap2.jpg", radius: 0.4, tilt: 177.3, rot: -4.05, rotation:[30.1871, 88.761, -1.481]}, 19 | "ter": {map: "earthmap.jpg", bump:"earthbump.jpg", clouds:"earthclouds.png", radius: 0.4, tilt: 23.45, rot: 0.9973, rotation:[90, 66.5607, 360.9856]}, 20 | "lun": {map: "moonmap.jpg", bump:"moonbump.jpg", radius: 0.25, tilt: 1.54, rot: 27.3217, rotation:[264.6051, 89.9784, 13.1763]}, 21 | "mar": {map: "marsmap.jpg", bump:"marsbump.jpg", clouds:"marsclouds.png", radius: 0.35, tilt: 25.19, rot: 1.026, rotation:[352.9076, 63.2821, 350.8919] }, 22 | "phob": {map: "phobosmap.jpg", bump: "phobosbump.jpg", radius: 0.05, tilt: 0.009, rot: 0.3189, rotation:[352.9259, 63.2928, 1128.8445]}, 23 | "deim": {map: "deimosmap.jpg", radius: 0.04, tilt: 0.889, rot: 1.2624, rotation:[352.801, 64.1656, 285.1618]}, 24 | "vest": {map: "vestamap.jpg", bump: "vestabump.jpg", radius: 0.1, tilt: 29.0, rot: 0.223, rotation:[330.8257, 57.7239, 1617.3329]}, 25 | "cer": {map: "ceresmap.jpg", radius: 0.16, tilt: 4.0, rot: 0.378, rotation:[331.5058, 77.8769, 952.1532]}, 26 | "jup": {map: "jupitermap.jpg", radius: 1.2, tilt: 3.12, rot: 0.414, rotation:[247.8167, 87.7835, 870.5360], 27 | ring: {map: "jupiterrings.png", radius: 2.7, opacity: 0.5} }, 28 | "io": {map: "iomap.jpg", radius: 0.25, tilt: 0.0, rot: 1.7691, rotation:[247.7063, 87.7869, 203.4889]}, 29 | "euro": {map: "europamap.jpg", radius: 0.25, tilt: 0.016, rot: 3.5512, rotation:[247.93, 87.8008, 101.3747]}, 30 | "gany": {map: "ganymedemap.jpg", radius: 0.3, tilt: 0.068, rot: 7.1546, rotation:[248.6707, 87.8748, 50.3176]}, 31 | "call": {map: "callistomap.jpg", radius: 0.3, tilt: 0.356, rot: 16.689, rotation:[252.4849, 88.191, 21.5710]}, 32 | "sat": {map: "saturnmap.jpg", radius: 1.2, tilt: 26.73, rot: 0.444, rotation:[79.5275, 61.9478, 810.7939], 33 | ring: {map: "saturnrings.png", radius: 2.6, opacity: 1.0} }, 34 | "mima": {map: "mimasmap.jpg", radius: 0.1, tilt: 0.002, rot: 0.942, rotation:[79.5174, 61.9296, 381.9945]}, 35 | "ence": {map: "enceladusmap.jpg", radius: 0.1, tilt: 0.002, rot: 1.37, rotation:[79.5174, 61.9296, 262.7318]}, 36 | "teth": {map: "tethysmap.jpg", radius: 0.15, tilt: 0.001, rot: 1.888, rotation:[79.5174, 61.9296, 190.6979]}, 37 | "dion": {map: "dionemap.jpg", radius: 0.15, tilt: 0.005, rot: 2.737, rotation:[79.5174, 61.9296, 131.5349]}, 38 | "rhea": {map: "rheamap.jpg", radius: 0.2, tilt: 0.036, rot: 4.518, rotation:[79.507, 61.9729, 79.690]}, 39 | "tita": {map: "titanclouds.jpg", radius: 0.35, tilt: 0.629, rot: 15.95, rotation:[79.1738, 61.946, 22.577]}, 40 | "hype": {map: "hyperionmap.jpg", radius: 0.08, tilt: 0.564, rot: 21.28, rotation:[79.3687, 61.9625, 16.917]}, 41 | "iape": {map: "iapetusmap.jpg", radius: 0.15, tilt: 15.21, rot: 79.33, rotation:[49.608, 72.7238, 4.5379]}, 42 | "phoe": {map: "phoebemap.jpg", radius: 0.08, tilt: 26.723, rot: 0.4, rotation:[60.8478, 64.3305, 931.639]}, 43 | "ura": {map: "uranusmap.jpg", radius: 1.0, tilt: 97.86, rot: 0.718, rotation:[77.6467, -7.7218, 501.1601], 44 | ring: {map: "uranusrings.png", radius: 2.0, opacity: 0.5} }, 45 | "arie": {map: "arielmap.jpg", radius: 0.15, tilt: 0.0, rot: -2.520, rotation:[77.7555, -7.8066, -142.8357]}, 46 | "umbr": {map: "umbrielmap.jpg", radius: 0.15, tilt: 0.0, rot: -4.144, rotation:[77.7555, -7.8066, -86.8689]}, 47 | "titan": {map: "titaniamap.jpg", radius: 0.2, tilt: 0.0, rot: -8.706, rotation:[77.7555, -7.8066, -41.3514]}, 48 | "ober": {map: "oberonmap.jpg", radius: 0.2, tilt: 0.0, rot: -13.46, rotation:[77.7555, -7.8066, -26.7394]}, 49 | "mira": {map: "mirandamap.jpg", radius: 0.1, tilt: 0.0, rot: -1.413, rotation:[77.7538, -7.8265, -254.6906]}, 50 | "nep": {map: "neptunemap.jpg", radius: 1.0, tilt: 29.56, rot: 0.671, rotation:[319.2351, 61.9736, 536.3128], 51 | ring: {map: "neptunerings.png", radius: 2.5, opacity: 0.8} }, 52 | "trit": {map: "tritonmap.jpg", radius: 0.2, tilt: 0.01, rot: -5.877, rotation:[317.3413, 59.8764, -61.2573]}, 53 | "prot": {map: "proteusmap.jpg", radius: 0.1, tilt: 0.974, rot: 1.122, rotation:[318.631, 61.4993, 320.7654]}, 54 | "plu": {map: "plutomap.jpg", radius: 0.2, tilt: 122.53, rot: 6.387, rotation:[133.6817, -10.9977, 56.3625]}, 55 | "cha": {map: "charonmap.jpg", radius: 0.1, tilt: 0.0, rot: 6.387, rotation:[133.6817, -10.9977, 56.3625]} 56 | }; 57 | 58 | 59 | // Friendly names 60 | var substitutes = { 61 | "Sun": "sol", 62 | "Mercury": "mer", 63 | "Venus": "ven", 64 | "Earth": "ter", 65 | "Moon": "lun", 66 | "Mars": "mar", 67 | "Phobos": "phob", 68 | "Deimos": "deim", 69 | "Vesta": "vest", 70 | "Ceres": "cer", 71 | "Jupiter": "jup", 72 | "Io": "io", 73 | "Europa": "euro", 74 | "Ganymede": "gany", 75 | "Callisto": "call", 76 | "Saturn": "sat", 77 | "Mimas": "mima", 78 | "Enceladus": "ence", 79 | "Tethys": "teth", 80 | "Dione": "dion", 81 | "Rhea": "rhea", 82 | "Titan": "tita", 83 | "Iapetus": "iape", 84 | "Phoebe": "phoe", 85 | "Uranus": "ura", 86 | "Ariel": "arie", 87 | "Umbriel": "umbr", 88 | "Titania": "titan", 89 | "Oberon": "ober", 90 | "Miranda": "mira", 91 | "Neptune": "nep", 92 | "Triton": "trit", 93 | "Pluto": "plu", 94 | "Charon": "cha", 95 | }; 96 | 97 | THREEx.Planets.createSun = function() { return THREEx.Planets.create("sol"); }; 98 | THREEx.Planets.createMercury = function() { return THREEx.Planets.create("mer"); }; 99 | THREEx.Planets.createVenus = function() { return THREEx.Planets.create("ven"); }; 100 | THREEx.Planets.createEarth = function() { return THREEx.Planets.create("ter", true); }; 101 | THREEx.Planets.createMoon = function() { return THREEx.Planets.create("lun"); }; 102 | THREEx.Planets.createMars = function() { return THREEx.Planets.create("mar"); }; 103 | THREEx.Planets.createJupiter = function() { return THREEx.Planets.create("jup"); }; 104 | THREEx.Planets.createJupiterRing = function() { return THREEx.Planets.createRing("jup"); }; 105 | THREEx.Planets.createSaturn = function() { return THREEx.Planets.create("sat", true); }; 106 | THREEx.Planets.createSaturnRing = function() { return THREEx.Planets.createRing("sat"); }; 107 | THREEx.Planets.createUranus = function() { return THREEx.Planets.create("ura", true); }; 108 | THREEx.Planets.createUranusRing = function() { return THREEx.Planets.createRing("ura"); }; 109 | THREEx.Planets.createNeptune = function() { return THREEx.Planets.create("nepe"); }; 110 | THREEx.Planets.createNeptuneRing = function() { return THREEx.Planets.createRing("nep"); }; 111 | THREEx.Planets.createPluto = function() { return THREEx.Planets.create("plu"); }; 112 | 113 | THREEx.Planets.createStarfield = function() { 114 | var loader = new THREE.TextureLoader(); 115 | var texture = loader.load(THREEx.Planets.baseURL + "tycho-skymap.jpg"); 116 | var material = new THREE.MeshBasicMaterial({ 117 | map : texture, 118 | side : THREE.BackSide 119 | }) 120 | var geometry = new THREE.SphereGeometry(100000, 32, 32) 121 | var mesh = new THREE.Mesh(geometry, material) 122 | return mesh 123 | } 124 | 125 | // Create body, skipextras true -> don't create cloud, ring etc. 126 | THREEx.Planets.create = function(body, skipextras) { 127 | if (!THREEx.Planets.params.hasOwnProperty(body)) { 128 | if (substitutes.hasOwnProperty(body)) body = substitutes[body]; 129 | else { 130 | console.log("Object not found: " + body); 131 | return null; 132 | } 133 | } 134 | var p = THREEx.Planets.params[body], arg = {}; 135 | var loader = new THREE.TextureLoader(); 136 | 137 | var geometry = new THREE.SphereGeometry(p.radius * THREEx.Planets.scale, 32, 32); 138 | 139 | arg.map = loader.load(THREEx.Planets.baseURL + p.map); 140 | 141 | if (p.hasOwnProperty("bump")) { 142 | arg.bumpMap = loader.load(THREEx.Planets.baseURL + p.bump); 143 | arg.bumpScale = 0.001; 144 | } 145 | if (p.hasOwnProperty("spec")) { 146 | arg.specularMap = loader.load(THREEx.Planets.baseURL + p.spec); 147 | } 148 | 149 | if (body === "sol") { //ommmmmmm 150 | var material = new THREE.MeshBasicMaterial(arg); 151 | } else { 152 | var material = new THREE.MeshPhongMaterial(arg); 153 | arg.specular = new THREE.Color( 0x333333 ); 154 | arg.shininess = 0.1; 155 | } 156 | var mesh = new THREE.Mesh(geometry, material); 157 | 158 | if (!skipextras && p.hasOwnProperty("ring")) { 159 | mesh.receiveShadow = true; 160 | mesh.castShadow = true; 161 | var ring = THREEx.Planets.createRings(body); 162 | ring.receiveShadow = true; 163 | ring.castShadow = true; 164 | mesh.add(ring); 165 | }; 166 | 167 | if (!skipextras && p.hasOwnProperty("clouds")) { 168 | mesh.add(THREEx.Planets.createClouds(body)); 169 | } 170 | 171 | if (!skipextras && body === "sol") { 172 | mesh.add(THREEx.Planets.createCorona()); 173 | } 174 | mesh.rotateY(THREE.Math.degToRad(p.rotation[0]-180)); 175 | mesh.rotateZ(THREE.Math.degToRad(90-p.rotation[1])); 176 | return mesh; 177 | } 178 | 179 | 180 | // Planetary rings 181 | THREEx.Planets.createRings = function(body) { 182 | if (!THREEx.Planets.params.hasOwnProperty(body)) { 183 | if (substitutes.hasOwnProperty(body)) body = substitutes[body]; 184 | else { 185 | console.log("Object not found: " + body); 186 | return null; 187 | } 188 | } 189 | if (!THREEx.Planets.params[body].hasOwnProperty("ring")) { 190 | console.log("Rings not found: " + body); 191 | return null; 192 | } 193 | var p = THREEx.Planets.params[body], map = THREEx.Planets.baseURL + p.ring.map, 194 | loader = new THREE.TextureLoader(); 195 | 196 | var geometry = new THREEx.Planets.RingGeometry(p.radius * THREEx.Planets.scale * 1.05, p.ring.radius * THREEx.Planets.scale, 64, 64); 197 | var material = new THREE.MeshPhongMaterial({ 198 | map: loader.load(map), 199 | side: THREE.DoubleSide, 200 | transparent: true, 201 | opacity: p.ring.opacity 202 | }); 203 | var mesh = new THREE.Mesh(geometry, material); 204 | mesh.lookAt(new THREE.Vector3(0, 1, 0)); 205 | mesh.name = body + "rings"; 206 | return mesh; 207 | } 208 | 209 | // Cloud layer from transparent png, see http://blog.thematicmapping.org/2013/09/creating-webgl-earth-with-threejs.html 210 | THREEx.Planets.createClouds = function(body) { 211 | if (!THREEx.Planets.params.hasOwnProperty(body)) { 212 | if (substitutes.hasOwnProperty(body)) body = substitutes[body]; 213 | else { 214 | console.log("Object not found: " + body); 215 | return null; 216 | } 217 | } 218 | if (!THREEx.Planets.params[body].hasOwnProperty("clouds")) { 219 | console.log("Clouds not found: " + body); 220 | return null; 221 | } 222 | var p = THREEx.Planets.params[body], map = THREEx.Planets.baseURL + p.clouds, 223 | loader = new THREE.TextureLoader(); 224 | 225 | var mesh = new THREE.Mesh( 226 | new THREE.SphereGeometry(p.radius * THREEx.Planets.scale * 1.01, 32, 32), 227 | new THREE.MeshPhongMaterial({ 228 | map: loader.load(map), 229 | transparent: true 230 | }) 231 | ); 232 | mesh.name = body + "clouds"; 233 | return mesh; 234 | } 235 | 236 | // Solar corona, based on Lee Stemkoski's https://github.com/stemkoski/stemkoski.github.com/blob/master/Three.js/Simple-Glow.html 237 | THREEx.Planets.createCorona = function() { 238 | var p = THREEx.Planets.params.sol, map = THREEx.Planets.baseURL + p.corona.map; 239 | 240 | var material = new THREE.SpriteMaterial({ 241 | map: new THREE.TextureLoader().load(map), 242 | color: 0xffff33, 243 | transparent: false, 244 | blending: THREE.AdditiveBlending 245 | }); 246 | var mesh = new THREE.Sprite(material); 247 | mesh.scale.multiplyScalar(p.corona.radius * THREEx.Planets.scale); 248 | mesh.name = "solcorona"; 249 | return mesh; 250 | }; 251 | 252 | 253 | 254 | /** 255 | * change the original from three.js because i needed different UV 256 | * 257 | * @author Kaleb Murphy 258 | * @author Olaf Frohn 259 | */ 260 | THREEx.Planets.RingGeometry = function (innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength) { 261 | 262 | THREE.Geometry.call( this ); 263 | 264 | this.type = 'RingGeometry'; 265 | 266 | this.parameters = { 267 | innerRadius: innerRadius, 268 | outerRadius: outerRadius, 269 | thetaSegments: thetaSegments, 270 | phiSegments: phiSegments, 271 | thetaStart: thetaStart, 272 | thetaLength: thetaLength 273 | }; 274 | 275 | innerRadius = innerRadius || 0; 276 | outerRadius = outerRadius || 50; 277 | 278 | thetaStart = thetaStart !== undefined ? thetaStart : 0; 279 | thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2; 280 | 281 | thetaSegments = thetaSegments !== undefined ? Math.max( 3, thetaSegments ) : 8; 282 | phiSegments = phiSegments !== undefined ? Math.max( 1, phiSegments ) : 8; 283 | 284 | var i, o, uvs = [], radius = innerRadius, radiusStep = ( ( outerRadius - innerRadius ) / phiSegments ); 285 | 286 | for ( i = 0; i < phiSegments + 1; i ++ ) { 287 | 288 | // concentric circles inside ring 289 | 290 | for ( o = 0; o < thetaSegments + 1; o ++ ) { 291 | 292 | // number of segments per circle 293 | 294 | var vertex = new THREE.Vector3(); 295 | var segment = thetaStart + o / thetaSegments * thetaLength; 296 | vertex.x = radius * Math.cos( segment ); 297 | vertex.y = radius * Math.sin( segment ); 298 | 299 | this.vertices.push( vertex ); 300 | //uvs.push( new THREE.Vector2( ( vertex.x / outerRadius + 1 ) / 2, ( vertex.y / outerRadius + 1 ) / 2 ) ); 301 | uvs.push( new THREE.Vector2( i/(thetaSegments-1), o/ (phiSegments-1) ) ); 302 | } 303 | 304 | radius += radiusStep; 305 | 306 | } 307 | 308 | var n = new THREE.Vector3( 0, 0, 1 ); 309 | 310 | for ( i = 0; i < phiSegments; i ++ ) { 311 | 312 | // concentric circles inside ring 313 | 314 | var thetaSegment = i * ( thetaSegments + 1 ); 315 | 316 | for ( o = 0; o < thetaSegments ; o ++ ) { 317 | 318 | // number of segments per circle 319 | 320 | var segment = o + thetaSegment; 321 | 322 | var v1 = segment; 323 | var v2 = segment + thetaSegments + 1; 324 | var v3 = segment + thetaSegments + 2; 325 | 326 | this.faces.push( new THREE.Face3( v1, v2, v3, [ n.clone(), n.clone(), n.clone() ] ) ); 327 | this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ].clone(), uvs[ v2 ].clone(), uvs[ v3 ].clone() ] ); 328 | 329 | v1 = segment; 330 | v2 = segment + thetaSegments + 2; 331 | v3 = segment + 1; 332 | 333 | this.faces.push( new THREE.Face3( v1, v2, v3, [ n.clone(), n.clone(), n.clone() ] ) ); 334 | this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ].clone(), uvs[ v2 ].clone(), uvs[ v3 ].clone() ] ); 335 | 336 | } 337 | 338 | } 339 | 340 | this.computeFaceNormals(); 341 | 342 | this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); 343 | 344 | }; 345 | 346 | THREEx.Planets.RingGeometry.prototype = Object.create( THREE.Geometry.prototype ); 347 | THREEx.Planets.RingGeometry.prototype.constructor = THREEx.Planets.RingGeometry; 348 | -------------------------------------------------------------------------------- /lib/OrbitControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | */ 8 | 9 | // This set of controls performs orbiting, dollying (zooming), and panning. 10 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 11 | // 12 | // Orbit - left mouse / touch: one finger move 13 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 14 | // Pan - right mouse, or arrow keys / touch: three finter swipe 15 | 16 | THREE.OrbitControls = function ( object, domElement ) { 17 | 18 | this.object = object; 19 | 20 | this.domElement = ( domElement !== undefined ) ? domElement : document; 21 | 22 | // Set to false to disable this control 23 | this.enabled = true; 24 | 25 | // "target" sets the location of focus, where the object orbits around 26 | this.target = new THREE.Vector3(); 27 | 28 | // How far you can dolly in and out ( PerspectiveCamera only ) 29 | this.minDistance = 0; 30 | this.maxDistance = Infinity; 31 | 32 | // How far you can zoom in and out ( OrthographicCamera only ) 33 | this.minZoom = 0; 34 | this.maxZoom = Infinity; 35 | 36 | // How far you can orbit vertically, upper and lower limits. 37 | // Range is 0 to Math.PI radians. 38 | this.minPolarAngle = 0; // radians 39 | this.maxPolarAngle = Math.PI; // radians 40 | 41 | // How far you can orbit horizontally, upper and lower limits. 42 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 43 | this.minAzimuthAngle = - Infinity; // radians 44 | this.maxAzimuthAngle = Infinity; // radians 45 | 46 | // Set to true to enable damping (inertia) 47 | // If damping is enabled, you must call controls.update() in your animation loop 48 | this.enableDamping = false; 49 | this.dampingFactor = 0.25; 50 | 51 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 52 | // Set to false to disable zooming 53 | this.enableZoom = true; 54 | this.zoomSpeed = 1.0; 55 | 56 | // Set to false to disable rotating 57 | this.enableRotate = true; 58 | this.rotateSpeed = 1.0; 59 | 60 | // Set to false to disable panning 61 | this.enablePan = true; 62 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 63 | 64 | // Set to true to automatically rotate around the target 65 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 66 | this.autoRotate = false; 67 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 68 | 69 | // Set to false to disable use of the keys 70 | this.enableKeys = true; 71 | 72 | // The four arrow keys 73 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 74 | 75 | // Mouse buttons 76 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 77 | 78 | // for reset 79 | this.target0 = this.target.clone(); 80 | this.position0 = this.object.position.clone(); 81 | this.zoom0 = this.object.zoom; 82 | 83 | // 84 | // public methods 85 | // 86 | 87 | this.getPolarAngle = function () { 88 | 89 | return phi; 90 | 91 | }; 92 | 93 | this.getAzimuthalAngle = function () { 94 | 95 | return theta; 96 | 97 | }; 98 | 99 | this.reset = function () { 100 | 101 | scope.target.copy( scope.target0 ); 102 | scope.object.position.copy( scope.position0 ); 103 | scope.object.zoom = scope.zoom0; 104 | 105 | scope.object.updateProjectionMatrix(); 106 | scope.dispatchEvent( changeEvent ); 107 | 108 | scope.update(); 109 | 110 | state = STATE.NONE; 111 | 112 | }; 113 | 114 | // this method is exposed, but perhaps it would be better if we can make it private... 115 | this.update = function() { 116 | 117 | var offset = new THREE.Vector3(); 118 | 119 | // so camera.up is the orbit axis 120 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 121 | var quatInverse = quat.clone().inverse(); 122 | 123 | var lastPosition = new THREE.Vector3(); 124 | var lastQuaternion = new THREE.Quaternion(); 125 | 126 | return function () { 127 | 128 | var position = scope.object.position; 129 | 130 | offset.copy( position ).sub( scope.target ); 131 | 132 | // rotate offset to "y-axis-is-up" space 133 | offset.applyQuaternion( quat ); 134 | 135 | // angle from z-axis around y-axis 136 | 137 | theta = Math.atan2( offset.x, offset.z ); 138 | 139 | // angle from y-axis 140 | 141 | phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); 142 | 143 | if ( scope.autoRotate && state === STATE.NONE ) { 144 | 145 | rotateLeft( getAutoRotationAngle() ); 146 | 147 | } 148 | 149 | theta += thetaDelta; 150 | phi += phiDelta; 151 | 152 | // restrict theta to be between desired limits 153 | theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, theta ) ); 154 | 155 | // restrict phi to be between desired limits 156 | phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, phi ) ); 157 | 158 | // restrict phi to be betwee EPS and PI-EPS 159 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); 160 | 161 | var radius = offset.length() * scale; 162 | 163 | // restrict radius to be between desired limits 164 | radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, radius ) ); 165 | 166 | // move target to panned location 167 | scope.target.add( panOffset ); 168 | 169 | offset.x = radius * Math.sin( phi ) * Math.sin( theta ); 170 | offset.y = radius * Math.cos( phi ); 171 | offset.z = radius * Math.sin( phi ) * Math.cos( theta ); 172 | 173 | // rotate offset back to "camera-up-vector-is-up" space 174 | offset.applyQuaternion( quatInverse ); 175 | 176 | position.copy( scope.target ).add( offset ); 177 | 178 | scope.object.lookAt( scope.target ); 179 | 180 | if ( scope.enableDamping === true ) { 181 | 182 | thetaDelta *= ( 1 - scope.dampingFactor ); 183 | phiDelta *= ( 1 - scope.dampingFactor ); 184 | 185 | } else { 186 | 187 | thetaDelta = 0; 188 | phiDelta = 0; 189 | 190 | } 191 | 192 | scale = 1; 193 | panOffset.set( 0, 0, 0 ); 194 | 195 | // update condition is: 196 | // min(camera displacement, camera rotation in radians)^2 > EPS 197 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 198 | 199 | if ( zoomChanged || 200 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 201 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 202 | 203 | scope.dispatchEvent( changeEvent ); 204 | 205 | lastPosition.copy( scope.object.position ); 206 | lastQuaternion.copy( scope.object.quaternion ); 207 | zoomChanged = false; 208 | 209 | return true; 210 | 211 | } 212 | 213 | return false; 214 | 215 | }; 216 | 217 | }(); 218 | 219 | this.dispose = function() { 220 | 221 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 222 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 223 | scope.domElement.removeEventListener( 'mousewheel', onMouseWheel, false ); 224 | scope.domElement.removeEventListener( 'MozMousePixelScroll', onMouseWheel, false ); // firefox 225 | 226 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 227 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 228 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 229 | 230 | document.removeEventListener( 'mousemove', onMouseMove, false ); 231 | document.removeEventListener( 'mouseup', onMouseUp, false ); 232 | document.removeEventListener( 'mouseout', onMouseUp, false ); 233 | 234 | window.removeEventListener( 'keydown', onKeyDown, false ); 235 | 236 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 237 | 238 | }; 239 | 240 | // 241 | // internals 242 | // 243 | 244 | var scope = this; 245 | 246 | var changeEvent = { type: 'change' }; 247 | var startEvent = { type: 'start' }; 248 | var endEvent = { type: 'end' }; 249 | 250 | var STATE = { NONE : - 1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 251 | 252 | var state = STATE.NONE; 253 | 254 | var EPS = 0.000001; 255 | 256 | // current position in spherical coordinates 257 | var theta; 258 | var phi; 259 | 260 | var phiDelta = 0; 261 | var thetaDelta = 0; 262 | var scale = 1; 263 | var panOffset = new THREE.Vector3(); 264 | var zoomChanged = false; 265 | 266 | var rotateStart = new THREE.Vector2(); 267 | var rotateEnd = new THREE.Vector2(); 268 | var rotateDelta = new THREE.Vector2(); 269 | 270 | var panStart = new THREE.Vector2(); 271 | var panEnd = new THREE.Vector2(); 272 | var panDelta = new THREE.Vector2(); 273 | 274 | var dollyStart = new THREE.Vector2(); 275 | var dollyEnd = new THREE.Vector2(); 276 | var dollyDelta = new THREE.Vector2(); 277 | 278 | function getAutoRotationAngle() { 279 | 280 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 281 | 282 | } 283 | 284 | function getZoomScale() { 285 | 286 | return Math.pow( 0.95, scope.zoomSpeed ); 287 | 288 | } 289 | 290 | function rotateLeft( angle ) { 291 | 292 | thetaDelta -= angle; 293 | 294 | } 295 | 296 | function rotateUp( angle ) { 297 | 298 | phiDelta -= angle; 299 | 300 | } 301 | 302 | var panLeft = function() { 303 | 304 | var v = new THREE.Vector3(); 305 | 306 | return function panLeft( distance, objectMatrix ) { 307 | 308 | var te = objectMatrix.elements; 309 | 310 | // get X column of objectMatrix 311 | v.set( te[ 0 ], te[ 1 ], te[ 2 ] ); 312 | 313 | v.multiplyScalar( - distance ); 314 | 315 | panOffset.add( v ); 316 | 317 | }; 318 | 319 | }(); 320 | 321 | var panUp = function() { 322 | 323 | var v = new THREE.Vector3(); 324 | 325 | return function panUp( distance, objectMatrix ) { 326 | 327 | var te = objectMatrix.elements; 328 | 329 | // get Y column of objectMatrix 330 | v.set( te[ 4 ], te[ 5 ], te[ 6 ] ); 331 | 332 | v.multiplyScalar( distance ); 333 | 334 | panOffset.add( v ); 335 | 336 | }; 337 | 338 | }(); 339 | 340 | // deltaX and deltaY are in pixels; right and down are positive 341 | var pan = function() { 342 | 343 | var offset = new THREE.Vector3(); 344 | 345 | return function( deltaX, deltaY ) { 346 | 347 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 348 | 349 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 350 | 351 | // perspective 352 | var position = scope.object.position; 353 | offset.copy( position ).sub( scope.target ); 354 | var targetDistance = offset.length(); 355 | 356 | // half of the fov is center to top of screen 357 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 358 | 359 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 360 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 361 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 362 | 363 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 364 | 365 | // orthographic 366 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / element.clientWidth, scope.object.matrix ); 367 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / element.clientHeight, scope.object.matrix ); 368 | 369 | } else { 370 | 371 | // camera neither orthographic nor perspective 372 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 373 | scope.enablePan = false; 374 | 375 | } 376 | 377 | }; 378 | 379 | }(); 380 | 381 | function dollyIn( dollyScale ) { 382 | 383 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 384 | 385 | scale /= dollyScale; 386 | 387 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 388 | 389 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 390 | scope.object.updateProjectionMatrix(); 391 | zoomChanged = true; 392 | 393 | } else { 394 | 395 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 396 | scope.enableZoom = false; 397 | 398 | } 399 | 400 | } 401 | 402 | function dollyOut( dollyScale ) { 403 | 404 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 405 | 406 | scale *= dollyScale; 407 | 408 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 409 | 410 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 411 | scope.object.updateProjectionMatrix(); 412 | zoomChanged = true; 413 | 414 | } else { 415 | 416 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 417 | scope.enableZoom = false; 418 | 419 | } 420 | 421 | } 422 | 423 | // 424 | // event callbacks - update the object state 425 | // 426 | 427 | function handleMouseDownRotate( event ) { 428 | 429 | //console.log( 'handleMouseDownRotate' ); 430 | 431 | rotateStart.set( event.clientX, event.clientY ); 432 | 433 | } 434 | 435 | function handleMouseDownDolly( event ) { 436 | 437 | //console.log( 'handleMouseDownDolly' ); 438 | 439 | dollyStart.set( event.clientX, event.clientY ); 440 | 441 | } 442 | 443 | function handleMouseDownPan( event ) { 444 | 445 | //console.log( 'handleMouseDownPan' ); 446 | 447 | panStart.set( event.clientX, event.clientY ); 448 | 449 | } 450 | 451 | function handleMouseMoveRotate( event ) { 452 | 453 | //console.log( 'handleMouseMoveRotate' ); 454 | 455 | rotateEnd.set( event.clientX, event.clientY ); 456 | rotateDelta.subVectors( rotateEnd, rotateStart ); 457 | 458 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 459 | 460 | // rotating across whole screen goes 360 degrees around 461 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 462 | 463 | // rotating up and down along whole screen attempts to go 360, but limited to 180 464 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 465 | 466 | rotateStart.copy( rotateEnd ); 467 | 468 | scope.update(); 469 | 470 | } 471 | 472 | function handleMouseMoveDolly( event ) { 473 | 474 | //console.log( 'handleMouseMoveDolly' ); 475 | 476 | dollyEnd.set( event.clientX, event.clientY ); 477 | 478 | dollyDelta.subVectors( dollyEnd, dollyStart ); 479 | 480 | if ( dollyDelta.y > 0 ) { 481 | 482 | dollyIn( getZoomScale() ); 483 | 484 | } else if ( dollyDelta.y < 0 ) { 485 | 486 | dollyOut( getZoomScale() ); 487 | 488 | } 489 | 490 | dollyStart.copy( dollyEnd ); 491 | 492 | scope.update(); 493 | 494 | } 495 | 496 | function handleMouseMovePan( event ) { 497 | 498 | //console.log( 'handleMouseMovePan' ); 499 | 500 | panEnd.set( event.clientX, event.clientY ); 501 | 502 | panDelta.subVectors( panEnd, panStart ); 503 | 504 | pan( panDelta.x, panDelta.y ); 505 | 506 | panStart.copy( panEnd ); 507 | 508 | scope.update(); 509 | 510 | } 511 | 512 | function handleMouseUp( event ) { 513 | 514 | //console.log( 'handleMouseUp' ); 515 | 516 | } 517 | 518 | function handleMouseWheel( event ) { 519 | 520 | //console.log( 'handleMouseWheel' ); 521 | 522 | var delta = 0; 523 | 524 | if ( event.wheelDelta !== undefined ) { 525 | 526 | // WebKit / Opera / Explorer 9 527 | 528 | delta = event.wheelDelta; 529 | 530 | } else if ( event.detail !== undefined ) { 531 | 532 | // Firefox 533 | 534 | delta = - event.detail; 535 | 536 | } 537 | 538 | if ( delta > 0 ) { 539 | 540 | dollyOut( getZoomScale() ); 541 | 542 | } else if ( delta < 0 ) { 543 | 544 | dollyIn( getZoomScale() ); 545 | 546 | } 547 | 548 | scope.update(); 549 | 550 | } 551 | 552 | function handleKeyDown( event ) { 553 | 554 | //console.log( 'handleKeyDown' ); 555 | 556 | switch ( event.keyCode ) { 557 | 558 | case scope.keys.UP: 559 | pan( 0, scope.keyPanSpeed ); 560 | scope.update(); 561 | break; 562 | 563 | case scope.keys.BOTTOM: 564 | pan( 0, - scope.keyPanSpeed ); 565 | scope.update(); 566 | break; 567 | 568 | case scope.keys.LEFT: 569 | pan( scope.keyPanSpeed, 0 ); 570 | scope.update(); 571 | break; 572 | 573 | case scope.keys.RIGHT: 574 | pan( - scope.keyPanSpeed, 0 ); 575 | scope.update(); 576 | break; 577 | 578 | } 579 | 580 | } 581 | 582 | function handleTouchStartRotate( event ) { 583 | 584 | //console.log( 'handleTouchStartRotate' ); 585 | 586 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 587 | 588 | } 589 | 590 | function handleTouchStartDolly( event ) { 591 | 592 | //console.log( 'handleTouchStartDolly' ); 593 | 594 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 595 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 596 | 597 | var distance = Math.sqrt( dx * dx + dy * dy ); 598 | 599 | dollyStart.set( 0, distance ); 600 | 601 | } 602 | 603 | function handleTouchStartPan( event ) { 604 | 605 | //console.log( 'handleTouchStartPan' ); 606 | 607 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 608 | 609 | } 610 | 611 | function handleTouchMoveRotate( event ) { 612 | 613 | //console.log( 'handleTouchMoveRotate' ); 614 | 615 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 616 | rotateDelta.subVectors( rotateEnd, rotateStart ); 617 | 618 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 619 | 620 | // rotating across whole screen goes 360 degrees around 621 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 622 | 623 | // rotating up and down along whole screen attempts to go 360, but limited to 180 624 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 625 | 626 | rotateStart.copy( rotateEnd ); 627 | 628 | scope.update(); 629 | 630 | } 631 | 632 | function handleTouchMoveDolly( event ) { 633 | 634 | //console.log( 'handleTouchMoveDolly' ); 635 | 636 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 637 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 638 | 639 | var distance = Math.sqrt( dx * dx + dy * dy ); 640 | 641 | dollyEnd.set( 0, distance ); 642 | 643 | dollyDelta.subVectors( dollyEnd, dollyStart ); 644 | 645 | if ( dollyDelta.y > 0 ) { 646 | 647 | dollyOut( getZoomScale() ); 648 | 649 | } else if ( dollyDelta.y < 0 ) { 650 | 651 | dollyIn( getZoomScale() ); 652 | 653 | } 654 | 655 | dollyStart.copy( dollyEnd ); 656 | 657 | scope.update(); 658 | 659 | } 660 | 661 | function handleTouchMovePan( event ) { 662 | 663 | //console.log( 'handleTouchMovePan' ); 664 | 665 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 666 | 667 | panDelta.subVectors( panEnd, panStart ); 668 | 669 | pan( panDelta.x, panDelta.y ); 670 | 671 | panStart.copy( panEnd ); 672 | 673 | scope.update(); 674 | 675 | } 676 | 677 | function handleTouchEnd( event ) { 678 | 679 | //console.log( 'handleTouchEnd' ); 680 | 681 | } 682 | 683 | // 684 | // event handlers - FSM: listen for events and reset state 685 | // 686 | 687 | function onMouseDown( event ) { 688 | 689 | if ( scope.enabled === false ) return; 690 | 691 | event.preventDefault(); 692 | 693 | if ( event.button === scope.mouseButtons.ORBIT ) { 694 | 695 | if ( scope.enableRotate === false ) return; 696 | 697 | handleMouseDownRotate( event ); 698 | 699 | state = STATE.ROTATE; 700 | 701 | } else if ( event.button === scope.mouseButtons.ZOOM ) { 702 | 703 | if ( scope.enableZoom === false ) return; 704 | 705 | handleMouseDownDolly( event ); 706 | 707 | state = STATE.DOLLY; 708 | 709 | } else if ( event.button === scope.mouseButtons.PAN ) { 710 | 711 | if ( scope.enablePan === false ) return; 712 | 713 | handleMouseDownPan( event ); 714 | 715 | state = STATE.PAN; 716 | 717 | } 718 | 719 | if ( state !== STATE.NONE ) { 720 | 721 | document.addEventListener( 'mousemove', onMouseMove, false ); 722 | document.addEventListener( 'mouseup', onMouseUp, false ); 723 | document.addEventListener( 'mouseout', onMouseUp, false ); 724 | 725 | scope.dispatchEvent( startEvent ); 726 | 727 | } 728 | 729 | } 730 | 731 | function onMouseMove( event ) { 732 | 733 | if ( scope.enabled === false ) return; 734 | 735 | event.preventDefault(); 736 | 737 | if ( state === STATE.ROTATE ) { 738 | 739 | if ( scope.enableRotate === false ) return; 740 | 741 | handleMouseMoveRotate( event ); 742 | 743 | } else if ( state === STATE.DOLLY ) { 744 | 745 | if ( scope.enableZoom === false ) return; 746 | 747 | handleMouseMoveDolly( event ); 748 | 749 | } else if ( state === STATE.PAN ) { 750 | 751 | if ( scope.enablePan === false ) return; 752 | 753 | handleMouseMovePan( event ); 754 | 755 | } 756 | 757 | } 758 | 759 | function onMouseUp( event ) { 760 | 761 | if ( scope.enabled === false ) return; 762 | 763 | handleMouseUp( event ); 764 | 765 | document.removeEventListener( 'mousemove', onMouseMove, false ); 766 | document.removeEventListener( 'mouseup', onMouseUp, false ); 767 | document.removeEventListener( 'mouseout', onMouseUp, false ); 768 | 769 | scope.dispatchEvent( endEvent ); 770 | 771 | state = STATE.NONE; 772 | 773 | } 774 | 775 | function onMouseWheel( event ) { 776 | 777 | if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return; 778 | 779 | event.preventDefault(); 780 | event.stopPropagation(); 781 | 782 | handleMouseWheel( event ); 783 | 784 | scope.dispatchEvent( startEvent ); // not sure why these are here... 785 | scope.dispatchEvent( endEvent ); 786 | 787 | } 788 | 789 | function onKeyDown( event ) { 790 | 791 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 792 | 793 | handleKeyDown( event ); 794 | 795 | } 796 | 797 | function onTouchStart( event ) { 798 | 799 | if ( scope.enabled === false ) return; 800 | 801 | switch ( event.touches.length ) { 802 | 803 | case 1: // one-fingered touch: rotate 804 | 805 | if ( scope.enableRotate === false ) return; 806 | 807 | handleTouchStartRotate( event ); 808 | 809 | state = STATE.TOUCH_ROTATE; 810 | 811 | break; 812 | 813 | case 2: // two-fingered touch: dolly 814 | 815 | if ( scope.enableZoom === false ) return; 816 | 817 | handleTouchStartDolly( event ); 818 | 819 | state = STATE.TOUCH_DOLLY; 820 | 821 | break; 822 | 823 | case 3: // three-fingered touch: pan 824 | 825 | if ( scope.enablePan === false ) return; 826 | 827 | handleTouchStartPan( event ); 828 | 829 | state = STATE.TOUCH_PAN; 830 | 831 | break; 832 | 833 | default: 834 | 835 | state = STATE.NONE; 836 | 837 | } 838 | 839 | if ( state !== STATE.NONE ) { 840 | 841 | scope.dispatchEvent( startEvent ); 842 | 843 | } 844 | 845 | } 846 | 847 | function onTouchMove( event ) { 848 | 849 | if ( scope.enabled === false ) return; 850 | 851 | event.preventDefault(); 852 | event.stopPropagation(); 853 | 854 | switch ( event.touches.length ) { 855 | 856 | case 1: // one-fingered touch: rotate 857 | 858 | if ( scope.enableRotate === false ) return; 859 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?... 860 | 861 | handleTouchMoveRotate( event ); 862 | 863 | break; 864 | 865 | case 2: // two-fingered touch: dolly 866 | 867 | if ( scope.enableZoom === false ) return; 868 | if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?... 869 | 870 | handleTouchMoveDolly( event ); 871 | 872 | break; 873 | 874 | case 3: // three-fingered touch: pan 875 | 876 | if ( scope.enablePan === false ) return; 877 | if ( state !== STATE.TOUCH_PAN ) return; // is this needed?... 878 | 879 | handleTouchMovePan( event ); 880 | 881 | break; 882 | 883 | default: 884 | 885 | state = STATE.NONE; 886 | 887 | } 888 | 889 | } 890 | 891 | function onTouchEnd( event ) { 892 | 893 | if ( scope.enabled === false ) return; 894 | 895 | handleTouchEnd( event ); 896 | 897 | scope.dispatchEvent( endEvent ); 898 | 899 | state = STATE.NONE; 900 | 901 | } 902 | 903 | function onContextMenu( event ) { 904 | 905 | event.preventDefault(); 906 | 907 | } 908 | 909 | // 910 | 911 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 912 | 913 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 914 | scope.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); 915 | scope.domElement.addEventListener( 'MozMousePixelScroll', onMouseWheel, false ); // firefox 916 | 917 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 918 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 919 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 920 | 921 | window.addEventListener( 'keydown', onKeyDown, false ); 922 | 923 | // force an update at start 924 | 925 | this.update(); 926 | 927 | }; 928 | 929 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 930 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 931 | 932 | Object.defineProperties( THREE.OrbitControls.prototype, { 933 | 934 | center: { 935 | 936 | get: function () { 937 | 938 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 939 | return this.target; 940 | 941 | } 942 | 943 | }, 944 | 945 | // backward compatibility 946 | 947 | noZoom: { 948 | 949 | get: function () { 950 | 951 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 952 | return ! this.enableZoom; 953 | 954 | }, 955 | 956 | set: function ( value ) { 957 | 958 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 959 | this.enableZoom = ! value; 960 | 961 | } 962 | 963 | }, 964 | 965 | noRotate: { 966 | 967 | get: function () { 968 | 969 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 970 | return ! this.enableRotate; 971 | 972 | }, 973 | 974 | set: function ( value ) { 975 | 976 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 977 | this.enableRotate = ! value; 978 | 979 | } 980 | 981 | }, 982 | 983 | noPan: { 984 | 985 | get: function () { 986 | 987 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 988 | return ! this.enablePan; 989 | 990 | }, 991 | 992 | set: function ( value ) { 993 | 994 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 995 | this.enablePan = ! value; 996 | 997 | } 998 | 999 | }, 1000 | 1001 | noKeys: { 1002 | 1003 | get: function () { 1004 | 1005 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1006 | return ! this.enableKeys; 1007 | 1008 | }, 1009 | 1010 | set: function ( value ) { 1011 | 1012 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1013 | this.enableKeys = ! value; 1014 | 1015 | } 1016 | 1017 | }, 1018 | 1019 | staticMoving : { 1020 | 1021 | get: function () { 1022 | 1023 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1024 | return ! this.constraint.enableDamping; 1025 | 1026 | }, 1027 | 1028 | set: function ( value ) { 1029 | 1030 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1031 | this.constraint.enableDamping = ! value; 1032 | 1033 | } 1034 | 1035 | }, 1036 | 1037 | dynamicDampingFactor : { 1038 | 1039 | get: function () { 1040 | 1041 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1042 | return this.constraint.dampingFactor; 1043 | 1044 | }, 1045 | 1046 | set: function ( value ) { 1047 | 1048 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1049 | this.constraint.dampingFactor = value; 1050 | 1051 | } 1052 | 1053 | } 1054 | 1055 | } ); 1056 | -------------------------------------------------------------------------------- /examples/vendor/three.js/examples/js/controls/OrbitControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | */ 8 | 9 | // This set of controls performs orbiting, dollying (zooming), and panning. 10 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 11 | // 12 | // Orbit - left mouse / touch: one finger move 13 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 14 | // Pan - right mouse, or arrow keys / touch: three finter swipe 15 | 16 | THREE.OrbitControls = function ( object, domElement ) { 17 | 18 | this.object = object; 19 | 20 | this.domElement = ( domElement !== undefined ) ? domElement : document; 21 | 22 | // Set to false to disable this control 23 | this.enabled = true; 24 | 25 | // "target" sets the location of focus, where the object orbits around 26 | this.target = new THREE.Vector3(); 27 | 28 | // How far you can dolly in and out ( PerspectiveCamera only ) 29 | this.minDistance = 0; 30 | this.maxDistance = Infinity; 31 | 32 | // How far you can zoom in and out ( OrthographicCamera only ) 33 | this.minZoom = 0; 34 | this.maxZoom = Infinity; 35 | 36 | // How far you can orbit vertically, upper and lower limits. 37 | // Range is 0 to Math.PI radians. 38 | this.minPolarAngle = 0; // radians 39 | this.maxPolarAngle = Math.PI; // radians 40 | 41 | // How far you can orbit horizontally, upper and lower limits. 42 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 43 | this.minAzimuthAngle = - Infinity; // radians 44 | this.maxAzimuthAngle = Infinity; // radians 45 | 46 | // Set to true to enable damping (inertia) 47 | // If damping is enabled, you must call controls.update() in your animation loop 48 | this.enableDamping = false; 49 | this.dampingFactor = 0.25; 50 | 51 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 52 | // Set to false to disable zooming 53 | this.enableZoom = true; 54 | this.zoomSpeed = 1.0; 55 | 56 | // Set to false to disable rotating 57 | this.enableRotate = true; 58 | this.rotateSpeed = 1.0; 59 | 60 | // Set to false to disable panning 61 | this.enablePan = true; 62 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 63 | 64 | // Set to true to automatically rotate around the target 65 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 66 | this.autoRotate = false; 67 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 68 | 69 | // Set to false to disable use of the keys 70 | this.enableKeys = true; 71 | 72 | // The four arrow keys 73 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 74 | 75 | // Mouse buttons 76 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 77 | 78 | // for reset 79 | this.target0 = this.target.clone(); 80 | this.position0 = this.object.position.clone(); 81 | this.zoom0 = this.object.zoom; 82 | 83 | // 84 | // public methods 85 | // 86 | 87 | this.getPolarAngle = function () { 88 | 89 | return phi; 90 | 91 | }; 92 | 93 | this.getAzimuthalAngle = function () { 94 | 95 | return theta; 96 | 97 | }; 98 | 99 | this.reset = function () { 100 | 101 | scope.target.copy( scope.target0 ); 102 | scope.object.position.copy( scope.position0 ); 103 | scope.object.zoom = scope.zoom0; 104 | 105 | scope.object.updateProjectionMatrix(); 106 | scope.dispatchEvent( changeEvent ); 107 | 108 | scope.update(); 109 | 110 | state = STATE.NONE; 111 | 112 | }; 113 | 114 | // this method is exposed, but perhaps it would be better if we can make it private... 115 | this.update = function() { 116 | 117 | var offset = new THREE.Vector3(); 118 | 119 | // so camera.up is the orbit axis 120 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 121 | var quatInverse = quat.clone().inverse(); 122 | 123 | var lastPosition = new THREE.Vector3(); 124 | var lastQuaternion = new THREE.Quaternion(); 125 | 126 | return function () { 127 | 128 | var position = scope.object.position; 129 | 130 | offset.copy( position ).sub( scope.target ); 131 | 132 | // rotate offset to "y-axis-is-up" space 133 | offset.applyQuaternion( quat ); 134 | 135 | // angle from z-axis around y-axis 136 | 137 | theta = Math.atan2( offset.x, offset.z ); 138 | 139 | // angle from y-axis 140 | 141 | phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); 142 | 143 | if ( scope.autoRotate && state === STATE.NONE ) { 144 | 145 | rotateLeft( getAutoRotationAngle() ); 146 | 147 | } 148 | 149 | theta += thetaDelta; 150 | phi += phiDelta; 151 | 152 | // restrict theta to be between desired limits 153 | theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, theta ) ); 154 | 155 | // restrict phi to be between desired limits 156 | phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, phi ) ); 157 | 158 | // restrict phi to be betwee EPS and PI-EPS 159 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); 160 | 161 | var radius = offset.length() * scale; 162 | 163 | // restrict radius to be between desired limits 164 | radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, radius ) ); 165 | 166 | // move target to panned location 167 | scope.target.add( panOffset ); 168 | 169 | offset.x = radius * Math.sin( phi ) * Math.sin( theta ); 170 | offset.y = radius * Math.cos( phi ); 171 | offset.z = radius * Math.sin( phi ) * Math.cos( theta ); 172 | 173 | // rotate offset back to "camera-up-vector-is-up" space 174 | offset.applyQuaternion( quatInverse ); 175 | 176 | position.copy( scope.target ).add( offset ); 177 | 178 | scope.object.lookAt( scope.target ); 179 | 180 | if ( scope.enableDamping === true ) { 181 | 182 | thetaDelta *= ( 1 - scope.dampingFactor ); 183 | phiDelta *= ( 1 - scope.dampingFactor ); 184 | 185 | } else { 186 | 187 | thetaDelta = 0; 188 | phiDelta = 0; 189 | 190 | } 191 | 192 | scale = 1; 193 | panOffset.set( 0, 0, 0 ); 194 | 195 | // update condition is: 196 | // min(camera displacement, camera rotation in radians)^2 > EPS 197 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 198 | 199 | if ( zoomChanged || 200 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 201 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 202 | 203 | scope.dispatchEvent( changeEvent ); 204 | 205 | lastPosition.copy( scope.object.position ); 206 | lastQuaternion.copy( scope.object.quaternion ); 207 | zoomChanged = false; 208 | 209 | return true; 210 | 211 | } 212 | 213 | return false; 214 | 215 | }; 216 | 217 | }(); 218 | 219 | this.dispose = function() { 220 | 221 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 222 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 223 | scope.domElement.removeEventListener( 'mousewheel', onMouseWheel, false ); 224 | scope.domElement.removeEventListener( 'MozMousePixelScroll', onMouseWheel, false ); // firefox 225 | 226 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 227 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 228 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 229 | 230 | document.removeEventListener( 'mousemove', onMouseMove, false ); 231 | document.removeEventListener( 'mouseup', onMouseUp, false ); 232 | document.removeEventListener( 'mouseout', onMouseUp, false ); 233 | 234 | window.removeEventListener( 'keydown', onKeyDown, false ); 235 | 236 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 237 | 238 | }; 239 | 240 | // 241 | // internals 242 | // 243 | 244 | var scope = this; 245 | 246 | var changeEvent = { type: 'change' }; 247 | var startEvent = { type: 'start' }; 248 | var endEvent = { type: 'end' }; 249 | 250 | var STATE = { NONE : - 1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 251 | 252 | var state = STATE.NONE; 253 | 254 | var EPS = 0.000001; 255 | 256 | // current position in spherical coordinates 257 | var theta; 258 | var phi; 259 | 260 | var phiDelta = 0; 261 | var thetaDelta = 0; 262 | var scale = 1; 263 | var panOffset = new THREE.Vector3(); 264 | var zoomChanged = false; 265 | 266 | var rotateStart = new THREE.Vector2(); 267 | var rotateEnd = new THREE.Vector2(); 268 | var rotateDelta = new THREE.Vector2(); 269 | 270 | var panStart = new THREE.Vector2(); 271 | var panEnd = new THREE.Vector2(); 272 | var panDelta = new THREE.Vector2(); 273 | 274 | var dollyStart = new THREE.Vector2(); 275 | var dollyEnd = new THREE.Vector2(); 276 | var dollyDelta = new THREE.Vector2(); 277 | 278 | function getAutoRotationAngle() { 279 | 280 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 281 | 282 | } 283 | 284 | function getZoomScale() { 285 | 286 | return Math.pow( 0.95, scope.zoomSpeed ); 287 | 288 | } 289 | 290 | function rotateLeft( angle ) { 291 | 292 | thetaDelta -= angle; 293 | 294 | } 295 | 296 | function rotateUp( angle ) { 297 | 298 | phiDelta -= angle; 299 | 300 | } 301 | 302 | var panLeft = function() { 303 | 304 | var v = new THREE.Vector3(); 305 | 306 | return function panLeft( distance, objectMatrix ) { 307 | 308 | var te = objectMatrix.elements; 309 | 310 | // get X column of objectMatrix 311 | v.set( te[ 0 ], te[ 1 ], te[ 2 ] ); 312 | 313 | v.multiplyScalar( - distance ); 314 | 315 | panOffset.add( v ); 316 | 317 | }; 318 | 319 | }(); 320 | 321 | var panUp = function() { 322 | 323 | var v = new THREE.Vector3(); 324 | 325 | return function panUp( distance, objectMatrix ) { 326 | 327 | var te = objectMatrix.elements; 328 | 329 | // get Y column of objectMatrix 330 | v.set( te[ 4 ], te[ 5 ], te[ 6 ] ); 331 | 332 | v.multiplyScalar( distance ); 333 | 334 | panOffset.add( v ); 335 | 336 | }; 337 | 338 | }(); 339 | 340 | // deltaX and deltaY are in pixels; right and down are positive 341 | var pan = function() { 342 | 343 | var offset = new THREE.Vector3(); 344 | 345 | return function( deltaX, deltaY ) { 346 | 347 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 348 | 349 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 350 | 351 | // perspective 352 | var position = scope.object.position; 353 | offset.copy( position ).sub( scope.target ); 354 | var targetDistance = offset.length(); 355 | 356 | // half of the fov is center to top of screen 357 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 358 | 359 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 360 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 361 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 362 | 363 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 364 | 365 | // orthographic 366 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / element.clientWidth, scope.object.matrix ); 367 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / element.clientHeight, scope.object.matrix ); 368 | 369 | } else { 370 | 371 | // camera neither orthographic nor perspective 372 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 373 | scope.enablePan = false; 374 | 375 | } 376 | 377 | }; 378 | 379 | }(); 380 | 381 | function dollyIn( dollyScale ) { 382 | 383 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 384 | 385 | scale /= dollyScale; 386 | 387 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 388 | 389 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 390 | scope.object.updateProjectionMatrix(); 391 | zoomChanged = true; 392 | 393 | } else { 394 | 395 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 396 | scope.enableZoom = false; 397 | 398 | } 399 | 400 | } 401 | 402 | function dollyOut( dollyScale ) { 403 | 404 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 405 | 406 | scale *= dollyScale; 407 | 408 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 409 | 410 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 411 | scope.object.updateProjectionMatrix(); 412 | zoomChanged = true; 413 | 414 | } else { 415 | 416 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 417 | scope.enableZoom = false; 418 | 419 | } 420 | 421 | } 422 | 423 | // 424 | // event callbacks - update the object state 425 | // 426 | 427 | function handleMouseDownRotate( event ) { 428 | 429 | //console.log( 'handleMouseDownRotate' ); 430 | 431 | rotateStart.set( event.clientX, event.clientY ); 432 | 433 | } 434 | 435 | function handleMouseDownDolly( event ) { 436 | 437 | //console.log( 'handleMouseDownDolly' ); 438 | 439 | dollyStart.set( event.clientX, event.clientY ); 440 | 441 | } 442 | 443 | function handleMouseDownPan( event ) { 444 | 445 | //console.log( 'handleMouseDownPan' ); 446 | 447 | panStart.set( event.clientX, event.clientY ); 448 | 449 | } 450 | 451 | function handleMouseMoveRotate( event ) { 452 | 453 | //console.log( 'handleMouseMoveRotate' ); 454 | 455 | rotateEnd.set( event.clientX, event.clientY ); 456 | rotateDelta.subVectors( rotateEnd, rotateStart ); 457 | 458 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 459 | 460 | // rotating across whole screen goes 360 degrees around 461 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 462 | 463 | // rotating up and down along whole screen attempts to go 360, but limited to 180 464 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 465 | 466 | rotateStart.copy( rotateEnd ); 467 | 468 | scope.update(); 469 | 470 | } 471 | 472 | function handleMouseMoveDolly( event ) { 473 | 474 | //console.log( 'handleMouseMoveDolly' ); 475 | 476 | dollyEnd.set( event.clientX, event.clientY ); 477 | 478 | dollyDelta.subVectors( dollyEnd, dollyStart ); 479 | 480 | if ( dollyDelta.y > 0 ) { 481 | 482 | dollyIn( getZoomScale() ); 483 | 484 | } else if ( dollyDelta.y < 0 ) { 485 | 486 | dollyOut( getZoomScale() ); 487 | 488 | } 489 | 490 | dollyStart.copy( dollyEnd ); 491 | 492 | scope.update(); 493 | 494 | } 495 | 496 | function handleMouseMovePan( event ) { 497 | 498 | //console.log( 'handleMouseMovePan' ); 499 | 500 | panEnd.set( event.clientX, event.clientY ); 501 | 502 | panDelta.subVectors( panEnd, panStart ); 503 | 504 | pan( panDelta.x, panDelta.y ); 505 | 506 | panStart.copy( panEnd ); 507 | 508 | scope.update(); 509 | 510 | } 511 | 512 | function handleMouseUp( event ) { 513 | 514 | //console.log( 'handleMouseUp' ); 515 | 516 | } 517 | 518 | function handleMouseWheel( event ) { 519 | 520 | //console.log( 'handleMouseWheel' ); 521 | 522 | var delta = 0; 523 | 524 | if ( event.wheelDelta !== undefined ) { 525 | 526 | // WebKit / Opera / Explorer 9 527 | 528 | delta = event.wheelDelta; 529 | 530 | } else if ( event.detail !== undefined ) { 531 | 532 | // Firefox 533 | 534 | delta = - event.detail; 535 | 536 | } 537 | 538 | if ( delta > 0 ) { 539 | 540 | dollyOut( getZoomScale() ); 541 | 542 | } else if ( delta < 0 ) { 543 | 544 | dollyIn( getZoomScale() ); 545 | 546 | } 547 | 548 | scope.update(); 549 | 550 | } 551 | 552 | function handleKeyDown( event ) { 553 | 554 | //console.log( 'handleKeyDown' ); 555 | 556 | switch ( event.keyCode ) { 557 | 558 | case scope.keys.UP: 559 | pan( 0, scope.keyPanSpeed ); 560 | scope.update(); 561 | break; 562 | 563 | case scope.keys.BOTTOM: 564 | pan( 0, - scope.keyPanSpeed ); 565 | scope.update(); 566 | break; 567 | 568 | case scope.keys.LEFT: 569 | pan( scope.keyPanSpeed, 0 ); 570 | scope.update(); 571 | break; 572 | 573 | case scope.keys.RIGHT: 574 | pan( - scope.keyPanSpeed, 0 ); 575 | scope.update(); 576 | break; 577 | 578 | } 579 | 580 | } 581 | 582 | function handleTouchStartRotate( event ) { 583 | 584 | //console.log( 'handleTouchStartRotate' ); 585 | 586 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 587 | 588 | } 589 | 590 | function handleTouchStartDolly( event ) { 591 | 592 | //console.log( 'handleTouchStartDolly' ); 593 | 594 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 595 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 596 | 597 | var distance = Math.sqrt( dx * dx + dy * dy ); 598 | 599 | dollyStart.set( 0, distance ); 600 | 601 | } 602 | 603 | function handleTouchStartPan( event ) { 604 | 605 | //console.log( 'handleTouchStartPan' ); 606 | 607 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 608 | 609 | } 610 | 611 | function handleTouchMoveRotate( event ) { 612 | 613 | //console.log( 'handleTouchMoveRotate' ); 614 | 615 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 616 | rotateDelta.subVectors( rotateEnd, rotateStart ); 617 | 618 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 619 | 620 | // rotating across whole screen goes 360 degrees around 621 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 622 | 623 | // rotating up and down along whole screen attempts to go 360, but limited to 180 624 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 625 | 626 | rotateStart.copy( rotateEnd ); 627 | 628 | scope.update(); 629 | 630 | } 631 | 632 | function handleTouchMoveDolly( event ) { 633 | 634 | //console.log( 'handleTouchMoveDolly' ); 635 | 636 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 637 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 638 | 639 | var distance = Math.sqrt( dx * dx + dy * dy ); 640 | 641 | dollyEnd.set( 0, distance ); 642 | 643 | dollyDelta.subVectors( dollyEnd, dollyStart ); 644 | 645 | if ( dollyDelta.y > 0 ) { 646 | 647 | dollyOut( getZoomScale() ); 648 | 649 | } else if ( dollyDelta.y < 0 ) { 650 | 651 | dollyIn( getZoomScale() ); 652 | 653 | } 654 | 655 | dollyStart.copy( dollyEnd ); 656 | 657 | scope.update(); 658 | 659 | } 660 | 661 | function handleTouchMovePan( event ) { 662 | 663 | //console.log( 'handleTouchMovePan' ); 664 | 665 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 666 | 667 | panDelta.subVectors( panEnd, panStart ); 668 | 669 | pan( panDelta.x, panDelta.y ); 670 | 671 | panStart.copy( panEnd ); 672 | 673 | scope.update(); 674 | 675 | } 676 | 677 | function handleTouchEnd( event ) { 678 | 679 | //console.log( 'handleTouchEnd' ); 680 | 681 | } 682 | 683 | // 684 | // event handlers - FSM: listen for events and reset state 685 | // 686 | 687 | function onMouseDown( event ) { 688 | 689 | if ( scope.enabled === false ) return; 690 | 691 | event.preventDefault(); 692 | 693 | if ( event.button === scope.mouseButtons.ORBIT ) { 694 | 695 | if ( scope.enableRotate === false ) return; 696 | 697 | handleMouseDownRotate( event ); 698 | 699 | state = STATE.ROTATE; 700 | 701 | } else if ( event.button === scope.mouseButtons.ZOOM ) { 702 | 703 | if ( scope.enableZoom === false ) return; 704 | 705 | handleMouseDownDolly( event ); 706 | 707 | state = STATE.DOLLY; 708 | 709 | } else if ( event.button === scope.mouseButtons.PAN ) { 710 | 711 | if ( scope.enablePan === false ) return; 712 | 713 | handleMouseDownPan( event ); 714 | 715 | state = STATE.PAN; 716 | 717 | } 718 | 719 | if ( state !== STATE.NONE ) { 720 | 721 | document.addEventListener( 'mousemove', onMouseMove, false ); 722 | document.addEventListener( 'mouseup', onMouseUp, false ); 723 | document.addEventListener( 'mouseout', onMouseUp, false ); 724 | 725 | scope.dispatchEvent( startEvent ); 726 | 727 | } 728 | 729 | } 730 | 731 | function onMouseMove( event ) { 732 | 733 | if ( scope.enabled === false ) return; 734 | 735 | event.preventDefault(); 736 | 737 | if ( state === STATE.ROTATE ) { 738 | 739 | if ( scope.enableRotate === false ) return; 740 | 741 | handleMouseMoveRotate( event ); 742 | 743 | } else if ( state === STATE.DOLLY ) { 744 | 745 | if ( scope.enableZoom === false ) return; 746 | 747 | handleMouseMoveDolly( event ); 748 | 749 | } else if ( state === STATE.PAN ) { 750 | 751 | if ( scope.enablePan === false ) return; 752 | 753 | handleMouseMovePan( event ); 754 | 755 | } 756 | 757 | } 758 | 759 | function onMouseUp( event ) { 760 | 761 | if ( scope.enabled === false ) return; 762 | 763 | handleMouseUp( event ); 764 | 765 | document.removeEventListener( 'mousemove', onMouseMove, false ); 766 | document.removeEventListener( 'mouseup', onMouseUp, false ); 767 | document.removeEventListener( 'mouseout', onMouseUp, false ); 768 | 769 | scope.dispatchEvent( endEvent ); 770 | 771 | state = STATE.NONE; 772 | 773 | } 774 | 775 | function onMouseWheel( event ) { 776 | 777 | if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return; 778 | 779 | event.preventDefault(); 780 | event.stopPropagation(); 781 | 782 | handleMouseWheel( event ); 783 | 784 | scope.dispatchEvent( startEvent ); // not sure why these are here... 785 | scope.dispatchEvent( endEvent ); 786 | 787 | } 788 | 789 | function onKeyDown( event ) { 790 | 791 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 792 | 793 | handleKeyDown( event ); 794 | 795 | } 796 | 797 | function onTouchStart( event ) { 798 | 799 | if ( scope.enabled === false ) return; 800 | 801 | switch ( event.touches.length ) { 802 | 803 | case 1: // one-fingered touch: rotate 804 | 805 | if ( scope.enableRotate === false ) return; 806 | 807 | handleTouchStartRotate( event ); 808 | 809 | state = STATE.TOUCH_ROTATE; 810 | 811 | break; 812 | 813 | case 2: // two-fingered touch: dolly 814 | 815 | if ( scope.enableZoom === false ) return; 816 | 817 | handleTouchStartDolly( event ); 818 | 819 | state = STATE.TOUCH_DOLLY; 820 | 821 | break; 822 | 823 | case 3: // three-fingered touch: pan 824 | 825 | if ( scope.enablePan === false ) return; 826 | 827 | handleTouchStartPan( event ); 828 | 829 | state = STATE.TOUCH_PAN; 830 | 831 | break; 832 | 833 | default: 834 | 835 | state = STATE.NONE; 836 | 837 | } 838 | 839 | if ( state !== STATE.NONE ) { 840 | 841 | scope.dispatchEvent( startEvent ); 842 | 843 | } 844 | 845 | } 846 | 847 | function onTouchMove( event ) { 848 | 849 | if ( scope.enabled === false ) return; 850 | 851 | event.preventDefault(); 852 | event.stopPropagation(); 853 | 854 | switch ( event.touches.length ) { 855 | 856 | case 1: // one-fingered touch: rotate 857 | 858 | if ( scope.enableRotate === false ) return; 859 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?... 860 | 861 | handleTouchMoveRotate( event ); 862 | 863 | break; 864 | 865 | case 2: // two-fingered touch: dolly 866 | 867 | if ( scope.enableZoom === false ) return; 868 | if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?... 869 | 870 | handleTouchMoveDolly( event ); 871 | 872 | break; 873 | 874 | case 3: // three-fingered touch: pan 875 | 876 | if ( scope.enablePan === false ) return; 877 | if ( state !== STATE.TOUCH_PAN ) return; // is this needed?... 878 | 879 | handleTouchMovePan( event ); 880 | 881 | break; 882 | 883 | default: 884 | 885 | state = STATE.NONE; 886 | 887 | } 888 | 889 | } 890 | 891 | function onTouchEnd( event ) { 892 | 893 | if ( scope.enabled === false ) return; 894 | 895 | handleTouchEnd( event ); 896 | 897 | scope.dispatchEvent( endEvent ); 898 | 899 | state = STATE.NONE; 900 | 901 | } 902 | 903 | function onContextMenu( event ) { 904 | 905 | event.preventDefault(); 906 | 907 | } 908 | 909 | // 910 | 911 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 912 | 913 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 914 | scope.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); 915 | scope.domElement.addEventListener( 'MozMousePixelScroll', onMouseWheel, false ); // firefox 916 | 917 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 918 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 919 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 920 | 921 | window.addEventListener( 'keydown', onKeyDown, false ); 922 | 923 | // force an update at start 924 | 925 | this.update(); 926 | 927 | }; 928 | 929 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 930 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 931 | 932 | Object.defineProperties( THREE.OrbitControls.prototype, { 933 | 934 | center: { 935 | 936 | get: function () { 937 | 938 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 939 | return this.target; 940 | 941 | } 942 | 943 | }, 944 | 945 | // backward compatibility 946 | 947 | noZoom: { 948 | 949 | get: function () { 950 | 951 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 952 | return ! this.enableZoom; 953 | 954 | }, 955 | 956 | set: function ( value ) { 957 | 958 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 959 | this.enableZoom = ! value; 960 | 961 | } 962 | 963 | }, 964 | 965 | noRotate: { 966 | 967 | get: function () { 968 | 969 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 970 | return ! this.enableRotate; 971 | 972 | }, 973 | 974 | set: function ( value ) { 975 | 976 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 977 | this.enableRotate = ! value; 978 | 979 | } 980 | 981 | }, 982 | 983 | noPan: { 984 | 985 | get: function () { 986 | 987 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 988 | return ! this.enablePan; 989 | 990 | }, 991 | 992 | set: function ( value ) { 993 | 994 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 995 | this.enablePan = ! value; 996 | 997 | } 998 | 999 | }, 1000 | 1001 | noKeys: { 1002 | 1003 | get: function () { 1004 | 1005 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1006 | return ! this.enableKeys; 1007 | 1008 | }, 1009 | 1010 | set: function ( value ) { 1011 | 1012 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1013 | this.enableKeys = ! value; 1014 | 1015 | } 1016 | 1017 | }, 1018 | 1019 | staticMoving : { 1020 | 1021 | get: function () { 1022 | 1023 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1024 | return ! this.constraint.enableDamping; 1025 | 1026 | }, 1027 | 1028 | set: function ( value ) { 1029 | 1030 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1031 | this.constraint.enableDamping = ! value; 1032 | 1033 | } 1034 | 1035 | }, 1036 | 1037 | dynamicDampingFactor : { 1038 | 1039 | get: function () { 1040 | 1041 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1042 | return this.constraint.dampingFactor; 1043 | 1044 | }, 1045 | 1046 | set: function ( value ) { 1047 | 1048 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1049 | this.constraint.dampingFactor = value; 1050 | 1051 | } 1052 | 1053 | } 1054 | 1055 | } ); 1056 | -------------------------------------------------------------------------------- /examples/vendor/three.js/examples/js/libs/dat.gui.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dat-gui JavaScript Controller Library 3 | * http://code.google.com/p/dat-gui 4 | * 5 | * Copyright 2011 Data Arts Team, Google Creative Lab 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | */ 13 | var dat=dat||{};dat.gui=dat.gui||{};dat.utils=dat.utils||{};dat.controllers=dat.controllers||{};dat.dom=dat.dom||{};dat.color=dat.color||{};dat.utils.css=function(){return{load:function(e,a){var a=a||document,c=a.createElement("link");c.type="text/css";c.rel="stylesheet";c.href=e;a.getElementsByTagName("head")[0].appendChild(c)},inject:function(e,a){var a=a||document,c=document.createElement("style");c.type="text/css";c.innerHTML=e;a.getElementsByTagName("head")[0].appendChild(c)}}}(); 14 | dat.utils.common=function(){var e=Array.prototype.forEach,a=Array.prototype.slice;return{BREAK:{},extend:function(c){this.each(a.call(arguments,1),function(a){for(var f in a)this.isUndefined(a[f])||(c[f]=a[f])},this);return c},defaults:function(c){this.each(a.call(arguments,1),function(a){for(var f in a)this.isUndefined(c[f])&&(c[f]=a[f])},this);return c},compose:function(){var c=a.call(arguments);return function(){for(var d=a.call(arguments),f=c.length-1;f>=0;f--)d=[c[f].apply(this,d)];return d[0]}}, 15 | each:function(a,d,f){if(e&&a.forEach===e)a.forEach(d,f);else if(a.length===a.length+0)for(var b=0,n=a.length;b-1?d.length-d.indexOf(".")-1:0};c.superclass=e;a.extend(c.prototype,e.prototype,{setValue:function(a){if(this.__min!==void 0&&athis.__max)a=this.__max;this.__step!==void 0&&a%this.__step!=0&&(a=Math.round(a/this.__step)*this.__step);return c.superclass.prototype.setValue.call(this,a)},min:function(a){this.__min=a;return this},max:function(a){this.__max=a;return this},step:function(a){this.__step=a;return this}});return c}(dat.controllers.Controller,dat.utils.common); 29 | dat.controllers.NumberControllerBox=function(e,a,c){var d=function(f,b,e){function h(){var a=parseFloat(l.__input.value);c.isNaN(a)||l.setValue(a)}function j(a){var b=o-a.clientY;l.setValue(l.getValue()+b*l.__impliedStep);o=a.clientY}function m(){a.unbind(window,"mousemove",j);a.unbind(window,"mouseup",m)}this.__truncationSuspended=false;d.superclass.call(this,f,b,e);var l=this,o;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"change",h); 30 | a.bind(this.__input,"blur",function(){h();l.__onFinishChange&&l.__onFinishChange.call(l,l.getValue())});a.bind(this.__input,"mousedown",function(b){a.bind(window,"mousemove",j);a.bind(window,"mouseup",m);o=b.clientY});a.bind(this.__input,"keydown",function(a){if(a.keyCode===13)l.__truncationSuspended=true,this.blur(),l.__truncationSuspended=false});this.updateDisplay();this.domElement.appendChild(this.__input)};d.superclass=e;c.extend(d.prototype,e.prototype,{updateDisplay:function(){var a=this.__input, 31 | b;if(this.__truncationSuspended)b=this.getValue();else{b=this.getValue();var c=Math.pow(10,this.__precision);b=Math.round(b*c)/c}a.value=b;return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.NumberController,dat.dom.dom,dat.utils.common); 32 | dat.controllers.NumberControllerSlider=function(e,a,c,d,f){var b=function(d,c,f,e,l){function o(b){b.preventDefault();var d=a.getOffset(g.__background),c=a.getWidth(g.__background);g.setValue(g.__min+(g.__max-g.__min)*((b.clientX-d.left)/(d.left+c-d.left)));return false}function y(){a.unbind(window,"mousemove",o);a.unbind(window,"mouseup",y);g.__onFinishChange&&g.__onFinishChange.call(g,g.getValue())}b.superclass.call(this,d,c,{min:f,max:e,step:l});var g=this;this.__background=document.createElement("div"); 33 | this.__foreground=document.createElement("div");a.bind(this.__background,"mousedown",function(b){a.bind(window,"mousemove",o);a.bind(window,"mouseup",y);o(b)});a.addClass(this.__background,"slider");a.addClass(this.__foreground,"slider-fg");this.updateDisplay();this.__background.appendChild(this.__foreground);this.domElement.appendChild(this.__background)};b.superclass=e;b.useDefaultStyles=function(){c.inject(f)};d.extend(b.prototype,e.prototype,{updateDisplay:function(){this.__foreground.style.width= 34 | (this.getValue()-this.__min)/(this.__max-this.__min)*100+"%";return b.superclass.prototype.updateDisplay.call(this)}});return b}(dat.controllers.NumberController,dat.dom.dom,dat.utils.css,dat.utils.common,".slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}"); 35 | dat.controllers.FunctionController=function(e,a,c){var d=function(c,b,e){d.superclass.call(this,c,b);var h=this;this.__button=document.createElement("div");this.__button.innerHTML=e===void 0?"Fire":e;a.bind(this.__button,"click",function(a){a.preventDefault();h.fire();return false});a.addClass(this.__button,"button");this.domElement.appendChild(this.__button)};d.superclass=e;c.extend(d.prototype,e.prototype,{fire:function(){this.__onChange&&this.__onChange.call(this);this.__onFinishChange&&this.__onFinishChange.call(this, 36 | this.getValue());this.getValue().call(this.object)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); 37 | dat.controllers.BooleanController=function(e,a,c){var d=function(c,b){d.superclass.call(this,c,b);var e=this;this.__prev=this.getValue();this.__checkbox=document.createElement("input");this.__checkbox.setAttribute("type","checkbox");a.bind(this.__checkbox,"change",function(){e.setValue(!e.__prev)},false);this.domElement.appendChild(this.__checkbox);this.updateDisplay()};d.superclass=e;c.extend(d.prototype,e.prototype,{setValue:function(a){a=d.superclass.prototype.setValue.call(this,a);this.__onFinishChange&& 38 | this.__onFinishChange.call(this,this.getValue());this.__prev=this.getValue();return a},updateDisplay:function(){this.getValue()===true?(this.__checkbox.setAttribute("checked","checked"),this.__checkbox.checked=true):this.__checkbox.checked=false;return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); 39 | dat.color.toString=function(e){return function(a){if(a.a==1||e.isUndefined(a.a)){for(a=a.hex.toString(16);a.length<6;)a="0"+a;return"#"+a}else return"rgba("+Math.round(a.r)+","+Math.round(a.g)+","+Math.round(a.b)+","+a.a+")"}}(dat.utils.common); 40 | dat.color.interpret=function(e,a){var c,d,f=[{litmus:a.isString,conversions:{THREE_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return a===null?false:{space:"HEX",hex:parseInt("0x"+a[1].toString()+a[1].toString()+a[2].toString()+a[2].toString()+a[3].toString()+a[3].toString())}},write:e},SIX_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9]{6})$/i);return a===null?false:{space:"HEX",hex:parseInt("0x"+a[1].toString())}},write:e},CSS_RGB:{read:function(a){a=a.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); 41 | return a===null?false:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3])}},write:e},CSS_RGBA:{read:function(a){a=a.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);return a===null?false:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3]),a:parseFloat(a[4])}},write:e}}},{litmus:a.isNumber,conversions:{HEX:{read:function(a){return{space:"HEX",hex:a,conversionName:"HEX"}},write:function(a){return a.hex}}}},{litmus:a.isArray,conversions:{RGB_ARRAY:{read:function(a){return a.length!= 42 | 3?false:{space:"RGB",r:a[0],g:a[1],b:a[2]}},write:function(a){return[a.r,a.g,a.b]}},RGBA_ARRAY:{read:function(a){return a.length!=4?false:{space:"RGB",r:a[0],g:a[1],b:a[2],a:a[3]}},write:function(a){return[a.r,a.g,a.b,a.a]}}}},{litmus:a.isObject,conversions:{RGBA_OBJ:{read:function(b){return a.isNumber(b.r)&&a.isNumber(b.g)&&a.isNumber(b.b)&&a.isNumber(b.a)?{space:"RGB",r:b.r,g:b.g,b:b.b,a:b.a}:false},write:function(a){return{r:a.r,g:a.g,b:a.b,a:a.a}}},RGB_OBJ:{read:function(b){return a.isNumber(b.r)&& 43 | a.isNumber(b.g)&&a.isNumber(b.b)?{space:"RGB",r:b.r,g:b.g,b:b.b}:false},write:function(a){return{r:a.r,g:a.g,b:a.b}}},HSVA_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)&&a.isNumber(b.a)?{space:"HSV",h:b.h,s:b.s,v:b.v,a:b.a}:false},write:function(a){return{h:a.h,s:a.s,v:a.v,a:a.a}}},HSV_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)?{space:"HSV",h:b.h,s:b.s,v:b.v}:false},write:function(a){return{h:a.h,s:a.s,v:a.v}}}}}];return function(){d= 44 | false;var b=arguments.length>1?a.toArray(arguments):arguments[0];a.each(f,function(e){if(e.litmus(b))return a.each(e.conversions,function(e,f){c=e.read(b);if(d===false&&c!==false)return d=c,c.conversionName=f,c.conversion=e,a.BREAK}),a.BREAK});return d}}(dat.color.toString,dat.utils.common); 45 | dat.GUI=dat.gui.GUI=function(e,a,c,d,f,b,n,h,j,m,l,o,y,g,i){function q(a,b,r,c){if(b[r]===void 0)throw Error("Object "+b+' has no property "'+r+'"');c.color?b=new l(b,r):(b=[b,r].concat(c.factoryArgs),b=d.apply(a,b));if(c.before instanceof f)c.before=c.before.__li;t(a,b);g.addClass(b.domElement,"c");r=document.createElement("span");g.addClass(r,"property-name");r.innerHTML=b.property;var e=document.createElement("div");e.appendChild(r);e.appendChild(b.domElement);c=s(a,e,c.before);g.addClass(c,k.CLASS_CONTROLLER_ROW); 46 | g.addClass(c,typeof b.getValue());p(a,c,b);a.__controllers.push(b);return b}function s(a,b,d){var c=document.createElement("li");b&&c.appendChild(b);d?a.__ul.insertBefore(c,params.before):a.__ul.appendChild(c);a.onResize();return c}function p(a,d,c){c.__li=d;c.__gui=a;i.extend(c,{options:function(b){if(arguments.length>1)return c.remove(),q(a,c.object,c.property,{before:c.__li.nextElementSibling,factoryArgs:[i.toArray(arguments)]});if(i.isArray(b)||i.isObject(b))return c.remove(),q(a,c.object,c.property, 47 | {before:c.__li.nextElementSibling,factoryArgs:[b]})},name:function(a){c.__li.firstElementChild.firstElementChild.innerHTML=a;return c},listen:function(){c.__gui.listen(c);return c},remove:function(){c.__gui.remove(c);return c}});if(c instanceof j){var e=new h(c.object,c.property,{min:c.__min,max:c.__max,step:c.__step});i.each(["updateDisplay","onChange","onFinishChange"],function(a){var b=c[a],H=e[a];c[a]=e[a]=function(){var a=Array.prototype.slice.call(arguments);b.apply(c,a);return H.apply(e,a)}}); 48 | g.addClass(d,"has-slider");c.domElement.insertBefore(e.domElement,c.domElement.firstElementChild)}else if(c instanceof h){var f=function(b){return i.isNumber(c.__min)&&i.isNumber(c.__max)?(c.remove(),q(a,c.object,c.property,{before:c.__li.nextElementSibling,factoryArgs:[c.__min,c.__max,c.__step]})):b};c.min=i.compose(f,c.min);c.max=i.compose(f,c.max)}else if(c instanceof b)g.bind(d,"click",function(){g.fakeEvent(c.__checkbox,"click")}),g.bind(c.__checkbox,"click",function(a){a.stopPropagation()}); 49 | else if(c instanceof n)g.bind(d,"click",function(){g.fakeEvent(c.__button,"click")}),g.bind(d,"mouseover",function(){g.addClass(c.__button,"hover")}),g.bind(d,"mouseout",function(){g.removeClass(c.__button,"hover")});else if(c instanceof l)g.addClass(d,"color"),c.updateDisplay=i.compose(function(a){d.style.borderLeftColor=c.__color.toString();return a},c.updateDisplay),c.updateDisplay();c.setValue=i.compose(function(b){a.getRoot().__preset_select&&c.isModified()&&B(a.getRoot(),true);return b},c.setValue)} 50 | function t(a,b){var c=a.getRoot(),d=c.__rememberedObjects.indexOf(b.object);if(d!=-1){var e=c.__rememberedObjectIndecesToControllers[d];e===void 0&&(e={},c.__rememberedObjectIndecesToControllers[d]=e);e[b.property]=b;if(c.load&&c.load.remembered){c=c.load.remembered;if(c[a.preset])c=c[a.preset];else if(c[w])c=c[w];else return;if(c[d]&&c[d][b.property]!==void 0)d=c[d][b.property],b.initialValue=d,b.setValue(d)}}}function I(a){var b=a.__save_row=document.createElement("li");g.addClass(a.domElement, 51 | "has-save");a.__ul.insertBefore(b,a.__ul.firstChild);g.addClass(b,"save-row");var c=document.createElement("span");c.innerHTML=" ";g.addClass(c,"button gears");var d=document.createElement("span");d.innerHTML="Save";g.addClass(d,"button");g.addClass(d,"save");var e=document.createElement("span");e.innerHTML="New";g.addClass(e,"button");g.addClass(e,"save-as");var f=document.createElement("span");f.innerHTML="Revert";g.addClass(f,"button");g.addClass(f,"revert");var m=a.__preset_select=document.createElement("select"); 52 | a.load&&a.load.remembered?i.each(a.load.remembered,function(b,c){C(a,c,c==a.preset)}):C(a,w,false);g.bind(m,"change",function(){for(var b=0;b0){a.preset=this.preset;if(!a.remembered)a.remembered={};a.remembered[this.preset]=z(this)}a.folders={};i.each(this.__folders,function(b, 69 | c){a.folders[c]=b.getSaveObject()});return a},save:function(){if(!this.load.remembered)this.load.remembered={};this.load.remembered[this.preset]=z(this);B(this,false)},saveAs:function(a){if(!this.load.remembered)this.load.remembered={},this.load.remembered[w]=z(this,true);this.load.remembered[a]=z(this);this.preset=a;C(this,a,true)},revert:function(a){i.each(this.__controllers,function(b){this.getRoot().load.remembered?t(a||this.getRoot(),b):b.setValue(b.initialValue)},this);i.each(this.__folders, 70 | function(a){a.revert(a)});a||B(this.getRoot(),false)},listen:function(a){var b=this.__listening.length==0;this.__listening.push(a);b&&E(this.__listening)}});return k}(dat.utils.css,'
\n\n Here\'s the new load parameter for your GUI\'s constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI\'s constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n \n
\n \n
\n\n
', 71 | ".dg ul{list-style:none;margin:0;padding:0;width:100%;clear:both}.dg.ac{position:fixed;top:0;left:0;right:0;height:0;z-index:0}.dg:not(.ac) .main{overflow:hidden}.dg.main{-webkit-transition:opacity 0.1s linear;-o-transition:opacity 0.1s linear;-moz-transition:opacity 0.1s linear;transition:opacity 0.1s linear}.dg.main.taller-than-window{overflow-y:auto}.dg.main.taller-than-window .close-button{opacity:1;margin-top:-1px;border-top:1px solid #2c2c2c}.dg.main ul.closed .close-button{opacity:1 !important}.dg.main:hover .close-button,.dg.main .close-button.drag{opacity:1}.dg.main .close-button{-webkit-transition:opacity 0.1s linear;-o-transition:opacity 0.1s linear;-moz-transition:opacity 0.1s linear;transition:opacity 0.1s linear;border:0;position:absolute;line-height:19px;height:20px;cursor:pointer;text-align:center;background-color:#000}.dg.main .close-button:hover{background-color:#111}.dg.a{float:right;margin-right:15px;overflow-x:hidden}.dg.a.has-save ul{margin-top:27px}.dg.a.has-save ul.closed{margin-top:0}.dg.a .save-row{position:fixed;top:0;z-index:1002}.dg li{-webkit-transition:height 0.1s ease-out;-o-transition:height 0.1s ease-out;-moz-transition:height 0.1s ease-out;transition:height 0.1s ease-out}.dg li:not(.folder){cursor:auto;height:27px;line-height:27px;overflow:hidden;padding:0 4px 0 5px}.dg li.folder{padding:0;border-left:4px solid rgba(0,0,0,0)}.dg li.title{cursor:pointer;margin-left:-4px}.dg .closed li:not(.title),.dg .closed ul li,.dg .closed ul li > *{height:0;overflow:hidden;border:0}.dg .cr{clear:both;padding-left:3px;height:27px}.dg .property-name{cursor:default;float:left;clear:left;width:40%;overflow:hidden;text-overflow:ellipsis}.dg .c{float:left;width:60%}.dg .c input[type=text]{border:0;margin-top:4px;padding:3px;width:100%;float:right}.dg .has-slider input[type=text]{width:30%;margin-left:0}.dg .slider{float:left;width:66%;margin-left:-5px;margin-right:0;height:19px;margin-top:4px}.dg .slider-fg{height:100%}.dg .c input[type=checkbox]{margin-top:9px}.dg .c select{margin-top:5px}.dg .cr.function,.dg .cr.function .property-name,.dg .cr.function *,.dg .cr.boolean,.dg .cr.boolean *{cursor:pointer}.dg .selector{display:none;position:absolute;margin-left:-9px;margin-top:23px;z-index:10}.dg .c:hover .selector,.dg .selector.drag{display:block}.dg li.save-row{padding:0}.dg li.save-row .button{display:inline-block;padding:0px 6px}.dg.dialogue{background-color:#222;width:460px;padding:15px;font-size:13px;line-height:15px}#dg-new-constructor{padding:10px;color:#222;font-family:Monaco, monospace;font-size:10px;border:0;resize:none;box-shadow:inset 1px 1px 1px #888;word-wrap:break-word;margin:12px 0;display:block;width:440px;overflow-y:scroll;height:100px;position:relative}#dg-local-explain{display:none;font-size:11px;line-height:17px;border-radius:3px;background-color:#333;padding:8px;margin-top:10px}#dg-local-explain code{font-size:10px}#dat-gui-save-locally{display:none}.dg{color:#eee;font:11px 'Lucida Grande', sans-serif;text-shadow:0 -1px 0 #111}.dg.main::-webkit-scrollbar{width:5px;background:#1a1a1a}.dg.main::-webkit-scrollbar-corner{height:0;display:none}.dg.main::-webkit-scrollbar-thumb{border-radius:5px;background:#676767}.dg li:not(.folder){background:#1a1a1a;border-bottom:1px solid #2c2c2c}.dg li.save-row{line-height:25px;background:#dad5cb;border:0}.dg li.save-row select{margin-left:5px;width:108px}.dg li.save-row .button{margin-left:5px;margin-top:1px;border-radius:2px;font-size:9px;line-height:7px;padding:4px 4px 5px 4px;background:#c5bdad;color:#fff;text-shadow:0 1px 0 #b0a58f;box-shadow:0 -1px 0 #b0a58f;cursor:pointer}.dg li.save-row .button.gears{background:#c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;height:7px;width:8px}.dg li.save-row .button:hover{background-color:#bab19e;box-shadow:0 -1px 0 #b0a58f}.dg li.folder{border-bottom:0}.dg li.title{padding-left:16px;background:#000 url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;cursor:pointer;border-bottom:1px solid rgba(255,255,255,0.2)}.dg .closed li.title{background-image:url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==)}.dg .cr.boolean{border-left:3px solid #806787}.dg .cr.function{border-left:3px solid #e61d5f}.dg .cr.number{border-left:3px solid #2fa1d6}.dg .cr.number input[type=text]{color:#2fa1d6}.dg .cr.string{border-left:3px solid #1ed36f}.dg .cr.string input[type=text]{color:#1ed36f}.dg .cr.function:hover,.dg .cr.boolean:hover{background:#111}.dg .c input[type=text]{background:#303030;outline:none}.dg .c input[type=text]:hover{background:#3c3c3c}.dg .c input[type=text]:focus{background:#494949;color:#fff}.dg .c .slider{background:#303030;cursor:ew-resize}.dg .c .slider-fg{background:#2fa1d6}.dg .c .slider:hover{background:#3c3c3c}.dg .c .slider:hover .slider-fg{background:#44abda}\n", 72 | dat.controllers.factory=function(e,a,c,d,f,b,n){return function(h,j,m,l){var o=h[j];if(n.isArray(m)||n.isObject(m))return new e(h,j,m);if(n.isNumber(o))return n.isNumber(m)&&n.isNumber(l)?new c(h,j,m,l):new a(h,j,{min:m,max:l});if(n.isString(o))return new d(h,j);if(n.isFunction(o))return new f(h,j,"");if(n.isBoolean(o))return new b(h,j)}}(dat.controllers.OptionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.StringController=function(e,a,c){var d= 73 | function(c,b){function e(){h.setValue(h.__input.value)}d.superclass.call(this,c,b);var h=this;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"keyup",e);a.bind(this.__input,"change",e);a.bind(this.__input,"blur",function(){h.__onFinishChange&&h.__onFinishChange.call(h,h.getValue())});a.bind(this.__input,"keydown",function(a){a.keyCode===13&&this.blur()});this.updateDisplay();this.domElement.appendChild(this.__input)};d.superclass=e;c.extend(d.prototype, 74 | e.prototype,{updateDisplay:function(){if(!a.isActive(this.__input))this.__input.value=this.getValue();return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common),dat.controllers.FunctionController,dat.controllers.BooleanController,dat.utils.common),dat.controllers.Controller,dat.controllers.BooleanController,dat.controllers.FunctionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.OptionController, 75 | dat.controllers.ColorController=function(e,a,c,d,f){function b(a,b,c,d){a.style.background="";f.each(j,function(e){a.style.cssText+="background: "+e+"linear-gradient("+b+", "+c+" 0%, "+d+" 100%); "})}function n(a){a.style.background="";a.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);";a.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"; 76 | a.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}var h=function(e,l){function o(b){q(b);a.bind(window,"mousemove",q);a.bind(window, 77 | "mouseup",j)}function j(){a.unbind(window,"mousemove",q);a.unbind(window,"mouseup",j)}function g(){var a=d(this.value);a!==false?(p.__color.__state=a,p.setValue(p.__color.toOriginal())):this.value=p.__color.toString()}function i(){a.unbind(window,"mousemove",s);a.unbind(window,"mouseup",i)}function q(b){b.preventDefault();var c=a.getWidth(p.__saturation_field),d=a.getOffset(p.__saturation_field),e=(b.clientX-d.left+document.body.scrollLeft)/c,b=1-(b.clientY-d.top+document.body.scrollTop)/c;b>1?b= 78 | 1:b<0&&(b=0);e>1?e=1:e<0&&(e=0);p.__color.v=b;p.__color.s=e;p.setValue(p.__color.toOriginal());return false}function s(b){b.preventDefault();var c=a.getHeight(p.__hue_field),d=a.getOffset(p.__hue_field),b=1-(b.clientY-d.top+document.body.scrollTop)/c;b>1?b=1:b<0&&(b=0);p.__color.h=b*360;p.setValue(p.__color.toOriginal());return false}h.superclass.call(this,e,l);this.__color=new c(this.getValue());this.__temp=new c(0);var p=this;this.domElement=document.createElement("div");a.makeSelectable(this.domElement, 79 | false);this.__selector=document.createElement("div");this.__selector.className="selector";this.__saturation_field=document.createElement("div");this.__saturation_field.className="saturation-field";this.__field_knob=document.createElement("div");this.__field_knob.className="field-knob";this.__field_knob_border="2px solid ";this.__hue_knob=document.createElement("div");this.__hue_knob.className="hue-knob";this.__hue_field=document.createElement("div");this.__hue_field.className="hue-field";this.__input= 80 | document.createElement("input");this.__input.type="text";this.__input_textShadow="0 1px 1px ";a.bind(this.__input,"keydown",function(a){a.keyCode===13&&g.call(this)});a.bind(this.__input,"blur",g);a.bind(this.__selector,"mousedown",function(){a.addClass(this,"drag").bind(window,"mouseup",function(){a.removeClass(p.__selector,"drag")})});var t=document.createElement("div");f.extend(this.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"}); 81 | f.extend(this.__field_knob.style,{position:"absolute",width:"12px",height:"12px",border:this.__field_knob_border+(this.__color.v<0.5?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1});f.extend(this.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1});f.extend(this.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"});f.extend(t.style, 82 | {width:"100%",height:"100%",background:"none"});b(t,"top","rgba(0,0,0,0)","#000");f.extend(this.__hue_field.style,{width:"15px",height:"100px",display:"inline-block",border:"1px solid #555",cursor:"ns-resize"});n(this.__hue_field);f.extend(this.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:this.__input_textShadow+"rgba(0,0,0,0.7)"});a.bind(this.__saturation_field,"mousedown",o);a.bind(this.__field_knob,"mousedown",o);a.bind(this.__hue_field,"mousedown", 83 | function(b){s(b);a.bind(window,"mousemove",s);a.bind(window,"mouseup",i)});this.__saturation_field.appendChild(t);this.__selector.appendChild(this.__field_knob);this.__selector.appendChild(this.__saturation_field);this.__selector.appendChild(this.__hue_field);this.__hue_field.appendChild(this.__hue_knob);this.domElement.appendChild(this.__input);this.domElement.appendChild(this.__selector);this.updateDisplay()};h.superclass=e;f.extend(h.prototype,e.prototype,{updateDisplay:function(){var a=d(this.getValue()); 84 | if(a!==false){var e=false;f.each(c.COMPONENTS,function(b){if(!f.isUndefined(a[b])&&!f.isUndefined(this.__color.__state[b])&&a[b]!==this.__color.__state[b])return e=true,{}},this);e&&f.extend(this.__color.__state,a)}f.extend(this.__temp.__state,this.__color.__state);this.__temp.a=1;var h=this.__color.v<0.5||this.__color.s>0.5?255:0,j=255-h;f.extend(this.__field_knob.style,{marginLeft:100*this.__color.s-7+"px",marginTop:100*(1-this.__color.v)-7+"px",backgroundColor:this.__temp.toString(),border:this.__field_knob_border+ 85 | "rgb("+h+","+h+","+h+")"});this.__hue_knob.style.marginTop=(1-this.__color.h/360)*100+"px";this.__temp.s=1;this.__temp.v=1;b(this.__saturation_field,"left","#fff",this.__temp.toString());f.extend(this.__input.style,{backgroundColor:this.__input.value=this.__color.toString(),color:"rgb("+h+","+h+","+h+")",textShadow:this.__input_textShadow+"rgba("+j+","+j+","+j+",.7)"})}});var j=["-moz-","-o-","-webkit-","-ms-",""];return h}(dat.controllers.Controller,dat.dom.dom,dat.color.Color=function(e,a,c,d){function f(a, 86 | b,c){Object.defineProperty(a,b,{get:function(){if(this.__state.space==="RGB")return this.__state[b];n(this,b,c);return this.__state[b]},set:function(a){if(this.__state.space!=="RGB")n(this,b,c),this.__state.space="RGB";this.__state[b]=a}})}function b(a,b){Object.defineProperty(a,b,{get:function(){if(this.__state.space==="HSV")return this.__state[b];h(this);return this.__state[b]},set:function(a){if(this.__state.space!=="HSV")h(this),this.__state.space="HSV";this.__state[b]=a}})}function n(b,c,e){if(b.__state.space=== 87 | "HEX")b.__state[c]=a.component_from_hex(b.__state.hex,e);else if(b.__state.space==="HSV")d.extend(b.__state,a.hsv_to_rgb(b.__state.h,b.__state.s,b.__state.v));else throw"Corrupted color state";}function h(b){var c=a.rgb_to_hsv(b.r,b.g,b.b);d.extend(b.__state,{s:c.s,v:c.v});if(d.isNaN(c.h)){if(d.isUndefined(b.__state.h))b.__state.h=0}else b.__state.h=c.h}var j=function(){this.__state=e.apply(this,arguments);if(this.__state===false)throw"Failed to interpret color arguments";this.__state.a=this.__state.a|| 88 | 1};j.COMPONENTS="r,g,b,h,s,v,hex,a".split(",");d.extend(j.prototype,{toString:function(){return c(this)},toOriginal:function(){return this.__state.conversion.write(this)}});f(j.prototype,"r",2);f(j.prototype,"g",1);f(j.prototype,"b",0);b(j.prototype,"h");b(j.prototype,"s");b(j.prototype,"v");Object.defineProperty(j.prototype,"a",{get:function(){return this.__state.a},set:function(a){this.__state.a=a}});Object.defineProperty(j.prototype,"hex",{get:function(){if(!this.__state.space!=="HEX")this.__state.hex= 89 | a.rgb_to_hex(this.r,this.g,this.b);return this.__state.hex},set:function(a){this.__state.space="HEX";this.__state.hex=a}});return j}(dat.color.interpret,dat.color.math=function(){var e;return{hsv_to_rgb:function(a,c,d){var e=a/60-Math.floor(a/60),b=d*(1-c),n=d*(1-e*c),c=d*(1-(1-e)*c),a=[[d,c,b],[n,d,b],[b,d,c],[b,n,d],[c,b,d],[d,b,n]][Math.floor(a/60)%6];return{r:a[0]*255,g:a[1]*255,b:a[2]*255}},rgb_to_hsv:function(a,c,d){var e=Math.min(a,c,d),b=Math.max(a,c,d),e=b-e;if(b==0)return{h:NaN,s:0,v:0}; 90 | a=a==b?(c-d)/e:c==b?2+(d-a)/e:4+(a-c)/e;a/=6;a<0&&(a+=1);return{h:a*360,s:e/b,v:b/255}},rgb_to_hex:function(a,c,d){a=this.hex_with_component(0,2,a);a=this.hex_with_component(a,1,c);return a=this.hex_with_component(a,0,d)},component_from_hex:function(a,c){return a>>c*8&255},hex_with_component:function(a,c,d){return d<<(e=c*8)|a&~(255<