Earth (in THREE.js)
by PhilQ
HTML
<script type="x-shader/x-vertex" id="_vertexShader">
uniform vec2 resolution;
varying vec4 vPos;
varying vec3 vNormal;
varying vec2 vUv;
varying mat4 matModel;
varying mat4 matModelView;
varying mat4 matProjection;
varying mat3 matNormal;
void main() {
// Send to fragment shader
vNormal = normal;
vUv = uv;
vPos = vec4(position, 1.0);
matModel = modelMatrix;
matModelView = modelViewMatrix;
matProjection = projectionMatrix;
matNormal = normalMatrix;
// Local space (object)
gl_Position = vPos;
// to World space (scene)
gl_Position = modelMatrix * vPos;
// to View space (camera)
gl_Position = viewMatrix * modelMatrix * vPos;
// to Clip space
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vPos;
// Note: Three.js will convert to NDC space and then to Screen/Viewport space
}
</script>
<script type="x-shader/x-fragment" id="_fragmentShader">
uniform vec2 resolution;
varying vec4 vPos;
varying vec3 vNormal;
varying vec2 vUv;
varying mat4 matModel;
varying mat4 matModelView;
varying mat4 matProjection;
varying mat3 matNormal;
void main() {
vec4 localUv = vec4(vUv, 0.0, 1.0); // Local uv, bottom-left (0,0) to top-right (1,1)
vec4 localPos = vPos;
vec4 worldPos = matModel * vPos;
vec4 viewPos = viewMatrix * matModel * vPos;
vec4 clipPos = matProjection * viewMatrix * matModel * vPos;
vec4 ndcPos = vec4(clipPos.xyz / clipPos.w, clipPos.w); // Added w to be able to back-calculate to clip space
vec2 screenPos = (ndcPos.xy + 1.0) * 0.5; // Screen uv, bottom-left (0,0) to top-right (1,1)
vec2 viewportPos = vec2(screenPos.x, -screenPos.y); // Top-left (0,0) to bottom-right (1,1)
vec2 pixelPos = screenPos * resolution; // Actual pixel coordinates
// gl_FragColor = vec4(screenPos.xy, 0.0, 1.0);
// Basic color + lighting
vec4 color = vec4(1.0, 0.0, 1.0, 1.0);
vec3 light = normalize( vec3( 0.5, 0.2, 1.0 ) ); // Normalized direction (like sunlight)
float...
CSS
body { margin: 0; }
canvas { width: 100%; height: 100% }
#toggle {
position: fixed;
top: 0;
left: 0;
z-index: 10;
}
JavaScript
import * as THREE from 'three';
import { OrbitControls } from 'OrbitControls';
import { VertexNormalsHelper } from 'VertexNormalsHelper';
class LatLng {
constructor( lat, lng ) {
// Latitude and longitude in radians
this.lat = lat ? lat : 0;
this.lng = lng ? lng : 0;
this.u = 0;
this.v = 0;
this.updateUV();
}
// Latitude:
// -90 (N) --> 90 (S)
// PI/2 --> -PI/2
// Longitude:
// -180 (W) --> 180 (E)
// -PI --> PI
updateUV() {
this.v = (this.lat / (-0.5 * Math.PI)) * 0.5 + 0.5;
this.u = (this.lng / Math.PI) * 0.5 + 0.5;
if (this.v > 0 && this.v < 1) {
this.u = ((this.u - 0.125) + 1) % 1; // Translate uv map 1/8 eastward to align map and face edges
}
}
fromUV( u, v ) {
this.lng = 2 * (u - 0.5) * Math.PI;
this.lat = 2 * (v - 0.5) * -0.5 * Math.PI;
}
fromVector( v ) {
// From point on unit sphere
v = v.clone().normalize();
this.lat = Math.asin( v.y );
this.lng = Math.atan2( v.x, v.z ); //TODO: maybe -z ?
this.updateUV();
return this;
}
toVector() {
// To point on unit sphere
let r = Math.cos( this.lat );
return new THREE.Vector3(
Math.sin( this.lng ) * r,
Math.sin( this.lat ),
Math.cos( this.lng ) * r //TODO: maybe -z ?
);
}
}
THREE.Vector3.prototype.toLatLng = function() {
return new LatLng().fromVector( this );
};
THREE.Vector3.prototype.fromLatLng = function( latlng ) {
return this.copy( latlng.toVector() );
};
class Planet extends THREE.Group {
constructor( params ) {
super();
//this.type = 'Planet';
if ( params.position ) {
this.position.copy( params.position );
}
this.r = params.r ? params.r : 1;
this.segments = params.segments ? params.segments : 1;
this.distributeMoreEqually = typeof params.distributeMoreEqually !== 'undefined' ? params.distributeMoreEqually : false;
this.colored = typeof params.colored !== 'undefined' ? params.colored : true;
this.minU = Infinity;
this.maxU = -Infinity;
this.minV = Infinity;
this.maxV =...