build js files for globe with webpack and html with react
|
@ -18,10 +18,9 @@
|
||||||
},
|
},
|
||||||
"author": "HF <hf@example.com>",
|
"author": "HF <hf@example.com>",
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
">1%",
|
"defaults",
|
||||||
"last 2 versions",
|
"not IE 11",
|
||||||
"Firefox ESR",
|
"not IE_Mob 11"
|
||||||
"not ie <= 11"
|
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcrypt": "^3.0.6",
|
"bcrypt": "^3.0.6",
|
||||||
|
@ -80,6 +79,8 @@
|
||||||
"sharp": "^0.21.3",
|
"sharp": "^0.21.3",
|
||||||
"startaudiocontext": "^1.2.1",
|
"startaudiocontext": "^1.2.1",
|
||||||
"sweetalert2": "^6.6.6",
|
"sweetalert2": "^6.6.6",
|
||||||
|
"three": "^0.112.1",
|
||||||
|
"three-trackballcontrols-ts": "^0.1.2",
|
||||||
"url-search-params-polyfill": "^7.0.0",
|
"url-search-params-polyfill": "^7.0.0",
|
||||||
"validator": "^7.0.0",
|
"validator": "^7.0.0",
|
||||||
"visibilityjs": "^1.2.4",
|
"visibilityjs": "^1.2.4",
|
||||||
|
|
Before Width: | Height: | Size: 778 KiB After Width: | Height: | Size: 778 KiB |
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 195 KiB |
Before Width: | Height: | Size: 958 KiB After Width: | Height: | Size: 958 KiB |
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
Before Width: | Height: | Size: 658 KiB After Width: | Height: | Size: 658 KiB |
Before Width: | Height: | Size: 4.8 MiB |
Before Width: | Height: | Size: 610 KiB |
|
@ -1,5 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* react html for adminpage
|
* react html for adminpage
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
35
src/components/Globe.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* react html for 3D globe page
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/server';
|
||||||
|
|
||||||
|
import Html from './Html';
|
||||||
|
import assets from './assets.json';
|
||||||
|
import { ASSET_SERVER } from '../core/config';
|
||||||
|
|
||||||
|
import globeCss from './globe.tcss';
|
||||||
|
|
||||||
|
const Globe = () => (
|
||||||
|
<div>
|
||||||
|
<style dangerouslySetInnerHTML={{ __html: globeCss }} />
|
||||||
|
<div id="webgl"></div>
|
||||||
|
<div id="coorbox">(0, 0)</div>
|
||||||
|
<div id="info">Double click on globe to go back.</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
title: 'PixelPlanet.fun 3DGlobe',
|
||||||
|
description: '3D globe of our canvas',
|
||||||
|
scripts: [
|
||||||
|
ASSET_SERVER + assets.globe.js,
|
||||||
|
],
|
||||||
|
body: <Globe />,
|
||||||
|
};
|
||||||
|
const globeHtml = `<!doctype html>${ReactDOM.renderToStaticMarkup(<Html {...data} />)}`;
|
||||||
|
|
||||||
|
export default globeHtml;
|
|
@ -15,7 +15,7 @@ import type { State } from '../reducers';
|
||||||
*/
|
*/
|
||||||
function globe(canvasId, canvasIdent, canvasSize, view) {
|
function globe(canvasId, canvasIdent, canvasSize, view) {
|
||||||
const [x, y] = view.map(Math.round);
|
const [x, y] = view.map(Math.round);
|
||||||
window.location.href = `go/#${canvasIdent},${canvasId},${canvasSize},${x},${y}`;
|
window.location.href = `globe#${canvasIdent},${canvasId},${canvasSize},${x},${y}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
60
src/components/globe.tcss
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #000;
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: Montserrat,sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.tm) {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.webgl-error) {
|
||||||
|
font: 15px/30px monospace;
|
||||||
|
text-align: center;
|
||||||
|
color: #fff;
|
||||||
|
margin: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
:global(.webgl-error) a {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
:global(#coorbox) {
|
||||||
|
position: absolute;
|
||||||
|
background-color: hsla(0,0%,89%,.8);
|
||||||
|
color: #000;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 36px;
|
||||||
|
width: auto;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0 24px;
|
||||||
|
border: solid #000;
|
||||||
|
border-width: thin;
|
||||||
|
left: 16px;
|
||||||
|
bottom: 16px
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
:global(#info) {
|
||||||
|
font-size: 13px;
|
||||||
|
position: absolute;
|
||||||
|
background-color: hsla(0,0%,89%,.8);
|
||||||
|
color: #000;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 36px;
|
||||||
|
width: auto;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0 24px;
|
||||||
|
border: solid #000;
|
||||||
|
border-width: thin;
|
||||||
|
left: 16px;
|
||||||
|
top: 16px
|
||||||
|
}
|
230
src/globe.js
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||||
|
import { TrackballControls } from 'three-trackballcontrols-ts';
|
||||||
|
|
||||||
|
var Detector = {
|
||||||
|
|
||||||
|
canvas: !! window.CanvasRenderingContext2D,
|
||||||
|
webgl: ( function () { try { var canvas = document.createElement( 'canvas' ); return !! window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ); } catch( e ) { return false; } } )(),
|
||||||
|
workers: !! window.Worker,
|
||||||
|
fileapi: window.File && window.FileReader && window.FileList && window.Blob,
|
||||||
|
|
||||||
|
getWebGLErrorMessage: function () {
|
||||||
|
|
||||||
|
var element = document.createElement( 'div' );
|
||||||
|
element.className = 'webgl-error';
|
||||||
|
|
||||||
|
if ( !this.webgl ) {
|
||||||
|
|
||||||
|
element.innerHTML = window.WebGLRenderingContext ? [
|
||||||
|
'Your graphics card does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation">WebGL</a>.<br />',
|
||||||
|
'Find out how to get it <a href="http://get.webgl.org/">here</a>.'
|
||||||
|
].join( '\n' ) : [
|
||||||
|
'Your browser does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation">WebGL</a>.<br/>',
|
||||||
|
'Find out how to get it <a href="http://get.webgl.org/">here</a>.'
|
||||||
|
].join( '\n' );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return element;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
addGetWebGLMessage: function (parent ) {
|
||||||
|
|
||||||
|
parent.appendChild( Detector.getWebGLErrorMessage() );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
function checkMaterial(object) {
|
||||||
|
if (object.material) {
|
||||||
|
const materialName = object.material.name;
|
||||||
|
if (materialName == "canvas") {
|
||||||
|
console.log("Found material");
|
||||||
|
object.material = canvasTexture;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseHashCoords() {
|
||||||
|
try {
|
||||||
|
const hash = window.location.hash;
|
||||||
|
const array = hash.substring(1).split(',');
|
||||||
|
const ident = array.shift();
|
||||||
|
const [id, size, x, y] = array.map((z) => parseInt(z));
|
||||||
|
if (!ident || isNaN(x) || isNaN(y) || isNaN(id) || isNaN(size)) {
|
||||||
|
throw "NaN";
|
||||||
|
}
|
||||||
|
return [ident, id, size, x, y];
|
||||||
|
} catch (error) {
|
||||||
|
return ['d', 0, 65536, 0, 0];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function rotateToCoords(canvasSize, object, coords) {
|
||||||
|
console.log("Rotate to", coords);
|
||||||
|
const [x, y] = coords;
|
||||||
|
const rotOffsetX = 0;
|
||||||
|
const rotOffsetY = 3 * Math.PI / 2;
|
||||||
|
const rotX = -y * Math.PI / canvasSize;
|
||||||
|
const rotY = -x * 2 * Math.PI / canvasSize;
|
||||||
|
object.rotation.x += rotOffsetX + rotX;
|
||||||
|
object.rotation.y += rotOffsetY + rotY;
|
||||||
|
}
|
||||||
|
|
||||||
|
var webglEl = document.getElementById('webgl');
|
||||||
|
|
||||||
|
if (!Detector.webgl) {
|
||||||
|
Detector.addGetWebGLMessage(webglEl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [canvasIdent, canvasId, canvasSize, x, y] = parseHashCoords();
|
||||||
|
|
||||||
|
const canvasTexture = new THREE.MeshPhongMaterial({
|
||||||
|
map: new THREE.TextureLoader().load(`./tiles/${canvasId}/texture.png`),
|
||||||
|
bumpMap: new THREE.TextureLoader().load(`./assets3d/normal${canvasId}.jpg`),
|
||||||
|
bumpScale: 0.02,
|
||||||
|
specularMap: new THREE.TextureLoader().load(`./assets3d/specular${canvasId}.jpg`),
|
||||||
|
specular: new THREE.Color('grey')
|
||||||
|
});
|
||||||
|
|
||||||
|
var width = window.innerWidth,
|
||||||
|
height = window.innerHeight;
|
||||||
|
|
||||||
|
var scene = new THREE.Scene();
|
||||||
|
|
||||||
|
var camera = new THREE.PerspectiveCamera(45, width / height, 0.01, 1000);
|
||||||
|
camera.position.z = 4.0;
|
||||||
|
|
||||||
|
var renderer = new THREE.WebGLRenderer();
|
||||||
|
renderer.setSize(width, height);
|
||||||
|
|
||||||
|
scene.add(new THREE.AmbientLight(0x333333));
|
||||||
|
|
||||||
|
var light = new THREE.DirectionalLight(0xffffff, 0.7);
|
||||||
|
light.position.set(10,6,10);
|
||||||
|
scene.add(light);
|
||||||
|
|
||||||
|
var object = null;
|
||||||
|
var loader = new GLTFLoader();
|
||||||
|
loader.load('./assets3d/globe.glb', function (glb) {
|
||||||
|
scene.add(glb.scene);
|
||||||
|
const children = glb.scene.children;
|
||||||
|
for (let cnt = 0; cnt < children.length; cnt++) {
|
||||||
|
//children[cnt].scale.x *= -1;
|
||||||
|
//children[cnt].scale.y *= -1;
|
||||||
|
if (checkMaterial(children[cnt]))
|
||||||
|
object = children[cnt];
|
||||||
|
const grandchildren = children[cnt].children;
|
||||||
|
for (let xnt = 0; xnt < grandchildren.length; xnt++) {
|
||||||
|
if (checkMaterial(grandchildren[xnt]))
|
||||||
|
object = children[cnt];
|
||||||
|
//children[cnt].material.side = THREE.DoubleSide;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rotateToCoords(canvasSize, object, [x, y]);
|
||||||
|
}, function (xhr) {console.log(`${xhr.loaded} / ${xhr.total}% loaded`);
|
||||||
|
}, function (error) {console.log('An error happened', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Earth params
|
||||||
|
var radius = 0.5,
|
||||||
|
segments = 32,
|
||||||
|
rotation = 6;
|
||||||
|
|
||||||
|
|
||||||
|
var stars = createStars(90, 64);
|
||||||
|
scene.add(stars);
|
||||||
|
|
||||||
|
var controls = new TrackballControls(camera, renderer.domElement);
|
||||||
|
controls.rotateSpeed = 1.0;
|
||||||
|
controls.zoomSpeed = 1.2;
|
||||||
|
controls.panSpeed = 0.3;
|
||||||
|
controls.minDistance = 1.6;
|
||||||
|
controls.maxDistance = 118.32;
|
||||||
|
controls.keys = [65, 83, 68]; // ASD
|
||||||
|
controls.dynamicDampingFactor = 0.2;
|
||||||
|
|
||||||
|
|
||||||
|
webglEl.appendChild(renderer.domElement);
|
||||||
|
|
||||||
|
render();
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
controls.update();
|
||||||
|
if (object) object.rotation.y += 0.0005;
|
||||||
|
requestAnimationFrame(render);
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createStars(radius, segments) {
|
||||||
|
return new THREE.Mesh(
|
||||||
|
new THREE.SphereGeometry(radius, segments, segments),
|
||||||
|
new THREE.MeshBasicMaterial({
|
||||||
|
map: new THREE.TextureLoader().load('./assets3d/starfield'),
|
||||||
|
side: THREE.BackSide
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(onDocumentMouseMove, 1000);
|
||||||
|
|
||||||
|
var raycaster = new THREE.Raycaster();
|
||||||
|
var mouse = new THREE.Vector2();
|
||||||
|
var lastView = [0, 0];
|
||||||
|
const coorbox = document.getElementById("coorbox");
|
||||||
|
function onDocumentMouseMove(event) {
|
||||||
|
if (!object) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event) {
|
||||||
|
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
|
||||||
|
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
|
||||||
|
} else {
|
||||||
|
mouse.x = 0.0;
|
||||||
|
mouse.y = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
raycaster.setFromCamera( mouse, camera );
|
||||||
|
var intersects = raycaster.intersectObject( object );
|
||||||
|
|
||||||
|
const elem = document.getElementsByTagName("BODY")[0];
|
||||||
|
if(intersects.length > 0) {
|
||||||
|
const { x, y } = intersects[0].uv;
|
||||||
|
const xabs = Math.floor((x - 0.5) * canvasSize);
|
||||||
|
const yabs = Math.floor((0.5 - y) * canvasSize);
|
||||||
|
//console.log(`On ${xabs} / ${yabs} cam: ${camera.position.z}`);
|
||||||
|
coorbox.innerHTML = `(${xabs}, ${yabs})`;
|
||||||
|
elem.style.cursor = 'move';
|
||||||
|
} else {
|
||||||
|
elem.style.cursor = 'default';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDocumentDblClick(event) {
|
||||||
|
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
|
||||||
|
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
|
||||||
|
|
||||||
|
raycaster.setFromCamera( mouse, camera );
|
||||||
|
var intersects = raycaster.intersectObject( object );
|
||||||
|
|
||||||
|
if(intersects.length > 0) {
|
||||||
|
const { x, y } = intersects[0].uv;
|
||||||
|
const xabs = Math.round((x - 0.5) * canvasSize);
|
||||||
|
const yabs = Math.round((0.5 - y) * canvasSize);
|
||||||
|
window.location.href = `./#${canvasIdent},${xabs},${yabs},0`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('mousemove', onDocumentMouseMove, false);
|
||||||
|
document.addEventListener('dblclick', onDocumentDblClick, false);
|
||||||
|
}());
|
||||||
|
|
29
src/web.js
|
@ -24,6 +24,8 @@ import {
|
||||||
admintools,
|
admintools,
|
||||||
resetPassword,
|
resetPassword,
|
||||||
} from './routes';
|
} from './routes';
|
||||||
|
import globeHtml from './components/Globe';
|
||||||
|
|
||||||
import { SECOND, MONTH } from './core/constants';
|
import { SECOND, MONTH } from './core/constants';
|
||||||
import { PORT, ASSET_SERVER, DISCORD_INVITE } from './core/config';
|
import { PORT, ASSET_SERVER, DISCORD_INVITE } from './core/config';
|
||||||
|
|
||||||
|
@ -107,12 +109,36 @@ app.get('/chunks/:c([0-9]+)/:x([0-9]+)/:y([0-9]+).bmp', chunks);
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
app.use('/admintools', admintools);
|
app.use('/admintools', admintools);
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Password Reset Link
|
// Password Reset Link
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
app.use('/reset_password', resetPassword);
|
app.use('/reset_password', resetPassword);
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// 3D Globe
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
const globeEtag = etag(
|
||||||
|
`${assets.globe.js}`,
|
||||||
|
{ weak: true },
|
||||||
|
);
|
||||||
|
app.get('/globe', async (req, res) => {
|
||||||
|
res.set({
|
||||||
|
'Cache-Control': `private, max-age=${15 * 60}`, // seconds
|
||||||
|
'Content-Type': 'text/html; charset=utf-8',
|
||||||
|
ETag: indexEtag,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (req.headers['if-none-match'] === indexEtag) {
|
||||||
|
res.status(304).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).send(globeHtml);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Register server-side rendering middleware
|
// Register server-side rendering middleware
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
@ -136,6 +162,7 @@ const indexEtag = etag(
|
||||||
app.get('/', async (req, res) => {
|
app.get('/', async (req, res) => {
|
||||||
res.set({
|
res.set({
|
||||||
'Cache-Control': `private, max-age=${15 * 60}`, // seconds
|
'Cache-Control': `private, max-age=${15 * 60}`, // seconds
|
||||||
|
'Content-Type': 'text/html; charset=utf-8',
|
||||||
ETag: indexEtag,
|
ETag: indexEtag,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -153,7 +180,7 @@ app.get('/', async (req, res) => {
|
||||||
const html = ReactDOM.renderToStaticMarkup(<Html {...htmldata} />);
|
const html = ReactDOM.renderToStaticMarkup(<Html {...htmldata} />);
|
||||||
const index = `<!doctype html>${html}`;
|
const index = `<!doctype html>${html}`;
|
||||||
|
|
||||||
res.send(index);
|
res.status(200).send(index);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -222,6 +222,7 @@ const clientConfig = {
|
||||||
|
|
||||||
entry: {
|
entry: {
|
||||||
client: ['./src/client.js'],
|
client: ['./src/client.js'],
|
||||||
|
globe: ['./src/globe.js'],
|
||||||
},
|
},
|
||||||
|
|
||||||
output: {
|
output: {
|
||||||
|
@ -262,7 +263,7 @@ const clientConfig = {
|
||||||
|
|
||||||
vendor: {
|
vendor: {
|
||||||
name: "vendor",
|
name: "vendor",
|
||||||
chunks: chunk => chunk.name !== "client",
|
chunks: chunk => chunk.name === "client",
|
||||||
test: /node_modules/,
|
test: /node_modules/,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|