WIP Interactive globe from Nico's sources

- The globe is a bit small? Ability to control the max-min size potentially
- Is there a reason why `globe.update()` runs every second? Sounds like a lot of resources?
- Have the ability to control the `addEventListener` of the markers to do whatever (in this case, going to a route by clicking on a link with a sapper-noscroll attribute + changing the href attribute on click - the method `goto` from Sapper scrolls back to top / maybe something to fix with the current transition issues?)
- Edited in `./index.js`:
    1. Using the class as `export default WebglGlobe` instead of Window (as Svelte or Sapper doesn't likayt)
- Edited in `Camera.js`:
    1. Commented line 218: `e.preventDefault();` would cause this error: `[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See <URL>`
This commit is contained in:
2020-04-02 20:55:20 +02:00
parent 730eb75457
commit 2064885997
73 changed files with 15339 additions and 137 deletions

286
src/globe/index.js Normal file
View File

@@ -0,0 +1,286 @@
import { Renderer } from './beam'
import { Camera } from './beam'
import { vec2, vec3, mat4 } from './beam'
import { Container, Mesh, Material, Texture, SphereGeometryBuffer } from './beam'
import settings from './settings'
// Shaders in strings
import GlobeVS from './globe-vs'
import GlobeFS from './globe-fs'
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? [
parseInt(result[1], 16) / 255,
parseInt(result[2], 16) / 255,
parseInt(result[3], 16) / 255
] : [0,0,0];
}
function lonLatToVector3(lng, lat) {
var phi = (90-lat)*(Math.PI/180),
theta = (lng+180)*(Math.PI/180),
x = -( Math.sin(phi) * Math.cos(theta) ),
z = Math.sin(phi) * Math.sin(theta),
y = Math.cos(phi);
return [x,y,z];
}
class WebglGlobe {
// Constructor
constructor (options) {
this.options = options;
this.$el = options.el;
this.cities = options.markers
let gl;
let canvas = document.createElement('canvas')
try { gl = canvas.getContext("webgl"); }
catch (x) {
try { gl = canvas.getContext("experimental-webgl"); }
catch (x) { gl = null; }
}
this.supportWebgl = gl !== null;
if (this.supportWebgl) {
this.buildWebglScene()
this.resize()
// this.initPointer()
}
}
// Build
buildWebglScene () {
this.renderer = new Renderer({
alpha: this.alpha,
antialias: this.antialias,
});
this.$el.appendChild(this.renderer.canvas);
this.texture = Texture.fromUrl(this.renderer.gl, this.options.texture)
this.scene = new Container()
this.camera = new Camera({
fov: settings.fov,
near: 1,
far: settings.cameraFar,
type: 'perspective',
orbitControl: true,
firstPerson: false,
lookAt: [0,0,0],
position: [0,0, (3) / 2 / Math.tan(Math.PI * settings.fov / 360)],
});
this.camera.lookAt = vec3.create()
this.inverseViewProjectionMatrix = mat4.create()
this.viewProjectionMatrix = mat4.create()
this.cameraDirection = vec3.create();
this.cameraPosition = vec3.create();
this.globeMesh = new Mesh();
this.globeMesh.material = new Material(this.renderer.gl, {
uniforms: {
tInput: this.texture
},
vertexShader: GlobeVS,
fragmentShader: GlobeFS,
// depthTest: true
})
this.globeMesh.geometry = new SphereGeometryBuffer(this.renderer.gl, {
radius: 1,
widthSegments: 100, heightSegments: 100
})
this.scene.add( this.globeMesh )
this.markers = []
let markers = this.cities
for (let i=0; i<markers.length; i++) {
let markerMesh = new Mesh();
markerMesh.material = new Material(this.renderer.gl, {
blend: true,
uniforms: {
color: hexToRgb( "#FF6C89" ),
alpha: 0,
},
})
markerMesh.geometry = new SphereGeometryBuffer(this.renderer.gl, {
radius: 0.01,
widthSegments: 10, heightSegments: 10
})
let p = lonLatToVector3( markers[i].lng, markers[i].lat )
markerMesh.position[0] = p[0] * 1.
markerMesh.position[1] = p[1] * 1.
markerMesh.position[2] = p[2] * 1.
this.scene.add( markerMesh )
let el = document.createElement('div')
let span = document.createElement('span')
span.innerHTML = markers[i].name
el.appendChild(span)
el.addEventListener('click', ()=>{
alert('click on ' + markers[i].name)
})
el.classList.add('marker')
this.$el.appendChild( el )
this.markers.push({
mesh: markerMesh,
el: el,
position: p,
screenPosition: [0,0]
})
}
}
// Resize method
resize () {
this.width = this.$el.clientWidth;
this.height = this.$el.clientHeight;
settings.resolution[0] = this.width //* settings.devicePixelRatio
settings.resolution[1] = this.height //* settings.devicePixelRatio
if (!this.supportWebgl) {
return
}
this.renderer.setPixelRatio(settings.devicePixelRatio);
this.renderer.resize(settings.resolution[0], settings.resolution[1])
this.camera.aspect = settings.resolution[0] / settings.resolution[1]
this.camera.updateProjectionMatrix()
this.camera.lookAt[0] = 0
this.camera.lookAt[1] = 0
this.camera.lookAt[2] = 0
this.camera.rotation[0] = 0
this.camera.rotation[1] = 0
this.camera.rotation[2] = 0
this.camera.position[0] = 0
this.camera.position[1] = 0
this.camera.position[2] = (3) / 2 / Math.tan(Math.PI * settings.fov / 360)
this.camera.render();
vec3.set(this.cameraPosition, this.camera.worldMatrix[12], this.camera.worldMatrix[13], this.camera.worldMatrix[14]);
vec3.copy(this.cameraDirection, this.camera.lookAt);
vec3.subtract(this.cameraDirection, this.cameraDirection, this.cameraPosition);
vec3.normalize(this.cameraDirection, this.cameraDirection);
mat4.copy(this.viewProjectionMatrix, this.camera.projectionMatrix);
mat4.multiply(this.viewProjectionMatrix, this.viewProjectionMatrix, this.camera.inverseWorldMatrix);
let refPos = vec3.create()
vec3.set(refPos, 0, 1, 0 )
vec3.transformMat4(refPos, refPos, this.viewProjectionMatrix);
let refx = ( (refPos[0] + 1) / 2) * window.innerWidth
let refy = (1. - (refPos[1] + 1) / 2) * window.innerHeight
let dir2 = vec2.create()
vec2.set( dir2, refx, refy )
let center2 = vec2.create()
vec2.set( center2, window.innerWidth/2, window.innerHeight/2 )
let dir2d2 = vec2.clone(dir2, dir2)
vec2.subtract( dir2d2, dir2d2, center2 )
this.circleScreenSize = vec2.length( dir2d2 ) * 1.04
}
// Update
update () {
if (!this.supportWebgl){
return;
}
// this.currPointer[0] += ( this.pointerRatio[0] - this.currPointer[0]) * 0.05
// this.currPointer[1] += ( this.pointerRatio[1] - this.currPointer[1]) * 0.05
// this.globeMesh.rotation[1] = this.currPointer[0]
// this.globeMesh.rotation[2] = this.currPointer[1]
//manually call this as we prevent ithe camera from update between passes
this.camera.update();
vec3.set(this.cameraPosition, this.camera.worldMatrix[12], this.camera.worldMatrix[13], this.camera.worldMatrix[14]);
vec3.copy(this.cameraDirection, this.camera.lookAt);
vec3.subtract(this.cameraDirection, this.cameraDirection, this.cameraPosition);
vec3.normalize(this.cameraDirection, this.cameraDirection);
mat4.copy(this.viewProjectionMatrix, this.camera.projectionMatrix);
mat4.multiply(this.viewProjectionMatrix, this.viewProjectionMatrix, this.camera.inverseWorldMatrix);
mat4.invert(this.inverseViewProjectionMatrix, this.viewProjectionMatrix);
let screenPos = vec3.create()
this.markers.forEach((marker, i)=>{
vec3.set(screenPos, marker.position[0], marker.position[1], marker.position[2] )
vec3.transformMat4(screenPos, screenPos, this.viewProjectionMatrix);
let x = ( (screenPos[0] + 1) / 2) * window.innerWidth
let y = (1. - (screenPos[1] + 1) / 2) * window.innerHeight
let N = vec3.create()
vec3.set( N, marker.position[0], marker.position[1], marker.position[2] )
vec3.normalize( N, N )
let V = vec3.create()
vec3.set( V, marker.position[0], marker.position[1], marker.position[2] )
vec3.subtract( V, V, this.cameraPosition )
vec3.normalize( V, V );
//behind
if ( vec3.dot( V, N ) * -1 < 0) {
let dir = vec2.create()
vec2.set( dir, x, y )
let center = vec2.create()
vec2.set( center, window.innerWidth/2, window.innerHeight/2 )
let dir2d = vec2.clone(dir, dir)
vec2.subtract( dir2d, dir2d, center )
vec2.normalize( dir2d, dir2d );
vec2.scale( dir2d, dir2d, this.circleScreenSize );
vec2.add(dir2d, dir2d, center)
marker.el.style.transform = `translate(${dir2d[0]}px, ${dir2d[1]}px) translateZ(0)`;
marker.el.classList.remove('is-active')
}
else {
//vec3.dot( V, N ) * -1 < 0 ? (1 - vec3.dot( V, N ) / 0.1 ) : 1;//Math.max( 0, vec3.dot( N, V ) );
marker.el.style.transform = `translate(${x}px, ${y}px) translateZ(0)`;
marker.el.classList.add('is-active')
}
// marker.el.style.opacity = 1.
})
this.renderer.clearColor(0,0,0,0)
this.renderer.clear()
this.renderer.render(this.scene, this.camera);
}
// initPointer() {
// this.currPointer = [0,0]
// this.pointer = [0,0]
// this.pointerRatio = [0,0]
// this._onPointerDown = this._onPointerDown.bind(this)
// this._onPointerMove = this._onPointerMove.bind(this)
// this._onPointerUp = this._onPointerUp.bind(this)
// this._handleOrientation = this._handleOrientation.bind(this)
// document.addEventListener(support.pointerdown, this._onPointerDown, false);
// document.addEventListener(support.pointermove, this._onPointerMove, false);
// document.addEventListener(support.pointerup, this._onPointerUp, false);
// window.addEventListener('deviceorientation', this._handleOrientation);
// }
// _handleOrientation(event) {
// this.pointerRatio[0] = (event.gamma) / 25
// this.pointerRatio[1] = (event.beta-45) / 25
// }
// _onPointerDown() {
// this._isPointerDown = true;
// }
// _onPointerUp() {
// this._isPointerDown = false
// }
// _onPointerMove(event) {
// let pe = support.touch && event.type != 'mousemove' ? (event.touches[0] || event.changedTouches[0]) : event;
// this.pointer[0] = pe.pageX;
// this.pointer[1] = pe.pageY;
// this.pointer[1] -= window.pageYOffset || document.documentElement.scrollTop
// this.pointerRatio[0] = (this.pointer[0] / window.innerWidth - .5) * 2
// this.pointerRatio[1] = (this.pointer[1] / window.innerHeight - .5) * 2
// }
}
// window.WebglGlobe = WebglGlobe
export default WebglGlobe