/** * dat.globe Javascript WebGL Globe Toolkit * http://dataarts.github.com/dat.globe * * Copyright 2011 Data Arts Team, Google Creative Lab * * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 */ var DAT = DAT || {}; DAT.Globe = function(container, colorFn) { colorFn = colorFn || function(x) { var c = new THREE.Color(); c.setHSV( ( 0.6 - ( x * 0.5 ) ), 1.0, 1.0 ); return c; }; var Shaders = { 'earth' : { uniforms: { 'texture': { type: 't', value: 0, texture: null } }, vertexShader: [ 'varying vec3 vNormal;', 'varying vec2 vUv;', 'void main() {', 'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', 'vNormal = normalize( normalMatrix * normal );', 'vUv = uv;', '}' ].join('\n'), fragmentShader: [ 'uniform sampler2D texture;', 'varying vec3 vNormal;', 'varying vec2 vUv;', 'void main() {', 'vec3 diffuse = texture2D( texture, vUv ).xyz;', 'float intensity = 1.05 - dot( vNormal, vec3( 0.0, 0.0, 1.0 ) );', 'vec3 atmosphere = vec3( 1.0, 1.0, 1.0 ) * pow( intensity, 3.0 );', 'gl_FragColor = vec4( diffuse + atmosphere, 1.0 );', '}' ].join('\n') }, 'atmosphere' : { uniforms: {}, vertexShader: [ 'varying vec3 vNormal;', 'void main() {', 'vNormal = normalize( normalMatrix * normal );', 'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', '}' ].join('\n'), fragmentShader: [ 'varying vec3 vNormal;', 'void main() {', 'float intensity = pow( 0.8 - dot( vNormal, vec3( 0, 0, 1.0 ) ), 12.0 ) * 0.7;', 'gl_FragColor = vec4( 0.55 + 0.36*intensity, 0.83 + 0.14*intensity, 0.95 + 0.04 * intensity, 1.0 * intensity ) ;', '}' ].join('\n') } }; var camera, scene, sceneAtmosphere, renderer, w, h; var vector, mesh, atmosphere, point; var overRenderer; /*custom change: the 'imgDir' attribute control the path of the globle's texture*/ var imgDir = './images/'; var curZoomSpeed = 0; var zoomSpeed = 50; var mouse = { x: 0, y: 0 }, mouseOnDown = { x: 0, y: 0 }; var rotation = { x: 0, y: 0 }, target = { x: Math.PI*3/2, y: Math.PI / 6.0 }, targetOnDown = { x: 0, y: 0 }; var distance = 100000, distanceTarget = 100000; var padding = 40; var PI_HALF = Math.PI / 2; function init() { container.style.color = '#ff0'; container.style.font = '13px/20px Arial, sans-serif'; var shader, uniforms, material; w = container.offsetWidth || window.innerWidth; h = container.offsetHeight || window.innerHeight; camera = new THREE.Camera( 30, w / h, 1, 10000); camera.position.z = distance; vector = new THREE.Vector3(); scene = new THREE.Scene(); sceneAtmosphere = new THREE.Scene(); var geometry = new THREE.Sphere(200, 40, 30); shader = Shaders['earth']; uniforms = THREE.UniformsUtils.clone(shader.uniforms); uniforms['texture'].texture = THREE.ImageUtils.loadTexture(imgDir+'world' + '.jpg'); material = new THREE.MeshShaderMaterial({ uniforms: uniforms, vertexShader: shader.vertexShader, fragmentShader: shader.fragmentShader }); mesh = new THREE.Mesh(geometry, material); mesh.matrixAutoUpdate = false; scene.addObject(mesh); shader = Shaders['atmosphere']; uniforms = THREE.UniformsUtils.clone(shader.uniforms); material = new THREE.MeshShaderMaterial({ uniforms: uniforms, vertexShader: shader.vertexShader, fragmentShader: shader.fragmentShader }); mesh = new THREE.Mesh(geometry, material); mesh.scale.x = mesh.scale.y = mesh.scale.z = 1.1; mesh.flipSided = true; mesh.matrixAutoUpdate = false; mesh.updateMatrix(); sceneAtmosphere.addObject(mesh); geometry = new THREE.Cube(0.75, 0.75, 1, 1, 1, 1, null, false, { px: true, nx: true, py: true, ny: true, pz: false, nz: true}); for (var i = 0; i < geometry.vertices.length; i++) { var vertex = geometry.vertices[i]; vertex.position.z += 0.5; } point = new THREE.Mesh(geometry); renderer = new THREE.WebGLRenderer({antialias: true}); renderer.autoClear = false; renderer.setClearColorHex(0x000000, 0.0); renderer.setSize(w, h); renderer.domElement.style.position = 'absolute'; container.appendChild(renderer.domElement); container.addEventListener('mousedown', onMouseDown, false); container.addEventListener('mousewheel', onMouseWheel, false); document.addEventListener('keydown', onDocumentKeyDown, false); window.addEventListener('resize', onWindowResize, false); container.addEventListener('mouseover', function() { overRenderer = true; }, false); container.addEventListener('mouseout', function() { overRenderer = false; }, false); } addData = function(data, opts) { var lat, lng, size, color, i, step, colorFnWrapper; opts.animated = opts.animated || false; this.is_animated = opts.animated; opts.format = opts.format || 'magnitude'; // other option is 'legend' console.log(opts.format); if (opts.format === 'magnitude') { step = 3; colorFnWrapper = function(data, i) { return colorFn(data[i+2]); } } else if (opts.format === 'legend') { step = 4; colorFnWrapper = function(data, i) { return colorFn(data[i+3]); } } else { throw('error: format not supported: '+opts.format); } if (opts.animated) { if (this._baseGeometry === undefined) { this._baseGeometry = new THREE.Geometry(); for (i = 0; i < data.length; i += step) { lat = data[i]; lng = data[i + 1]; // size = data[i + 2]; color = colorFnWrapper(data,i); size = 0; addPoint(lat, lng, size, color, this._baseGeometry); } } if(this._morphTargetId === undefined) { this._morphTargetId = 0; } else { this._morphTargetId += 1; } opts.name = opts.name || 'morphTarget'+this._morphTargetId; } var subgeo = new THREE.Geometry(); for (i = 0; i < data.length; i += step) { lat = data[i]; lng = data[i + 1]; color = colorFnWrapper(data,i); size = data[i + 2]; size = size*200; addPoint(lat, lng, size, color, subgeo); } if (opts.animated) { this._baseGeometry.morphTargets.push({'name': opts.name, vertices: subgeo.vertices}); } else { this._baseGeometry = subgeo; } }; function createPoints() { if (this._baseGeometry !== undefined) { if (this.is_animated === false) { this.points = new THREE.Mesh(this._baseGeometry, new THREE.MeshBasicMaterial({ color: 0xffffff, vertexColors: THREE.FaceColors, morphTargets: false })); } else { if (this._baseGeometry.morphTargets.length < 8) { console.log('t l',this._baseGeometry.morphTargets.length); var padding = 8-this._baseGeometry.morphTargets.length; console.log('padding', padding); for(var i=0; i<=padding; i++) { console.log('padding',i); this._baseGeometry.morphTargets.push({'name': 'morphPadding'+i, vertices: this._baseGeometry.vertices}); } } this.points = new THREE.Mesh(this._baseGeometry, new THREE.MeshBasicMaterial({ color: 0xffffff, vertexColors: THREE.FaceColors, morphTargets: true })); } scene.addObject(this.points); } } function addPoint(lat, lng, size, color, subgeo) { var phi = (90 - lat) * Math.PI / 180; var theta = (180 - lng) * Math.PI / 180; point.position.x = 200 * Math.sin(phi) * Math.cos(theta); point.position.y = 200 * Math.cos(phi); point.position.z = 200 * Math.sin(phi) * Math.sin(theta); point.lookAt(mesh.position); point.scale.z = -size; point.updateMatrix(); var i; for (i = 0; i < point.geometry.faces.length; i++) { point.geometry.faces[i].color = color; } GeometryUtils.merge(subgeo, point); } function onMouseDown(event) { event.preventDefault(); container.addEventListener('mousemove', onMouseMove, false); container.addEventListener('mouseup', onMouseUp, false); container.addEventListener('mouseout', onMouseOut, false); mouseOnDown.x = - event.clientX; mouseOnDown.y = event.clientY; targetOnDown.x = target.x; targetOnDown.y = target.y; container.style.cursor = 'move'; } function onMouseMove(event) { mouse.x = - event.clientX; mouse.y = event.clientY; var zoomDamp = distance/1000; target.x = targetOnDown.x + (mouse.x - mouseOnDown.x) * 0.005 * zoomDamp; target.y = targetOnDown.y + (mouse.y - mouseOnDown.y) * 0.005 * zoomDamp; target.y = target.y > PI_HALF ? PI_HALF : target.y; target.y = target.y < - PI_HALF ? - PI_HALF : target.y; } function onMouseUp(event) { container.removeEventListener('mousemove', onMouseMove, false); container.removeEventListener('mouseup', onMouseUp, false); container.removeEventListener('mouseout', onMouseOut, false); container.style.cursor = 'auto'; } function onMouseOut(event) { container.removeEventListener('mousemove', onMouseMove, false); container.removeEventListener('mouseup', onMouseUp, false); container.removeEventListener('mouseout', onMouseOut, false); } function onMouseWheel(event) { event.preventDefault(); if (overRenderer) { zoom(event.wheelDeltaY * 0.3); } return false; } function onDocumentKeyDown(event) { switch (event.keyCode) { case 38: zoom(100); event.preventDefault(); break; case 40: zoom(-100); event.preventDefault(); break; } } function onWindowResize( event ) { console.log('resize'); camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); /*custom change: if there is a container, the width and height is the container's width and height*/ renderer.setSize( container.offsetWidth || window.innerWidth, container.offsetHeight || window.innerHeight ); } function zoom(delta) { distanceTarget -= delta; distanceTarget = distanceTarget > 1000 ? 1000 : distanceTarget; distanceTarget = distanceTarget < 350 ? 350 : distanceTarget; } function animate() { requestAnimationFrame(animate); render(); } function render() { zoom(curZoomSpeed); rotation.x += (target.x - rotation.x) * 0.1; rotation.y += (target.y - rotation.y) * 0.1; distance += (distanceTarget - distance) * 0.3; camera.position.x = distance * Math.sin(rotation.x) * Math.cos(rotation.y); camera.position.y = distance * Math.sin(rotation.y); camera.position.z = distance * Math.cos(rotation.x) * Math.cos(rotation.y); vector.copy(camera.position); renderer.clear(); renderer.render(scene, camera); renderer.render(sceneAtmosphere, camera); } init(); this.animate = animate; this.__defineGetter__('time', function() { return this._time || 0; }); this.__defineSetter__('time', function(t) { var validMorphs = []; var morphDict = this.points.morphTargetDictionary; for(var k in morphDict) { if(k.indexOf('morphPadding') < 0) { validMorphs.push(morphDict[k]); } } validMorphs.sort(); var l = validMorphs.length-1; var scaledt = t*l+1; var index = Math.floor(scaledt); for (i=0;i= 0) { this.points.morphTargetInfluences[lastIndex] = 1 - leftover; } this.points.morphTargetInfluences[index] = leftover; this._time = t; }); this.addData = addData; this.createPoints = createPoints; this.renderer = renderer; this.scene = scene; return this; };