globe.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. /**
  2. * dat.globe Javascript WebGL Globe Toolkit
  3. * http://dataarts.github.com/dat.globe
  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 || {};
  14. DAT.Globe = function(container, opts) {
  15. opts = opts || {};
  16. var colorFn = opts.colorFn || function(x) {
  17. var c = new THREE.Color();
  18. c.setHSL( ( 0.5 - (x * 2) ), Math.max(0.8, 1.0 - (x * 3)), 0.5 );
  19. return c;
  20. };
  21. var imgDir = opts.imgDir || '/globe/';
  22. var Shaders = {
  23. 'earth' : {
  24. uniforms: {
  25. 'texture': { type: 't', value: null }
  26. },
  27. vertexShader: [
  28. 'varying vec3 vNormal;',
  29. 'varying vec2 vUv;',
  30. 'void main() {',
  31. 'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
  32. 'vNormal = normalize( normalMatrix * normal );',
  33. 'vUv = uv;',
  34. '}'
  35. ].join('\n'),
  36. fragmentShader: [
  37. 'uniform sampler2D texture;',
  38. 'varying vec3 vNormal;',
  39. 'varying vec2 vUv;',
  40. 'void main() {',
  41. 'vec3 diffuse = texture2D( texture, vUv ).xyz;',
  42. 'float intensity = 1.05 - dot( vNormal, vec3( 0.0, 0.0, 1.0 ) );',
  43. 'vec3 atmosphere = vec3( 1.0, 1.0, 1.0 ) * pow( intensity, 3.0 );',
  44. 'gl_FragColor = vec4( diffuse + atmosphere, 1.0 );',
  45. '}'
  46. ].join('\n')
  47. },
  48. 'atmosphere' : {
  49. uniforms: {},
  50. vertexShader: [
  51. 'varying vec3 vNormal;',
  52. 'void main() {',
  53. 'vNormal = normalize( normalMatrix * normal );',
  54. 'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
  55. '}'
  56. ].join('\n'),
  57. fragmentShader: [
  58. 'varying vec3 vNormal;',
  59. 'void main() {',
  60. 'float intensity = pow( 0.8 - dot( vNormal, vec3( 0, 0, 1.0 ) ), 12.0 );',
  61. 'gl_FragColor = vec4( 1.0, 1.0, 1.0, 1.0 ) * intensity;',
  62. '}'
  63. ].join('\n')
  64. }
  65. };
  66. var camera, scene, renderer, w, h;
  67. var mesh, atmosphere, point, running;
  68. var overRenderer;
  69. var running = true;
  70. var curZoomSpeed = 0;
  71. var zoomSpeed = 50;
  72. var mouse = { x: 0, y: 0 }, mouseOnDown = { x: 0, y: 0 };
  73. var rotation = { x: 0, y: 0 },
  74. target = { x: Math.PI*3/2, y: Math.PI / 6.0 },
  75. targetOnDown = { x: 0, y: 0 };
  76. var distance = 100000, distanceTarget = 100000;
  77. var padding = 10;
  78. var PI_HALF = Math.PI / 2;
  79. function init() {
  80. container.style.color = '#fff';
  81. container.style.font = '13px/20px Arial, sans-serif';
  82. var shader, uniforms, material;
  83. w = container.offsetWidth || window.innerWidth;
  84. h = container.offsetHeight || window.innerHeight;
  85. camera = new THREE.PerspectiveCamera(30, w / h, 1, 10000);
  86. camera.position.z = distance;
  87. scene = new THREE.Scene();
  88. var geometry = new THREE.SphereGeometry(200, 40, 30);
  89. shader = Shaders['earth'];
  90. uniforms = THREE.UniformsUtils.clone(shader.uniforms);
  91. uniforms['texture'].value = THREE.ImageUtils.loadTexture(imgDir+'world.jpg');
  92. material = new THREE.ShaderMaterial({
  93. uniforms: uniforms,
  94. vertexShader: shader.vertexShader,
  95. fragmentShader: shader.fragmentShader
  96. });
  97. mesh = new THREE.Mesh(geometry, material);
  98. mesh.rotation.y = Math.PI;
  99. scene.add(mesh);
  100. shader = Shaders['atmosphere'];
  101. uniforms = THREE.UniformsUtils.clone(shader.uniforms);
  102. material = new THREE.ShaderMaterial({
  103. uniforms: uniforms,
  104. vertexShader: shader.vertexShader,
  105. fragmentShader: shader.fragmentShader,
  106. side: THREE.BackSide,
  107. blending: THREE.AdditiveBlending,
  108. transparent: true
  109. });
  110. mesh = new THREE.Mesh(geometry, material);
  111. mesh.scale.set( 1.1, 1.1, 1.1 );
  112. scene.add(mesh);
  113. geometry = new THREE.BoxGeometry(2.75, 2.75, 1);
  114. geometry.applyMatrix(new THREE.Matrix4().makeTranslation(0,0,-0.5));
  115. point = new THREE.Mesh(geometry);
  116. renderer = new THREE.WebGLRenderer({antialias: true});
  117. renderer.setSize(w, h);
  118. renderer.setClearColor( 0x212121, 1 );
  119. renderer.domElement.style.position = 'relative';
  120. container.appendChild(renderer.domElement);
  121. container.addEventListener('mousedown', onMouseDown, false);
  122. if ('onwheel' in document) {
  123. container.addEventListener('wheel', onMouseWheel, false);
  124. } else {
  125. container.addEventListener('mousewheel', onMouseWheel, false);
  126. }
  127. document.addEventListener('keydown', onDocumentKeyDown, false);
  128. window.addEventListener('resize', onWindowResize, false);
  129. container.addEventListener('mouseover', function() {
  130. overRenderer = true;
  131. }, false);
  132. container.addEventListener('mouseout', function() {
  133. overRenderer = false;
  134. }, false);
  135. }
  136. function addData(data, opts) {
  137. var lat, lng, size, color, i, step, colorFnWrapper;
  138. opts.animated = opts.animated || false;
  139. this.is_animated = opts.animated;
  140. opts.format = opts.format || 'magnitude'; // other option is 'legend'
  141. if (opts.format === 'magnitude') {
  142. step = 3;
  143. colorFnWrapper = function(data, i) { return colorFn(data[i+2]); }
  144. } else if (opts.format === 'legend') {
  145. step = 4;
  146. colorFnWrapper = function(data, i) { return colorFn(data[i+3]); }
  147. } else if (opts.format === 'peer') {
  148. colorFnWrapper = function(data, i) { return colorFn(data[i+2]); }
  149. } else {
  150. throw('error: format not supported: '+opts.format);
  151. }
  152. if (opts.animated) {
  153. if (this._baseGeometry === undefined) {
  154. this._baseGeometry = new THREE.Geometry();
  155. for (i = 0; i < data.length; i += step) {
  156. lat = data[i];
  157. lng = data[i + 1];
  158. // size = data[i + 2];
  159. color = colorFnWrapper(data,i);
  160. size = 0;
  161. addPoint(lat, lng, size, color, this._baseGeometry);
  162. }
  163. }
  164. if(this._morphTargetId === undefined) {
  165. this._morphTargetId = 0;
  166. } else {
  167. this._morphTargetId += 1;
  168. }
  169. opts.name = opts.name || 'morphTarget'+this._morphTargetId;
  170. }
  171. var subgeo = new THREE.Geometry();
  172. for (i = 0; i < data.length; i += step) {
  173. lat = data[i];
  174. lng = data[i + 1];
  175. color = colorFnWrapper(data,i);
  176. size = data[i + 2];
  177. size = size*200;
  178. addPoint(lat, lng, size, color, subgeo);
  179. }
  180. if (opts.animated) {
  181. this._baseGeometry.morphTargets.push({'name': opts.name, vertices: subgeo.vertices});
  182. } else {
  183. this._baseGeometry = subgeo;
  184. }
  185. };
  186. function createPoints() {
  187. if (this._baseGeometry !== undefined) {
  188. if (this.is_animated === false) {
  189. this.points = new THREE.Mesh(this._baseGeometry, new THREE.MeshBasicMaterial({
  190. color: 0xffffff,
  191. vertexColors: THREE.FaceColors,
  192. morphTargets: false
  193. }));
  194. } else {
  195. if (this._baseGeometry.morphTargets.length < 8) {
  196. console.log('t l',this._baseGeometry.morphTargets.length);
  197. var padding = 8-this._baseGeometry.morphTargets.length;
  198. console.log('padding', padding);
  199. for(var i=0; i<=padding; i++) {
  200. console.log('padding',i);
  201. this._baseGeometry.morphTargets.push({'name': 'morphPadding'+i, vertices: this._baseGeometry.vertices});
  202. }
  203. }
  204. this.points = new THREE.Mesh(this._baseGeometry, new THREE.MeshBasicMaterial({
  205. color: 0xffffff,
  206. vertexColors: THREE.FaceColors,
  207. morphTargets: true
  208. }));
  209. }
  210. scene.add(this.points);
  211. }
  212. }
  213. function addPoint(lat, lng, size, color, subgeo) {
  214. var phi = (90 - lat) * Math.PI / 180;
  215. var theta = (180 - lng) * Math.PI / 180;
  216. point.position.x = 200 * Math.sin(phi) * Math.cos(theta);
  217. point.position.y = 200 * Math.cos(phi);
  218. point.position.z = 200 * Math.sin(phi) * Math.sin(theta);
  219. point.lookAt(mesh.position);
  220. point.scale.z = Math.max( size, 0.1 ); // avoid non-invertible matrix
  221. point.updateMatrix();
  222. for (var i = 0; i < point.geometry.faces.length; i++) {
  223. point.geometry.faces[i].color = color;
  224. }
  225. if(point.matrixAutoUpdate){
  226. point.updateMatrix();
  227. }
  228. subgeo.merge(point.geometry, point.matrix);
  229. }
  230. function onMouseDown(event) {
  231. event.preventDefault();
  232. container.addEventListener('mousemove', onMouseMove, false);
  233. container.addEventListener('mouseup', onMouseUp, false);
  234. container.addEventListener('mouseout', onMouseOut, false);
  235. mouseOnDown.x = - event.clientX;
  236. mouseOnDown.y = event.clientY;
  237. targetOnDown.x = target.x;
  238. targetOnDown.y = target.y;
  239. container.style.cursor = 'move';
  240. }
  241. function onMouseMove(event) {
  242. mouse.x = - event.clientX;
  243. mouse.y = event.clientY;
  244. var zoomDamp = distance/1000;
  245. target.x = targetOnDown.x + (mouse.x - mouseOnDown.x) * 0.005 * zoomDamp;
  246. target.y = targetOnDown.y + (mouse.y - mouseOnDown.y) * 0.005 * zoomDamp;
  247. target.y = target.y > PI_HALF ? PI_HALF : target.y;
  248. target.y = target.y < - PI_HALF ? - PI_HALF : target.y;
  249. }
  250. function onMouseUp(event) {
  251. container.removeEventListener('mousemove', onMouseMove, false);
  252. container.removeEventListener('mouseup', onMouseUp, false);
  253. container.removeEventListener('mouseout', onMouseOut, false);
  254. container.style.cursor = 'auto';
  255. }
  256. function onMouseOut(event) {
  257. container.removeEventListener('mousemove', onMouseMove, false);
  258. container.removeEventListener('mouseup', onMouseUp, false);
  259. container.removeEventListener('mouseout', onMouseOut, false);
  260. }
  261. function onMouseWheel(event) {
  262. if (container.style.cursor != "move") return false;
  263. event.preventDefault();
  264. if (overRenderer) {
  265. if (event.deltaY) {
  266. zoom(-event.deltaY * (event.deltaMode == 0 ? 1 : 50));
  267. } else {
  268. zoom(event.wheelDeltaY * 0.3);
  269. }
  270. }
  271. return false;
  272. }
  273. function onDocumentKeyDown(event) {
  274. switch (event.keyCode) {
  275. case 38:
  276. zoom(100);
  277. event.preventDefault();
  278. break;
  279. case 40:
  280. zoom(-100);
  281. event.preventDefault();
  282. break;
  283. }
  284. }
  285. function onWindowResize( event ) {
  286. camera.aspect = container.offsetWidth / container.offsetHeight;
  287. camera.updateProjectionMatrix();
  288. renderer.setSize( container.offsetWidth, container.offsetHeight );
  289. }
  290. function zoom(delta) {
  291. distanceTarget -= delta;
  292. distanceTarget = distanceTarget > 855 ? 855 : distanceTarget;
  293. distanceTarget = distanceTarget < 350 ? 350 : distanceTarget;
  294. }
  295. function animate() {
  296. if (!running) return
  297. requestAnimationFrame(animate);
  298. render();
  299. }
  300. function render() {
  301. zoom(curZoomSpeed);
  302. rotation.x += (target.x - rotation.x) * 0.1;
  303. rotation.y += (target.y - rotation.y) * 0.1;
  304. distance += (distanceTarget - distance) * 0.3;
  305. camera.position.x = distance * Math.sin(rotation.x) * Math.cos(rotation.y);
  306. camera.position.y = distance * Math.sin(rotation.y);
  307. camera.position.z = distance * Math.cos(rotation.x) * Math.cos(rotation.y);
  308. camera.lookAt(mesh.position);
  309. renderer.render(scene, camera);
  310. }
  311. function unload() {
  312. running = false
  313. container.removeEventListener('mousedown', onMouseDown, false);
  314. if ('onwheel' in document) {
  315. container.removeEventListener('wheel', onMouseWheel, false);
  316. } else {
  317. container.removeEventListener('mousewheel', onMouseWheel, false);
  318. }
  319. document.removeEventListener('keydown', onDocumentKeyDown, false);
  320. window.removeEventListener('resize', onWindowResize, false);
  321. }
  322. init();
  323. this.animate = animate;
  324. this.unload = unload;
  325. this.__defineGetter__('time', function() {
  326. return this._time || 0;
  327. });
  328. this.__defineSetter__('time', function(t) {
  329. var validMorphs = [];
  330. var morphDict = this.points.morphTargetDictionary;
  331. for(var k in morphDict) {
  332. if(k.indexOf('morphPadding') < 0) {
  333. validMorphs.push(morphDict[k]);
  334. }
  335. }
  336. validMorphs.sort();
  337. var l = validMorphs.length-1;
  338. var scaledt = t*l+1;
  339. var index = Math.floor(scaledt);
  340. for (i=0;i<validMorphs.length;i++) {
  341. this.points.morphTargetInfluences[validMorphs[i]] = 0;
  342. }
  343. var lastIndex = index - 1;
  344. var leftover = scaledt - index;
  345. if (lastIndex >= 0) {
  346. this.points.morphTargetInfluences[lastIndex] = 1 - leftover;
  347. }
  348. this.points.morphTargetInfluences[index] = leftover;
  349. this._time = t;
  350. });
  351. this.addData = addData;
  352. this.createPoints = createPoints;
  353. this.renderer = renderer;
  354. this.scene = scene;
  355. return this;
  356. };