forked from ppfun/pixelplanet
Add 3d voxel canvas
Add Canvas Selection Menu Improve Rendering Move ChunkRGB into seperate file
This commit is contained in:
parent
88990bf454
commit
caf08ee32d
BIN
public/preview0.png
Normal file
BIN
public/preview0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
public/preview1.png
Normal file
BIN
public/preview1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
BIN
public/preview2.png
Normal file
BIN
public/preview2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -1,21 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>three.js webgl - interactive - voxel painter</title>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
|
||||||
<link type="text/css" rel="stylesheet" href="main.css">
|
|
||||||
<script src="assets/voxel.js"></script>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #08f;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -8,12 +8,6 @@ import type {
|
||||||
import type { Cell } from '../core/Cell';
|
import type { Cell } from '../core/Cell';
|
||||||
import type { ColorIndex } from '../core/Palette';
|
import type { ColorIndex } from '../core/Palette';
|
||||||
|
|
||||||
import { loadImage } from '../ui/loadImage';
|
|
||||||
import {
|
|
||||||
getColorIndexOfPixel,
|
|
||||||
} from '../core/utils';
|
|
||||||
|
|
||||||
|
|
||||||
export function sweetAlert(
|
export function sweetAlert(
|
||||||
title: string,
|
title: string,
|
||||||
text: string,
|
text: string,
|
||||||
|
@ -228,7 +222,6 @@ export function requestPlacePixel(
|
||||||
y,
|
y,
|
||||||
clr: color,
|
clr: color,
|
||||||
token,
|
token,
|
||||||
a: x + y + 8,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch(setPlaceAllowed(false));
|
dispatch(setPlaceAllowed(false));
|
||||||
|
@ -295,9 +288,7 @@ export function tryPlacePixel(
|
||||||
? state.gui.selectedColor
|
? state.gui.selectedColor
|
||||||
: color;
|
: color;
|
||||||
|
|
||||||
if (getColorIndexOfPixel(getState(), coordinates) !== selectedColor) {
|
|
||||||
dispatch(requestPlacePixel(canvasId, coordinates, selectedColor));
|
dispatch(requestPlacePixel(canvasId, coordinates, selectedColor));
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,37 +367,23 @@ export function zoomOut(zoompoint): ThunkAction {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestBigChunk(center: Cell): Action {
|
export function requestBigChunk(center: Cell): Action {
|
||||||
return {
|
return {
|
||||||
type: 'REQUEST_BIG_CHUNK',
|
type: 'REQUEST_BIG_CHUNK',
|
||||||
center,
|
center,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function receiveBigChunk(
|
export function receiveBigChunk(
|
||||||
center: Cell,
|
center: Cell,
|
||||||
arrayBuffer: ArrayBuffer,
|
|
||||||
): Action {
|
): Action {
|
||||||
return {
|
return {
|
||||||
type: 'RECEIVE_BIG_CHUNK',
|
type: 'RECEIVE_BIG_CHUNK',
|
||||||
center,
|
center,
|
||||||
arrayBuffer,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function receiveImageTile(
|
export function receiveBigChunkFailure(center: Cell, error: Error): Action {
|
||||||
center: Cell,
|
|
||||||
tile: Image,
|
|
||||||
): Action {
|
|
||||||
return {
|
|
||||||
type: 'RECEIVE_IMAGE_TILE',
|
|
||||||
center,
|
|
||||||
tile,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function receiveBigChunkFailure(center: Cell, error: Error): Action {
|
|
||||||
return {
|
return {
|
||||||
type: 'RECEIVE_BIG_CHUNK_FAILURE',
|
type: 'RECEIVE_BIG_CHUNK_FAILURE',
|
||||||
center,
|
center,
|
||||||
|
@ -414,74 +391,6 @@ function receiveBigChunkFailure(center: Cell, error: Error): Action {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchTile(canvasId, center: Cell): PromiseAction {
|
|
||||||
const [cz, cx, cy] = center;
|
|
||||||
|
|
||||||
return async (dispatch) => {
|
|
||||||
dispatch(requestBigChunk(center));
|
|
||||||
try {
|
|
||||||
const url = `/tiles/${canvasId}/${cz}/${cx}/${cy}.png`;
|
|
||||||
const img = await loadImage(url);
|
|
||||||
dispatch(receiveImageTile(center, img));
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(receiveBigChunkFailure(center, error));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchHistoricalChunk(
|
|
||||||
canvasId: number,
|
|
||||||
center: Cell,
|
|
||||||
historicalDate: string,
|
|
||||||
historicalTime: string,
|
|
||||||
): PromiseAction {
|
|
||||||
const [cx, cy] = center;
|
|
||||||
|
|
||||||
return async (dispatch) => {
|
|
||||||
let url = `${window.backupurl}/${historicalDate}/`;
|
|
||||||
let zkey;
|
|
||||||
if (historicalTime) {
|
|
||||||
// incremential tiles
|
|
||||||
zkey = `${historicalDate}${historicalTime}`;
|
|
||||||
url += `${canvasId}/${historicalTime}/${cx}/${cy}.png`;
|
|
||||||
} else {
|
|
||||||
// full tiles
|
|
||||||
zkey = historicalDate;
|
|
||||||
url += `${canvasId}/tiles/${cx}/${cy}.png`;
|
|
||||||
}
|
|
||||||
const keyValues = [zkey, cx, cy];
|
|
||||||
dispatch(requestBigChunk(keyValues));
|
|
||||||
try {
|
|
||||||
const img = await loadImage(url);
|
|
||||||
dispatch(receiveImageTile(keyValues, img));
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(receiveBigChunkFailure(keyValues, error));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchChunk(canvasId, center: Cell): PromiseAction {
|
|
||||||
const [, cx, cy] = center;
|
|
||||||
|
|
||||||
return async (dispatch) => {
|
|
||||||
dispatch(requestBigChunk(center));
|
|
||||||
try {
|
|
||||||
const url = `/chunks/${canvasId}/${cx}/${cy}.bmp`;
|
|
||||||
const response = await fetch(url);
|
|
||||||
if (response.ok) {
|
|
||||||
const arrayBuffer = await response.arrayBuffer();
|
|
||||||
dispatch(receiveBigChunk(center, arrayBuffer));
|
|
||||||
} else {
|
|
||||||
const error = new Error('Network response was not ok.');
|
|
||||||
dispatch(receiveBigChunkFailure(center, error));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(receiveBigChunkFailure(center, error));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function receiveCoolDown(
|
export function receiveCoolDown(
|
||||||
waitSeconds: number,
|
waitSeconds: number,
|
||||||
): Action {
|
): Action {
|
||||||
|
@ -491,7 +400,6 @@ export function receiveCoolDown(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function receivePixelUpdate(
|
export function receivePixelUpdate(
|
||||||
i: number,
|
i: number,
|
||||||
j: number,
|
j: number,
|
||||||
|
@ -678,6 +586,10 @@ export function showHelpModal(): Action {
|
||||||
return showModal('HELP');
|
return showModal('HELP');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function showCanvasSelectionModal(): Action {
|
||||||
|
return showModal('CANVAS_SELECTION');
|
||||||
|
}
|
||||||
|
|
||||||
export function showChatModal(): Action {
|
export function showChatModal(): Action {
|
||||||
if (window.innerWidth > 604) { return toggleChatBox(); }
|
if (window.innerWidth > 604) { return toggleChatBox(); }
|
||||||
return showModal('CHAT');
|
return showModal('CHAT');
|
||||||
|
@ -714,10 +626,3 @@ export function urlChange(): PromiseAction {
|
||||||
dispatch(reloadUrl());
|
dispatch(reloadUrl());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function switchCanvas(canvasId: number): PromiseAction {
|
|
||||||
return async (dispatch) => {
|
|
||||||
await dispatch(selectCanvas(canvasId));
|
|
||||||
dispatch(onViewFinishChange());
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -41,8 +41,7 @@ export type Action =
|
||||||
| { type: 'SET_VIEW_COORDINATES', view: Cell }
|
| { type: 'SET_VIEW_COORDINATES', view: Cell }
|
||||||
| { type: 'SET_SCALE', scale: number, zoompoint: Cell }
|
| { type: 'SET_SCALE', scale: number, zoompoint: Cell }
|
||||||
| { type: 'REQUEST_BIG_CHUNK', center: Cell }
|
| { type: 'REQUEST_BIG_CHUNK', center: Cell }
|
||||||
| { type: 'RECEIVE_BIG_CHUNK', center: Cell, arrayBuffer: ArrayBuffer }
|
| { type: 'RECEIVE_BIG_CHUNK', center: Cell }
|
||||||
| { type: 'RECEIVE_IMAGE_TILE', center: Cell, tile: Image }
|
|
||||||
| { type: 'RECEIVE_BIG_CHUNK_FAILURE', center: Cell, error: Error }
|
| { type: 'RECEIVE_BIG_CHUNK_FAILURE', center: Cell, error: Error }
|
||||||
| { type: 'RECEIVE_COOLDOWN', waitSeconds: number }
|
| { type: 'RECEIVE_COOLDOWN', waitSeconds: number }
|
||||||
| { type: 'RECEIVE_PIXEL_UPDATE',
|
| { type: 'RECEIVE_PIXEL_UPDATE',
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"0": {
|
"0": {
|
||||||
"ident":"d",
|
"ident":"d",
|
||||||
|
"title": "Earth",
|
||||||
"colors": [
|
"colors": [
|
||||||
[ 202, 227, 255 ],
|
[ 202, 227, 255 ],
|
||||||
[ 255, 255, 255 ],
|
[ 255, 255, 255 ],
|
||||||
|
@ -41,10 +42,12 @@
|
||||||
"pcd" : 7000,
|
"pcd" : 7000,
|
||||||
"cds": 60000,
|
"cds": 60000,
|
||||||
"req": -1,
|
"req": -1,
|
||||||
"sd": "2020-01-08"
|
"sd": "2020-01-08",
|
||||||
|
"desc": "Our main canvas, a huge map of the world. Place everywhere you like"
|
||||||
},
|
},
|
||||||
"1": {
|
"1": {
|
||||||
"ident": "m",
|
"ident": "m",
|
||||||
|
"title": "Moon",
|
||||||
"colors" : [
|
"colors" : [
|
||||||
[ 49, 46, 47 ],
|
[ 49, 46, 47 ],
|
||||||
[ 99, 92, 90 ],
|
[ 99, 92, 90 ],
|
||||||
|
@ -85,6 +88,54 @@
|
||||||
"pcd": 15000,
|
"pcd": 15000,
|
||||||
"cds": 900000,
|
"cds": 900000,
|
||||||
"req": 8000,
|
"req": 8000,
|
||||||
"sd": "2020-01-08"
|
"sd": "2020-01-08",
|
||||||
|
"desc": "Moon canvas with a pastel tone palette and black background"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"ident":"v",
|
||||||
|
"title": "3D Canvas",
|
||||||
|
"colors": [
|
||||||
|
[ 202, 227, 255 ],
|
||||||
|
[ 255, 255, 255 ],
|
||||||
|
[ 255, 255, 255 ],
|
||||||
|
[ 228, 228, 228 ],
|
||||||
|
[ 196, 196, 196 ],
|
||||||
|
[ 136, 136, 136 ],
|
||||||
|
[ 78, 78, 78 ],
|
||||||
|
[ 0, 0, 0 ],
|
||||||
|
[ 244, 179, 174 ],
|
||||||
|
[ 255, 167, 209 ],
|
||||||
|
[ 255, 84, 178 ],
|
||||||
|
[ 255, 101, 101 ],
|
||||||
|
[ 229, 0, 0 ],
|
||||||
|
[ 154, 0, 0 ],
|
||||||
|
[ 254, 164, 96 ],
|
||||||
|
[ 229, 149, 0 ],
|
||||||
|
[ 160, 106, 66 ],
|
||||||
|
[ 96, 64, 40 ],
|
||||||
|
[ 245, 223, 176 ],
|
||||||
|
[ 255, 248, 137 ],
|
||||||
|
[ 229, 217, 0 ],
|
||||||
|
[ 148, 224, 68 ],
|
||||||
|
[ 2, 190, 1 ],
|
||||||
|
[ 104, 131, 56 ],
|
||||||
|
[ 0, 101, 19 ],
|
||||||
|
[ 202, 227, 255 ],
|
||||||
|
[ 0, 211, 221 ],
|
||||||
|
[ 0, 131, 199 ],
|
||||||
|
[ 0, 0, 234 ],
|
||||||
|
[ 25, 25, 115 ],
|
||||||
|
[ 207, 110, 228 ],
|
||||||
|
[ 130, 0, 128 ]
|
||||||
|
],
|
||||||
|
"alpha": 0,
|
||||||
|
"size": 1024,
|
||||||
|
"v": true,
|
||||||
|
"bcd": 2000,
|
||||||
|
"pcd" : 2000,
|
||||||
|
"cds": 60000,
|
||||||
|
"req": 0,
|
||||||
|
"sd": "2020-01-08",
|
||||||
|
"desc": "Test 3D canvas. Changes are not saved."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
215
src/client.js
215
src/client.js
|
@ -4,208 +4,39 @@ import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import fetch from 'isomorphic-fetch'; // TODO put in the beggining with webpack!
|
import fetch from 'isomorphic-fetch'; // TODO put in the beggining with webpack!
|
||||||
import Hammer from 'hammerjs';
|
|
||||||
|
|
||||||
import './components/font.css';
|
import './components/font.css';
|
||||||
|
|
||||||
|
// import initAds, { requestAds } from './ui/ads';
|
||||||
|
import onKeyPress from './controls/keypress';
|
||||||
import {
|
import {
|
||||||
screenToWorld,
|
|
||||||
getColorIndexOfPixel,
|
|
||||||
} from './core/utils';
|
|
||||||
|
|
||||||
import type { State } from './reducers';
|
|
||||||
import initAds, { requestAds } from './ui/ads';
|
|
||||||
import {
|
|
||||||
tryPlacePixel,
|
|
||||||
setHover,
|
|
||||||
unsetHover,
|
|
||||||
setViewCoordinates,
|
|
||||||
setScale,
|
|
||||||
zoomIn,
|
|
||||||
zoomOut,
|
|
||||||
receivePixelUpdate,
|
receivePixelUpdate,
|
||||||
receiveCoolDown,
|
receiveCoolDown,
|
||||||
fetchMe,
|
fetchMe,
|
||||||
fetchStats,
|
fetchStats,
|
||||||
initTimer,
|
initTimer,
|
||||||
urlChange,
|
urlChange,
|
||||||
onViewFinishChange,
|
|
||||||
receiveOnline,
|
receiveOnline,
|
||||||
receiveChatMessage,
|
receiveChatMessage,
|
||||||
receiveChatHistory,
|
receiveChatHistory,
|
||||||
selectColor,
|
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import store from './ui/store';
|
import store from './ui/store';
|
||||||
|
|
||||||
import onKeyPress from './ui/keypress';
|
|
||||||
|
|
||||||
import App from './components/App';
|
import App from './components/App';
|
||||||
|
|
||||||
import renderer from './ui/Renderer';
|
import { initRenderer, getRenderer } from './ui/renderer';
|
||||||
import ProtocolClient from './socket/ProtocolClient';
|
import ProtocolClient from './socket/ProtocolClient';
|
||||||
|
|
||||||
window.addEventListener('keydown', onKeyPress, false);
|
function init() {
|
||||||
|
initRenderer(store, false);
|
||||||
|
|
||||||
function initViewport() {
|
|
||||||
const canvas = document.getElementById('gameWindow');
|
|
||||||
|
|
||||||
const viewport = canvas;
|
|
||||||
viewport.width = window.innerWidth;
|
|
||||||
viewport.height = window.innerHeight;
|
|
||||||
|
|
||||||
// track hover
|
|
||||||
viewport.onmousemove = ({ clientX, clientY }: MouseEvent) => {
|
|
||||||
store.dispatch(setHover([clientX, clientY]));
|
|
||||||
};
|
|
||||||
viewport.onmouseout = () => {
|
|
||||||
store.dispatch(unsetHover());
|
|
||||||
};
|
|
||||||
viewport.onwheel = ({ deltaY }: WheelEvent) => {
|
|
||||||
const state = store.getState();
|
|
||||||
const { hover } = state.gui;
|
|
||||||
let zoompoint = null;
|
|
||||||
if (hover) {
|
|
||||||
zoompoint = screenToWorld(state, viewport, hover);
|
|
||||||
}
|
|
||||||
if (deltaY < 0) {
|
|
||||||
store.dispatch(zoomIn(zoompoint));
|
|
||||||
}
|
|
||||||
if (deltaY > 0) {
|
|
||||||
store.dispatch(zoomOut(zoompoint));
|
|
||||||
}
|
|
||||||
store.dispatch(onViewFinishChange());
|
|
||||||
};
|
|
||||||
viewport.onauxclick = ({ which, clientX, clientY }: MouseEvent) => {
|
|
||||||
// middle mouse button
|
|
||||||
if (which !== 2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const state = store.getState();
|
|
||||||
if (state.canvas.scale < 3) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const coords = screenToWorld(state, viewport, [clientX, clientY]);
|
|
||||||
const clrIndex = getColorIndexOfPixel(state, coords);
|
|
||||||
if (clrIndex === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
store.dispatch(selectColor(clrIndex));
|
|
||||||
};
|
|
||||||
|
|
||||||
// fingers controls on touch
|
|
||||||
const hammertime = new Hammer(viewport);
|
|
||||||
hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL });
|
|
||||||
hammertime.get('swipe').set({ direction: Hammer.DIRECTION_ALL });
|
|
||||||
// Zoom-in Zoom-out in touch devices
|
|
||||||
hammertime.get('pinch').set({ enable: true });
|
|
||||||
|
|
||||||
hammertime.on('tap', ({ center }) => {
|
|
||||||
const state = store.getState();
|
|
||||||
const { autoZoomIn } = state.gui;
|
|
||||||
const { placeAllowed } = state.user;
|
|
||||||
|
|
||||||
const {
|
|
||||||
scale,
|
|
||||||
isHistoricalView,
|
|
||||||
} = state.canvas;
|
|
||||||
if (isHistoricalView) return;
|
|
||||||
|
|
||||||
const { x, y } = center;
|
|
||||||
const cell = screenToWorld(state, viewport, [x, y]);
|
|
||||||
|
|
||||||
if (autoZoomIn && scale < 8) {
|
|
||||||
store.dispatch(setViewCoordinates(cell));
|
|
||||||
store.dispatch(setScale(12));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't allow placing of pixel just on low zoomlevels
|
|
||||||
if (scale < 3) return;
|
|
||||||
|
|
||||||
if (!placeAllowed) return;
|
|
||||||
|
|
||||||
// dirty trick: to fetch only before multiple 3 AND on user action
|
|
||||||
// if (pixelsPlaced % 3 === 0) requestAds();
|
|
||||||
|
|
||||||
// TODO assert only one finger
|
|
||||||
store.dispatch(tryPlacePixel(cell));
|
|
||||||
});
|
|
||||||
|
|
||||||
const initialState: State = store.getState();
|
|
||||||
[window.lastPosX, window.lastPosY] = initialState.canvas.view;
|
|
||||||
let lastScale = initialState.canvas.scale;
|
|
||||||
hammertime.on(
|
|
||||||
'panstart pinchstart pan pinch panend pinchend',
|
|
||||||
({
|
|
||||||
type, deltaX, deltaY, scale,
|
|
||||||
}) => {
|
|
||||||
viewport.style.cursor = 'move'; // like google maps
|
|
||||||
const { scale: viewportScale } = store.getState().canvas;
|
|
||||||
|
|
||||||
// pinch start
|
|
||||||
if (type === 'pinchstart') {
|
|
||||||
store.dispatch(unsetHover());
|
|
||||||
lastScale = viewportScale;
|
|
||||||
}
|
|
||||||
|
|
||||||
// panstart
|
|
||||||
if (type === 'panstart') {
|
|
||||||
store.dispatch(unsetHover());
|
|
||||||
const { view: initView } = store.getState().canvas;
|
|
||||||
[window.lastPosX, window.lastPosY] = initView;
|
|
||||||
}
|
|
||||||
|
|
||||||
// pinch
|
|
||||||
if (type === 'pinch') {
|
|
||||||
store.dispatch(setScale(lastScale * scale));
|
|
||||||
}
|
|
||||||
|
|
||||||
// pan
|
|
||||||
store.dispatch(setViewCoordinates([
|
|
||||||
window.lastPosX - (deltaX / viewportScale),
|
|
||||||
window.lastPosY - (deltaY / viewportScale),
|
|
||||||
]));
|
|
||||||
|
|
||||||
// pinch end
|
|
||||||
if (type === 'pinchend') {
|
|
||||||
lastScale = viewportScale;
|
|
||||||
}
|
|
||||||
|
|
||||||
// panend
|
|
||||||
if (type === 'panend') {
|
|
||||||
store.dispatch(onViewFinishChange());
|
|
||||||
const { view } = store.getState().canvas;
|
|
||||||
[window.lastPosX, window.lastPosY] = view;
|
|
||||||
viewport.style.cursor = 'auto';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return viewport;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
ReactDOM.render(
|
|
||||||
<Provider store={store}>
|
|
||||||
<App />
|
|
||||||
</Provider>,
|
|
||||||
document.getElementById('app'),
|
|
||||||
);
|
|
||||||
|
|
||||||
const viewport = initViewport();
|
|
||||||
renderer.setViewport(viewport, store);
|
|
||||||
|
|
||||||
ProtocolClient.on('pixelUpdate', ({
|
ProtocolClient.on('pixelUpdate', ({
|
||||||
i, j, offset, color,
|
i, j, offset, color,
|
||||||
}) => {
|
}) => {
|
||||||
store.dispatch(receivePixelUpdate(i, j, offset, color));
|
store.dispatch(receivePixelUpdate(i, j, offset, color));
|
||||||
// render updated pixel
|
|
||||||
renderer.renderPixel(i, j, offset, color);
|
|
||||||
});
|
});
|
||||||
ProtocolClient.on('cooldownPacket', (waitSeconds) => {
|
ProtocolClient.on('cooldownPacket', (waitSeconds) => {
|
||||||
console.log(`Received CoolDown ${waitSeconds}`);
|
|
||||||
store.dispatch(receiveCoolDown(waitSeconds));
|
store.dispatch(receiveCoolDown(waitSeconds));
|
||||||
});
|
});
|
||||||
ProtocolClient.on('onlineCounter', ({ online }) => {
|
ProtocolClient.on('onlineCounter', ({ online }) => {
|
||||||
|
@ -221,40 +52,36 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
store.dispatch(fetchMe());
|
store.dispatch(fetchMe());
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
viewport.width = window.innerWidth;
|
|
||||||
viewport.height = window.innerHeight;
|
|
||||||
renderer.forceNextRender = true;
|
|
||||||
});
|
|
||||||
window.addEventListener('hashchange', () => {
|
window.addEventListener('hashchange', () => {
|
||||||
store.dispatch(urlChange());
|
store.dispatch(urlChange());
|
||||||
});
|
});
|
||||||
|
|
||||||
store.subscribe(() => {
|
|
||||||
// const state: State = store.getState();
|
|
||||||
// this gets executed when store changes
|
|
||||||
});
|
|
||||||
|
|
||||||
store.dispatch(initTimer());
|
store.dispatch(initTimer());
|
||||||
|
|
||||||
window.animationLoop = function animationLoop() {
|
|
||||||
renderer.render(viewport);
|
|
||||||
window.requestAnimationFrame(window.animationLoop);
|
|
||||||
}
|
|
||||||
window.animationLoop();
|
|
||||||
window.store = store;
|
|
||||||
|
|
||||||
store.dispatch(fetchMe());
|
store.dispatch(fetchMe());
|
||||||
ProtocolClient.connect();
|
ProtocolClient.connect();
|
||||||
|
|
||||||
store.dispatch(fetchStats());
|
store.dispatch(fetchStats());
|
||||||
setInterval(() => { store.dispatch(fetchStats()); }, 300000);
|
setInterval(() => { store.dispatch(fetchStats()); }, 300000);
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
ReactDOM.render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<App />
|
||||||
|
</Provider>,
|
||||||
|
document.getElementById('app'),
|
||||||
|
);
|
||||||
|
|
||||||
|
document.addEventListener('keydown', onKeyPress, false);
|
||||||
|
|
||||||
// garbage collection
|
// garbage collection
|
||||||
function runGC() {
|
function runGC() {
|
||||||
const state: State = store.getState();
|
const renderer = getRenderer();
|
||||||
const { chunks } = state.canvas;
|
|
||||||
|
|
||||||
|
const chunks = renderer.getAllChunks();
|
||||||
|
if (chunks) {
|
||||||
const curTime = Date.now();
|
const curTime = Date.now();
|
||||||
let cnt = 0;
|
let cnt = 0;
|
||||||
chunks.forEach((value, key) => {
|
chunks.forEach((value, key) => {
|
||||||
|
@ -269,7 +96,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log('Garbage collection cleaned', cnt, 'chunks');
|
console.log('Garbage collection cleaned', cnt, 'chunks');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
setInterval(runGC, 300000);
|
setInterval(runGC, 300000);
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,32 +4,25 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { IconContext } from 'react-icons';
|
import { IconContext } from 'react-icons';
|
||||||
|
|
||||||
import type { State } from '../reducers';
|
|
||||||
import CoolDownBox from './CoolDownBox';
|
|
||||||
import NotifyBox from './NotifyBox';
|
|
||||||
import CoordinatesBox from './CoordinatesBox';
|
import CoordinatesBox from './CoordinatesBox';
|
||||||
import GlobeButton from './GlobeButton';
|
|
||||||
import CanvasSwitchButton from './CanvasSwitchButton';
|
import CanvasSwitchButton from './CanvasSwitchButton';
|
||||||
import OnlineBox from './OnlineBox';
|
import OnlineBox from './OnlineBox';
|
||||||
import PalselButton from './PalselButton';
|
|
||||||
import ChatButton from './ChatButton';
|
import ChatButton from './ChatButton';
|
||||||
import Palette from './Palette';
|
|
||||||
import ChatBox from './ChatBox';
|
import ChatBox from './ChatBox';
|
||||||
import Menu from './Menu';
|
import Menu from './Menu';
|
||||||
|
import UI from './UI';
|
||||||
import ReCaptcha from './ReCaptcha';
|
import ReCaptcha from './ReCaptcha';
|
||||||
import ExpandMenuButton from './ExpandMenuButton';
|
import ExpandMenuButton from './ExpandMenuButton';
|
||||||
import ModalRoot from './ModalRoot';
|
import ModalRoot from './ModalRoot';
|
||||||
import HistorySelect from './HistorySelect';
|
|
||||||
|
|
||||||
import baseCss from './base.tcss';
|
import baseCss from './base.tcss';
|
||||||
|
|
||||||
const App = ({ isHistoricalView }) => (
|
const App = () => (
|
||||||
<div>
|
<div>
|
||||||
|
{/* eslint-disable-next-line react/no-danger */}
|
||||||
<style dangerouslySetInnerHTML={{ __html: baseCss }} />
|
<style dangerouslySetInnerHTML={{ __html: baseCss }} />
|
||||||
<canvas id="gameWindow" />
|
|
||||||
<div id="outstreamContainer" />
|
<div id="outstreamContainer" />
|
||||||
<ReCaptcha />
|
<ReCaptcha />
|
||||||
<IconContext.Provider value={{ style: { verticalAlign: 'middle' } }}>
|
<IconContext.Provider value={{ style: { verticalAlign: 'middle' } }}>
|
||||||
|
@ -40,31 +33,10 @@ const App = ({ isHistoricalView }) => (
|
||||||
<OnlineBox />
|
<OnlineBox />
|
||||||
<CoordinatesBox />
|
<CoordinatesBox />
|
||||||
<ExpandMenuButton />
|
<ExpandMenuButton />
|
||||||
{
|
<UI />
|
||||||
(isHistoricalView)
|
|
||||||
? <HistorySelect />
|
|
||||||
: (
|
|
||||||
<div>
|
|
||||||
<PalselButton />
|
|
||||||
<Palette />
|
|
||||||
<GlobeButton />
|
|
||||||
<CoolDownBox />
|
|
||||||
<NotifyBox />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<ModalRoot />
|
<ModalRoot />
|
||||||
</IconContext.Provider>
|
</IconContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
function mapStateToProps(state: State) {
|
export default App;
|
||||||
const {
|
|
||||||
isHistoricalView,
|
|
||||||
} = state.canvas;
|
|
||||||
return {
|
|
||||||
isHistoricalView,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(App);
|
|
||||||
|
|
105
src/components/CanvasItem.jsx
Normal file
105
src/components/CanvasItem.jsx
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { THREE_CANVAS_HEIGHT } from '../core/constants';
|
||||||
|
import { selectCanvas } from '../actions';
|
||||||
|
|
||||||
|
|
||||||
|
const textStyle = {
|
||||||
|
color: 'hsla(218, 5%, 47%, .6)',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 500,
|
||||||
|
position: 'relative',
|
||||||
|
textAlign: 'inherit',
|
||||||
|
float: 'none',
|
||||||
|
margin: 2,
|
||||||
|
padding: 0,
|
||||||
|
overflow: 'auto',
|
||||||
|
};
|
||||||
|
|
||||||
|
const infoStyle = {
|
||||||
|
color: '#4f545c',
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: 500,
|
||||||
|
position: 'relative',
|
||||||
|
textAlign: 'inherit',
|
||||||
|
float: 'none',
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const titleStyle = {
|
||||||
|
color: '#4f545c',
|
||||||
|
marginLeft: 0,
|
||||||
|
marginRight: 10,
|
||||||
|
overflow: 'hidden',
|
||||||
|
wordWrap: 'break-word',
|
||||||
|
lineHeight: '24px',
|
||||||
|
fontSize: 16,
|
||||||
|
marginTop: 5,
|
||||||
|
marginBottom: 0,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonStyle = {
|
||||||
|
marginTop: 10,
|
||||||
|
marginBottom: 10,
|
||||||
|
border: '#c5c5c5',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
cursor: 'pointer',
|
||||||
|
};
|
||||||
|
|
||||||
|
const CanvasItem = ({ canvasId, canvas, changeCanvas }) => (
|
||||||
|
<div
|
||||||
|
style={buttonStyle}
|
||||||
|
onClick={() => { changeCanvas(canvasId); }}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<p style={textStyle}>
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
float: 'left', maxWidth: '20%', margin: 5, opacity: 0.5,
|
||||||
|
}}
|
||||||
|
alt="preview"
|
||||||
|
src={`/preview${canvasId}.png`}
|
||||||
|
/>
|
||||||
|
<span style={titleStyle}>{canvas.title}</span><br />
|
||||||
|
<span style={infoStyle}>{canvas.desc}</span><br />
|
||||||
|
Cooldown:
|
||||||
|
<span style={infoStyle}>
|
||||||
|
{(canvas.bcd !== canvas.pcd)
|
||||||
|
? <span> {canvas.bcd / 1000}s / {canvas.pcd / 1000}s</span>
|
||||||
|
: <span> {canvas.bcd / 1000}s</span>}
|
||||||
|
</span><br />
|
||||||
|
Stacking till
|
||||||
|
<span style={infoStyle}> {canvas.cds / 1000}s</span><br />
|
||||||
|
{(canvas.req !== -1) ? <span>Requirements:<br /></span> : null}
|
||||||
|
<span style={infoStyle}>
|
||||||
|
{(canvas.req !== -1) ? <span>User Account </span> : null}
|
||||||
|
{(canvas.req > 0) ? <span> and {canvas.req} Pixels set</span> : null}
|
||||||
|
</span><br />
|
||||||
|
Dimensions:
|
||||||
|
<span style={infoStyle}> {canvas.size} x {canvas.size}
|
||||||
|
{(canvas.v)
|
||||||
|
? <span> x {THREE_CANVAS_HEIGHT} Voxels</span>
|
||||||
|
: <span> Pixels</span>}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return {
|
||||||
|
changeCanvas(canvasId) {
|
||||||
|
dispatch(selectCanvas(canvasId));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(CanvasItem);
|
53
src/components/CanvasSelectModal.jsx
Normal file
53
src/components/CanvasSelectModal.jsx
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
// import FaFacebook from 'react-icons/lib/fa/facebook';
|
||||||
|
// import FaTwitter from 'react-icons/lib/fa/twitter';
|
||||||
|
// import FaRedditAlien from 'react-icons/lib/fa/reddit-alien';
|
||||||
|
|
||||||
|
import Modal from './Modal';
|
||||||
|
import CanvasItem from './CanvasItem';
|
||||||
|
|
||||||
|
import type { State } from '../reducers';
|
||||||
|
|
||||||
|
|
||||||
|
const textStyle = {
|
||||||
|
color: 'hsla(218, 5%, 47%, .6)',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 500,
|
||||||
|
position: 'relative',
|
||||||
|
textAlign: 'inherit',
|
||||||
|
float: 'none',
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
lineHeight: 'normal',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const CanvasSelectModal = ({ canvases }) => (
|
||||||
|
<Modal title="Canvas Selection">
|
||||||
|
<p style={{ textAlign: 'center' }}>
|
||||||
|
<p style={textStyle}>
|
||||||
|
Select the canvas you want to use.
|
||||||
|
Every canvas is unique and has different palettes,
|
||||||
|
cooldown and requirements.
|
||||||
|
</p>
|
||||||
|
{
|
||||||
|
Object.keys(canvases).map((canvasId) => (
|
||||||
|
<CanvasItem canvasId={canvasId} canvas={canvases[canvasId]} />
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
|
||||||
|
function mapStateToProps(state: State) {
|
||||||
|
const { canvases } = state.canvas;
|
||||||
|
return { canvases };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(CanvasSelectModal);
|
|
@ -5,16 +5,20 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { FaGlobe, FaGlobeAfrica } from 'react-icons/fa';
|
import { FaGlobe } from 'react-icons/fa';
|
||||||
|
|
||||||
import { switchCanvas } from '../actions';
|
import { showCanvasSelectionModal } from '../actions';
|
||||||
|
|
||||||
import type { State } from '../reducers';
|
import type { State } from '../reducers';
|
||||||
|
|
||||||
|
|
||||||
const CanvasSwitchButton = ({ canvasId, changeCanvas }) => (
|
const CanvasSwitchButton = ({ open }) => (
|
||||||
<div id="canvasbutton" className="actionbuttons" onClick={() => changeCanvas(canvasId)}>
|
<div
|
||||||
{(canvasId == 0) ? <FaGlobe /> : <FaGlobeAfrica />}
|
id="canvasbutton"
|
||||||
|
className="actionbuttons"
|
||||||
|
onClick={open}
|
||||||
|
>
|
||||||
|
<FaGlobe />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -25,12 +29,11 @@ function mapStateToProps(state: State) {
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
changeCanvas(canvasId) {
|
open() {
|
||||||
const newCanvasId = (canvasId == 0) ? 1 : 0;
|
dispatch(showCanvasSelectionModal());
|
||||||
dispatch(switchCanvas(newCanvasId));
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps,
|
export default connect(null,
|
||||||
mapDispatchToProps)(CanvasSwitchButton);
|
mapDispatchToProps)(CanvasSwitchButton);
|
||||||
|
|
|
@ -6,26 +6,24 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { screenToWorld } from '../core/utils';
|
|
||||||
import type { State } from '../reducers';
|
import type { State } from '../reducers';
|
||||||
|
|
||||||
|
|
||||||
function renderCoordinates([x, y]: Cell): string {
|
function renderCoordinates(cell): string {
|
||||||
return `(${x}, ${y})`;
|
return `(${cell.join(', ')})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO vaya chapuza, arreglalo un poco...
|
const CoordinatesBox = ({ view, hover }) => (
|
||||||
// TODO create viewport state
|
<div className="coorbox">{
|
||||||
const CoordinatesBox = ({ state, view, hover }) => (
|
renderCoordinates(hover
|
||||||
<div className="coorbox">{renderCoordinates(hover
|
|| view.map(Math.round))
|
||||||
? screenToWorld(state, document.getElementById('gameWindow'), hover)
|
}</div>
|
||||||
: view.map(Math.round))}</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
function mapStateToProps(state: State) {
|
function mapStateToProps(state: State) {
|
||||||
const { view } = state.canvas;
|
const { view } = state.canvas;
|
||||||
const { hover } = state.gui;
|
const { hover } = state.gui;
|
||||||
return { view, state, hover };
|
return { view, hover };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps)(CoordinatesBox);
|
export default connect(mapStateToProps)(CoordinatesBox);
|
||||||
|
|
|
@ -11,7 +11,6 @@ import LogInButton from './LogInButton';
|
||||||
import DownloadButton from './DownloadButton';
|
import DownloadButton from './DownloadButton';
|
||||||
import MinecraftTPButton from './MinecraftTPButton';
|
import MinecraftTPButton from './MinecraftTPButton';
|
||||||
import MinecraftButton from './MinecraftButton';
|
import MinecraftButton from './MinecraftButton';
|
||||||
import VoxelButton from './VoxelButton';
|
|
||||||
|
|
||||||
const Menu = ({
|
const Menu = ({
|
||||||
menuOpen, minecraftname, messages, canvasId,
|
menuOpen, minecraftname, messages, canvasId,
|
||||||
|
@ -22,7 +21,6 @@ const Menu = ({
|
||||||
{(menuOpen) ? <DownloadButton /> : null}
|
{(menuOpen) ? <DownloadButton /> : null}
|
||||||
{(menuOpen) ? <MinecraftButton /> : null}
|
{(menuOpen) ? <MinecraftButton /> : null}
|
||||||
{(menuOpen) ? <HelpButton /> : null}
|
{(menuOpen) ? <HelpButton /> : null}
|
||||||
{(menuOpen) ? <VoxelButton /> : null}
|
|
||||||
{(minecraftname && !messages.includes('not_mc_verified') && canvasId == 0) ? <MinecraftTPButton /> : null}
|
{(minecraftname && !messages.includes('not_mc_verified') && canvasId == 0) ? <MinecraftTPButton /> : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,6 +12,7 @@ import HelpModal from './HelpModal';
|
||||||
import SettingsModal from './SettingsModal';
|
import SettingsModal from './SettingsModal';
|
||||||
import UserAreaModal from './UserAreaModal';
|
import UserAreaModal from './UserAreaModal';
|
||||||
import RegisterModal from './RegisterModal';
|
import RegisterModal from './RegisterModal';
|
||||||
|
import CanvasSelectModal from './CanvasSelectModal';
|
||||||
import ChatModal from './ChatModal';
|
import ChatModal from './ChatModal';
|
||||||
import ForgotPasswordModal from './ForgotPasswordModal';
|
import ForgotPasswordModal from './ForgotPasswordModal';
|
||||||
import MinecraftModal from './MinecraftModal';
|
import MinecraftModal from './MinecraftModal';
|
||||||
|
@ -25,6 +26,7 @@ const MODAL_COMPONENTS = {
|
||||||
FORGOT_PASSWORD: ForgotPasswordModal,
|
FORGOT_PASSWORD: ForgotPasswordModal,
|
||||||
CHAT: ChatModal,
|
CHAT: ChatModal,
|
||||||
MINECRAFT: MinecraftModal,
|
MINECRAFT: MinecraftModal,
|
||||||
|
CANVAS_SELECTION: CanvasSelectModal,
|
||||||
/* other modals */
|
/* other modals */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,12 +10,19 @@ import type { State } from '../reducers';
|
||||||
|
|
||||||
let style = {};
|
let style = {};
|
||||||
function getStyle(notification) {
|
function getStyle(notification) {
|
||||||
if (notification) style = { backgroundColor: (notification >= 0) ? '#a9ffb0cc' : '#ffa9a9cc' };
|
if (notification) {
|
||||||
|
style = {
|
||||||
|
backgroundColor: (notification >= 0) ? '#a9ffb0cc' : '#ffa9a9cc',
|
||||||
|
};
|
||||||
|
}
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NotifyBox = ({ notification }) => (
|
const NotifyBox = ({ notification }) => (
|
||||||
<div className={(notification) ? 'notifyboxvis' : 'notifyboxhid'} style={getStyle(notification)}>
|
<div
|
||||||
|
className={(notification) ? 'notifyboxvis' : 'notifyboxhid'}
|
||||||
|
style={getStyle(notification)}
|
||||||
|
>
|
||||||
{notification}
|
{notification}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
42
src/components/UI.jsx
Normal file
42
src/components/UI.jsx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import type { State } from '../reducers';
|
||||||
|
import CoolDownBox from './CoolDownBox';
|
||||||
|
import NotifyBox from './NotifyBox';
|
||||||
|
import GlobeButton from './GlobeButton';
|
||||||
|
import PalselButton from './PalselButton';
|
||||||
|
import Palette from './Palette';
|
||||||
|
import HistorySelect from './HistorySelect';
|
||||||
|
|
||||||
|
|
||||||
|
const UI = ({ isHistoricalView }) => {
|
||||||
|
if (isHistoricalView) {
|
||||||
|
return <HistorySelect />;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PalselButton />
|
||||||
|
<Palette />
|
||||||
|
<GlobeButton />
|
||||||
|
<CoolDownBox />
|
||||||
|
<NotifyBox />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function mapStateToProps(state: State) {
|
||||||
|
const {
|
||||||
|
isHistoricalView,
|
||||||
|
} = state.canvas;
|
||||||
|
return {
|
||||||
|
isHistoricalView,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(UI);
|
|
@ -1,24 +0,0 @@
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import type { State } from '../reducers';
|
|
||||||
|
|
||||||
async function switchVoxel() {
|
|
||||||
await import(/* webpackChunkName: "voxel" */ '../voxel');
|
|
||||||
console.log("Chunk voxel loaded");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const VoxelButton = ({
|
|
||||||
canvasId, canvasIdent, canvasSize, view,
|
|
||||||
}) => (
|
|
||||||
<div id="voxelbutton" className="actionbuttons" onClick={switchVoxel}>
|
|
||||||
♠
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default VoxelButton;
|
|
File diff suppressed because it is too large
Load Diff
210
src/controls/PixelPainterControls.js
Normal file
210
src/controls/PixelPainterControls.js
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
/*
|
||||||
|
* Creates Viewport for 2D Canvas
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Hammer from 'hammerjs';
|
||||||
|
import keycode from 'keycode';
|
||||||
|
|
||||||
|
import {
|
||||||
|
tryPlacePixel,
|
||||||
|
setHover,
|
||||||
|
unsetHover,
|
||||||
|
setViewCoordinates,
|
||||||
|
setScale,
|
||||||
|
zoomIn,
|
||||||
|
zoomOut,
|
||||||
|
selectColor,
|
||||||
|
moveNorth,
|
||||||
|
moveWest,
|
||||||
|
moveSouth,
|
||||||
|
moveEast,
|
||||||
|
onViewFinishChange,
|
||||||
|
} from '../actions';
|
||||||
|
import {
|
||||||
|
screenToWorld,
|
||||||
|
} from '../core/utils';
|
||||||
|
|
||||||
|
let store = null;
|
||||||
|
|
||||||
|
function onKeyPress(event: KeyboardEvent) {
|
||||||
|
// ignore key presses if modal is open or chat is used
|
||||||
|
if (event.target.nodeName === 'INPUT'
|
||||||
|
|| event.target.nodeName === 'TEXTAREA'
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (keycode(event)) {
|
||||||
|
case 'up':
|
||||||
|
case 'w':
|
||||||
|
store.dispatch(moveNorth());
|
||||||
|
break;
|
||||||
|
case 'left':
|
||||||
|
case 'a':
|
||||||
|
store.dispatch(moveWest());
|
||||||
|
break;
|
||||||
|
case 'down':
|
||||||
|
case 's':
|
||||||
|
store.dispatch(moveSouth());
|
||||||
|
break;
|
||||||
|
case 'right':
|
||||||
|
case 'd':
|
||||||
|
store.dispatch(moveEast());
|
||||||
|
break;
|
||||||
|
/*
|
||||||
|
case 'space':
|
||||||
|
if ($viewport) $viewport.click();
|
||||||
|
return;
|
||||||
|
*/
|
||||||
|
case '+':
|
||||||
|
case 'e':
|
||||||
|
store.dispatch(zoomIn());
|
||||||
|
break;
|
||||||
|
case '-':
|
||||||
|
case 'q':
|
||||||
|
store.dispatch(zoomOut());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initControls(renderer, viewport: HTMLCanvasElement, curStore) {
|
||||||
|
store = curStore;
|
||||||
|
viewport.onmousemove = ({ clientX, clientY }: MouseEvent) => {
|
||||||
|
const state = store.getState();
|
||||||
|
const screenCoor = screenToWorld(state, viewport, [clientX, clientY]);
|
||||||
|
store.dispatch(setHover(screenCoor));
|
||||||
|
};
|
||||||
|
viewport.onmouseout = () => {
|
||||||
|
store.dispatch(unsetHover());
|
||||||
|
};
|
||||||
|
viewport.onwheel = ({ deltaY }: WheelEvent) => {
|
||||||
|
const state = store.getState();
|
||||||
|
const { hover } = state.gui;
|
||||||
|
let zoompoint = null;
|
||||||
|
if (hover) {
|
||||||
|
zoompoint = hover;
|
||||||
|
}
|
||||||
|
if (deltaY < 0) {
|
||||||
|
store.dispatch(zoomIn(zoompoint));
|
||||||
|
}
|
||||||
|
if (deltaY > 0) {
|
||||||
|
store.dispatch(zoomOut(zoompoint));
|
||||||
|
}
|
||||||
|
store.dispatch(onViewFinishChange());
|
||||||
|
};
|
||||||
|
viewport.onauxclick = ({ which, clientX, clientY }: MouseEvent) => {
|
||||||
|
// middle mouse button
|
||||||
|
if (which !== 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const state = store.getState();
|
||||||
|
if (state.canvas.scale < 3) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const coords = screenToWorld(state, viewport, [clientX, clientY]);
|
||||||
|
const clrIndex = renderer.getColorIndexOfPixel(...coords);
|
||||||
|
if (clrIndex === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
store.dispatch(selectColor(clrIndex));
|
||||||
|
};
|
||||||
|
|
||||||
|
// fingers controls on touch
|
||||||
|
const hammertime = new Hammer(viewport);
|
||||||
|
hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL });
|
||||||
|
hammertime.get('swipe').set({ direction: Hammer.DIRECTION_ALL });
|
||||||
|
// Zoom-in Zoom-out in touch devices
|
||||||
|
hammertime.get('pinch').set({ enable: true });
|
||||||
|
|
||||||
|
hammertime.on('tap', ({ center }) => {
|
||||||
|
const state = store.getState();
|
||||||
|
const { autoZoomIn, selectedColor } = state.gui;
|
||||||
|
const { placeAllowed } = state.user;
|
||||||
|
|
||||||
|
const {
|
||||||
|
scale,
|
||||||
|
isHistoricalView,
|
||||||
|
} = state.canvas;
|
||||||
|
if (isHistoricalView) return;
|
||||||
|
|
||||||
|
const { x, y } = center;
|
||||||
|
const cell = screenToWorld(state, viewport, [x, y]);
|
||||||
|
|
||||||
|
if (autoZoomIn && scale < 8) {
|
||||||
|
store.dispatch(setViewCoordinates(cell));
|
||||||
|
store.dispatch(setScale(12));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't allow placing of pixel just on low zoomlevels
|
||||||
|
if (scale < 3) return;
|
||||||
|
|
||||||
|
if (!placeAllowed) return;
|
||||||
|
|
||||||
|
// dirty trick: to fetch only before multiple 3 AND on user action
|
||||||
|
// if (pixelsPlaced % 3 === 0) requestAds();
|
||||||
|
|
||||||
|
if (selectedColor !== renderer.getColorIndexOfPixel(...cell)) {
|
||||||
|
store.dispatch(tryPlacePixel(cell));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const initialState: State = store.getState();
|
||||||
|
[window.lastPosX, window.lastPosY] = initialState.canvas.view;
|
||||||
|
let lastScale = initialState.canvas.scale;
|
||||||
|
hammertime.on(
|
||||||
|
'panstart pinchstart pan pinch panend pinchend',
|
||||||
|
({
|
||||||
|
type, deltaX, deltaY, scale,
|
||||||
|
}) => {
|
||||||
|
viewport.style.cursor = 'move'; // like google maps
|
||||||
|
const { scale: viewportScale } = store.getState().canvas;
|
||||||
|
|
||||||
|
// pinch start
|
||||||
|
if (type === 'pinchstart') {
|
||||||
|
store.dispatch(unsetHover());
|
||||||
|
lastScale = viewportScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// panstart
|
||||||
|
if (type === 'panstart') {
|
||||||
|
store.dispatch(unsetHover());
|
||||||
|
const { view: initView } = store.getState().canvas;
|
||||||
|
[window.lastPosX, window.lastPosY] = initView;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pinch
|
||||||
|
if (type === 'pinch') {
|
||||||
|
store.dispatch(setScale(lastScale * scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
// pan
|
||||||
|
store.dispatch(setViewCoordinates([
|
||||||
|
window.lastPosX - (deltaX / viewportScale),
|
||||||
|
window.lastPosY - (deltaY / viewportScale),
|
||||||
|
]));
|
||||||
|
|
||||||
|
// pinch end
|
||||||
|
if (type === 'pinchend') {
|
||||||
|
lastScale = viewportScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// panend
|
||||||
|
if (type === 'panend') {
|
||||||
|
store.dispatch(onViewFinishChange());
|
||||||
|
const { view } = store.getState().canvas;
|
||||||
|
[window.lastPosX, window.lastPosY] = view;
|
||||||
|
viewport.style.cursor = 'auto';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
document.addEventListener('keydown', onKeyPress, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeControls() {
|
||||||
|
document.removeEventListener('keydown', onKeyPress, false);
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
37
src/controls/keypress.js
Normal file
37
src/controls/keypress.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* keypress actions
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
import keycode from 'keycode';
|
||||||
|
|
||||||
|
import store from '../ui/store';
|
||||||
|
import {
|
||||||
|
toggleGrid,
|
||||||
|
togglePixelNotify,
|
||||||
|
toggleMute,
|
||||||
|
} from '../actions';
|
||||||
|
|
||||||
|
|
||||||
|
function onKeyPress(event: KeyboardEvent) {
|
||||||
|
// ignore key presses if modal is open or chat is used
|
||||||
|
if (event.target.nodeName === 'INPUT'
|
||||||
|
|| event.target.nodeName === 'TEXTAREA'
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (keycode(event)) {
|
||||||
|
case 'g':
|
||||||
|
store.dispatch(toggleGrid());
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
store.dispatch(togglePixelNotify());
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
store.dispatch(toggleMute());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default onKeyPress;
|
|
@ -40,15 +40,17 @@ export async function imageABGR2Canvas(
|
||||||
const canvasMinXY = -(canvas.size / 2);
|
const canvasMinXY = -(canvas.size / 2);
|
||||||
const imageData = new Uint32Array(data.buffer);
|
const imageData = new Uint32Array(data.buffer);
|
||||||
|
|
||||||
const [ucx, ucy] = getChunkOfPixel([x, y], canvas.size);
|
const [ucx, ucy] = getChunkOfPixel(canvas.size, x, y);
|
||||||
const [lcx, lcy] = getChunkOfPixel([(x + width), (y + height)], canvas.size);
|
const [lcx, lcy] = getChunkOfPixel(canvas.size, x + width, y + height);
|
||||||
|
|
||||||
logger.info(`Loading to chunks from ${ucx} / ${ucy} to ${lcx} / ${lcy} ...`);
|
logger.info(`Loading to chunks from ${ucx} / ${ucy} to ${lcx} / ${lcy} ...`);
|
||||||
let chunk;
|
let chunk;
|
||||||
for (let cx = ucx; cx <= lcx; cx += 1) {
|
for (let cx = ucx; cx <= lcx; cx += 1) {
|
||||||
for (let cy = ucy; cy <= lcy; cy += 1) {
|
for (let cy = ucy; cy <= lcy; cy += 1) {
|
||||||
chunk = await RedisCanvas.getChunk(cx, cy, canvasId);
|
chunk = await RedisCanvas.getChunk(cx, cy, canvasId);
|
||||||
chunk = (chunk) ? new Uint8Array(chunk) : new Uint8Array(TILE_SIZE * TILE_SIZE);
|
chunk = (chunk)
|
||||||
|
? new Uint8Array(chunk)
|
||||||
|
: new Uint8Array(TILE_SIZE * TILE_SIZE);
|
||||||
// offset of chunk in image
|
// offset of chunk in image
|
||||||
const cOffX = cx * TILE_SIZE + canvasMinXY - x;
|
const cOffX = cx * TILE_SIZE + canvasMinXY - x;
|
||||||
const cOffY = cy * TILE_SIZE + canvasMinXY - y;
|
const cOffY = cy * TILE_SIZE + canvasMinXY - y;
|
||||||
|
@ -60,7 +62,9 @@ export async function imageABGR2Canvas(
|
||||||
const clrY = cOffY + py;
|
const clrY = cOffY + py;
|
||||||
if (clrX >= 0 && clrY >= 0 && clrX < width && clrY < height) {
|
if (clrX >= 0 && clrY >= 0 && clrX < width && clrY < height) {
|
||||||
const clr = imageData[clrX + clrY * width];
|
const clr = imageData[clrX + clrY * width];
|
||||||
const clrIndex = (wipe) ? palette.abgr.indexOf(clr) : palette.abgr.indexOf(clr, 2);
|
const clrIndex = (wipe)
|
||||||
|
? palette.abgr.indexOf(clr)
|
||||||
|
: palette.abgr.indexOf(clr, 2);
|
||||||
if (~clrIndex) {
|
if (~clrIndex) {
|
||||||
const pixel = (protect) ? (clrIndex | 0x20) : clrIndex;
|
const pixel = (protect) ? (clrIndex | 0x20) : clrIndex;
|
||||||
chunk[cOff] = pixel;
|
chunk[cOff] = pixel;
|
||||||
|
@ -113,15 +117,17 @@ export async function imagemask2Canvas(
|
||||||
|
|
||||||
const imageData = new Uint8Array(data.buffer);
|
const imageData = new Uint8Array(data.buffer);
|
||||||
|
|
||||||
const [ucx, ucy] = getChunkOfPixel([x, y], canvas.size);
|
const [ucx, ucy] = getChunkOfPixel(canvas.size, x, y);
|
||||||
const [lcx, lcy] = getChunkOfPixel([(x + width), (y + height)], canvas.size);
|
const [lcx, lcy] = getChunkOfPixel(canvas.size, x + width, y + height);
|
||||||
|
|
||||||
logger.info(`Loading to chunks from ${ucx} / ${ucy} to ${lcx} / ${lcy} ...`);
|
logger.info(`Loading to chunks from ${ucx} / ${ucy} to ${lcx} / ${lcy} ...`);
|
||||||
let chunk;
|
let chunk;
|
||||||
for (let cx = ucx; cx <= lcx; cx += 1) {
|
for (let cx = ucx; cx <= lcx; cx += 1) {
|
||||||
for (let cy = ucy; cy <= lcy; cy += 1) {
|
for (let cy = ucy; cy <= lcy; cy += 1) {
|
||||||
chunk = await RedisCanvas.getChunk(cx, cy, canvasId);
|
chunk = await RedisCanvas.getChunk(cx, cy, canvasId);
|
||||||
chunk = (chunk) ? new Uint8Array(chunk) : new Uint8Array(TILE_SIZE * TILE_SIZE);
|
chunk = (chunk)
|
||||||
|
? new Uint8Array(chunk)
|
||||||
|
: new Uint8Array(TILE_SIZE * TILE_SIZE);
|
||||||
// offset of chunk in image
|
// offset of chunk in image
|
||||||
const cOffX = cx * TILE_SIZE + canvasMinXY - x;
|
const cOffX = cx * TILE_SIZE + canvasMinXY - x;
|
||||||
const cOffY = cy * TILE_SIZE + canvasMinXY - y;
|
const cOffY = cy * TILE_SIZE + canvasMinXY - y;
|
||||||
|
@ -133,7 +139,10 @@ export async function imagemask2Canvas(
|
||||||
const clrY = cOffY + py;
|
const clrY = cOffY + py;
|
||||||
if (clrX >= 0 && clrY >= 0 && clrX < width && clrY < height) {
|
if (clrX >= 0 && clrY >= 0 && clrX < width && clrY < height) {
|
||||||
let offset = (clrX + clrY * width) * 3;
|
let offset = (clrX + clrY * width) * 3;
|
||||||
if (!imageData[offset++] && !imageData[offset++] && !imageData[offset]) {
|
if (!imageData[offset++]
|
||||||
|
&& !imageData[offset++]
|
||||||
|
&& !imageData[offset]
|
||||||
|
) {
|
||||||
chunk[cOff] = filter(palette.abgr[chunk[cOff]]);
|
chunk[cOff] = filter(palette.abgr[chunk[cOff]]);
|
||||||
pxlCnt += 1;
|
pxlCnt += 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,9 @@ export const DEFAULT_CANVASES = {
|
||||||
|
|
||||||
export const TILE_LOADING_IMAGE = './loading.png';
|
export const TILE_LOADING_IMAGE = './loading.png';
|
||||||
|
|
||||||
|
// constants for 3D voxel canvas
|
||||||
|
export const THREE_CANVAS_HEIGHT = 128;
|
||||||
|
export const THREE_TILE_SIZE = 64;
|
||||||
// one bigchunk has 16x16 smallchunks, one smallchunk has 64x64 pixel, so one bigchunk is 1024x1024 pixels
|
// one bigchunk has 16x16 smallchunks, one smallchunk has 64x64 pixel, so one bigchunk is 1024x1024 pixels
|
||||||
export const TILE_SIZE = 256;
|
export const TILE_SIZE = 256;
|
||||||
// how much to scale for a new tiled zoomlevel
|
// how much to scale for a new tiled zoomlevel
|
||||||
|
|
|
@ -8,9 +8,10 @@ import { getChunkOfPixel, getOffsetOfPixel } from './utils';
|
||||||
import webSockets from '../socket/websockets';
|
import webSockets from '../socket/websockets';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
import RedisCanvas from '../data/models/RedisCanvas';
|
import RedisCanvas from '../data/models/RedisCanvas';
|
||||||
import { registerPixelChange } from './tileserver';
|
|
||||||
import canvases from '../canvases.json';
|
import canvases from '../canvases.json';
|
||||||
|
|
||||||
|
import { THREE_CANVAS_HEIGHT } from './constants';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -21,13 +22,14 @@ import canvases from '../canvases.json';
|
||||||
*/
|
*/
|
||||||
export function setPixel(
|
export function setPixel(
|
||||||
canvasId: number,
|
canvasId: number,
|
||||||
|
color: ColorIndex,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
color: ColorIndex,
|
z: number = null,
|
||||||
) {
|
) {
|
||||||
const canvasSize = canvases[canvasId].size;
|
const canvasSize = canvases[canvasId].size;
|
||||||
const [i, j] = getChunkOfPixel([x, y], canvasSize);
|
const [i, j] = getChunkOfPixel(canvasSize, x, y, z);
|
||||||
const offset = getOffsetOfPixel(x, y, canvasSize);
|
const offset = getOffsetOfPixel(canvasSize, x, y, z);
|
||||||
RedisCanvas.setPixelInChunk(i, j, offset, color, canvasId);
|
RedisCanvas.setPixelInChunk(i, j, offset, color, canvasId);
|
||||||
webSockets.broadcastPixel(canvasId, i, j, offset, color);
|
webSockets.broadcastPixel(canvasId, i, j, offset, color);
|
||||||
}
|
}
|
||||||
|
@ -44,9 +46,10 @@ export function setPixel(
|
||||||
async function draw(
|
async function draw(
|
||||||
user: User,
|
user: User,
|
||||||
canvasId: number,
|
canvasId: number,
|
||||||
|
color: ColorIndex,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
color: ColorIndex,
|
z: number = null,
|
||||||
): Promise<Object> {
|
): Promise<Object> {
|
||||||
if (!({}.hasOwnProperty.call(canvases, canvasId))) {
|
if (!({}.hasOwnProperty.call(canvases, canvasId))) {
|
||||||
return {
|
return {
|
||||||
|
@ -65,6 +68,25 @@ async function draw(
|
||||||
success: false,
|
success: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (z !== null) {
|
||||||
|
if (z >= THREE_CANVAS_HEIGHT) {
|
||||||
|
return {
|
||||||
|
error: 'You reached build limit. Can\'t place higher than 128 blocks.',
|
||||||
|
success: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!canvas.v) {
|
||||||
|
return {
|
||||||
|
error: 'This is not a 3D canvas',
|
||||||
|
success: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (canvas.v) {
|
||||||
|
return {
|
||||||
|
error: 'This is a 3D canvas. z is required.',
|
||||||
|
success: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (canvas.req !== -1) {
|
if (canvas.req !== -1) {
|
||||||
if (user.id === null) {
|
if (user.id === null) {
|
||||||
|
@ -80,13 +102,14 @@ async function draw(
|
||||||
if (totalPixels < canvas.req) {
|
if (totalPixels < canvas.req) {
|
||||||
return {
|
return {
|
||||||
errorTitle: 'Not Yet :(',
|
errorTitle: 'Not Yet :(',
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
error: `You need to set ${canvas.req} pixels on another canvas first, before you can use this one.`,
|
error: `You need to set ${canvas.req} pixels on another canvas first, before you can use this one.`,
|
||||||
success: false,
|
success: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const setColor = await RedisCanvas.getPixel(x, y, canvasId);
|
const setColor = await RedisCanvas.getPixel(canvasId, x, y, z);
|
||||||
|
|
||||||
let coolDown = !(setColor & 0x1E) ? canvas.bcd : canvas.pcd;
|
let coolDown = !(setColor & 0x1E) ? canvas.bcd : canvas.pcd;
|
||||||
if (user.isAdmin()) {
|
if (user.isAdmin()) {
|
||||||
|
@ -116,7 +139,7 @@ async function draw(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setPixel(canvasId, x, y, color);
|
setPixel(canvasId, color, x, y, null);
|
||||||
|
|
||||||
user.setWait(waitLeft, canvasId);
|
user.setWait(waitLeft, canvasId);
|
||||||
user.incrementPixelcount();
|
user.incrementPixelcount();
|
||||||
|
@ -141,12 +164,13 @@ async function draw(
|
||||||
function drawSafe(
|
function drawSafe(
|
||||||
user: User,
|
user: User,
|
||||||
canvasId: number,
|
canvasId: number,
|
||||||
|
color: ColorIndex,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
color: ColorIndex,
|
z: number = null,
|
||||||
): Promise<Cell> {
|
): Promise<Cell> {
|
||||||
if (user.isAdmin()) {
|
if (user.isAdmin()) {
|
||||||
return draw(user, canvasId, x, y, color);
|
return draw(user, canvasId, color, x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
// can just check for one unique occurence,
|
// can just check for one unique occurence,
|
||||||
|
@ -158,7 +182,7 @@ function drawSafe(
|
||||||
using(
|
using(
|
||||||
redlock.disposer(`locks:${userId}`, 5000, logger.error),
|
redlock.disposer(`locks:${userId}`, 5000, logger.error),
|
||||||
async () => {
|
async () => {
|
||||||
const ret = await draw(user, canvasId, x, y, color);
|
const ret = await draw(user, canvasId, color, x, y, z);
|
||||||
resolve(ret);
|
resolve(ret);
|
||||||
},
|
},
|
||||||
); // <-- unlock is automatically handled by bluebird
|
); // <-- unlock is automatically handled by bluebird
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Sequelize from 'sequelize';
|
// import Sequelize from 'sequelize';
|
||||||
import nodemailer from 'nodemailer';
|
import nodemailer from 'nodemailer';
|
||||||
|
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
|
|
|
@ -24,6 +24,10 @@ export async function updateBackupRedis(canvasRedis, backupRedis, canvases) {
|
||||||
for (let i = 0; i < ids.length; i += 1) {
|
for (let i = 0; i < ids.length; i += 1) {
|
||||||
const id = ids[i];
|
const id = ids[i];
|
||||||
const canvas = canvases[id];
|
const canvas = canvases[id];
|
||||||
|
if (canvas.v) {
|
||||||
|
// ignore 3D canvases
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const chunksXY = (canvas.size / TILE_SIZE);
|
const chunksXY = (canvas.size / TILE_SIZE);
|
||||||
console.log('Copy Chunks to backup redis...');
|
console.log('Copy Chunks to backup redis...');
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
@ -67,6 +71,11 @@ export async function incrementialBackupRedis(
|
||||||
for (let i = 0; i < ids.length; i += 1) {
|
for (let i = 0; i < ids.length; i += 1) {
|
||||||
const id = ids[i];
|
const id = ids[i];
|
||||||
|
|
||||||
|
const canvas = canvases[id];
|
||||||
|
if (canvas.v) {
|
||||||
|
// ignore 3D canvases
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const canvasBackupDir = `${backupDir}/${id}`;
|
const canvasBackupDir = `${backupDir}/${id}`;
|
||||||
if (!fs.existsSync(canvasBackupDir)) {
|
if (!fs.existsSync(canvasBackupDir)) {
|
||||||
|
@ -83,7 +92,6 @@ export async function incrementialBackupRedis(
|
||||||
fs.mkdirSync(canvasTileBackupDir);
|
fs.mkdirSync(canvasTileBackupDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
const canvas = canvases[id];
|
|
||||||
const palette = new Palette(canvas.colors, canvas.alpha);
|
const palette = new Palette(canvas.colors, canvas.alpha);
|
||||||
const chunksXY = (canvas.size / TILE_SIZE);
|
const chunksXY = (canvas.size / TILE_SIZE);
|
||||||
console.log('Creating Incremential Backup...');
|
console.log('Creating Incremential Backup...');
|
||||||
|
|
|
@ -23,7 +23,7 @@ import {
|
||||||
createTexture,
|
createTexture,
|
||||||
initializeTiles,
|
initializeTiles,
|
||||||
} from './Tile';
|
} from './Tile';
|
||||||
import { mod, getChunkOfPixel, getMaxTiledZoom } from './utils';
|
import { mod, getMaxTiledZoom } from './utils';
|
||||||
|
|
||||||
|
|
||||||
// Array that holds cells of all changed base zoomlevel tiles
|
// Array that holds cells of all changed base zoomlevel tiles
|
||||||
|
@ -116,15 +116,6 @@ class CanvasUpdater {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* register changed pixel, queue corespongind tile to reload
|
|
||||||
* @param pixel Pixel that got changed
|
|
||||||
*/
|
|
||||||
registerPixelChange(pixel: Cell) {
|
|
||||||
const chunk = getChunkOfPixel(pixel, this.canvas.size);
|
|
||||||
return this.registerChunkChange(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* initialize queues and start loops for updating tiles
|
* initialize queues and start loops for updating tiles
|
||||||
*/
|
*/
|
||||||
|
@ -163,14 +154,12 @@ class CanvasUpdater {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerChunkChange(canvasId: number, chunk: Cell) {
|
export function registerChunkChange(canvasId: number, chunk: Cell) {
|
||||||
return CanvasUpdaters[canvasId].registerChunkChange(chunk);
|
if (CanvasUpdaters[canvasId]) {
|
||||||
|
CanvasUpdaters[canvasId].registerChunkChange(chunk);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RedisCanvas.setChunkChangeCallback(registerChunkChange);
|
RedisCanvas.setChunkChangeCallback(registerChunkChange);
|
||||||
|
|
||||||
export function registerPixelChange(canvasId: number, pixel: Cell) {
|
|
||||||
return CanvasUpdaters[canvasId].registerPixelChange(pixel);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* starting update loops for canvases
|
* starting update loops for canvases
|
||||||
*/
|
*/
|
||||||
|
@ -178,7 +167,12 @@ export function startAllCanvasLoops() {
|
||||||
if (!fs.existsSync(`${TILE_FOLDER}`)) fs.mkdirSync(`${TILE_FOLDER}`);
|
if (!fs.existsSync(`${TILE_FOLDER}`)) fs.mkdirSync(`${TILE_FOLDER}`);
|
||||||
const ids = Object.keys(canvases);
|
const ids = Object.keys(canvases);
|
||||||
for (let i = 0; i < ids.length; i += 1) {
|
for (let i = 0; i < ids.length; i += 1) {
|
||||||
const updater = new CanvasUpdater(parseInt(ids[i], 10));
|
const id = parseInt(ids[i], 10);
|
||||||
|
const canvas = canvases[id];
|
||||||
|
if (!canvas.v) {
|
||||||
|
// just 2D canvases
|
||||||
|
const updater = new CanvasUpdater(id);
|
||||||
CanvasUpdaters[ids[i]] = updater;
|
CanvasUpdaters[ids[i]] = updater;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,11 @@
|
||||||
import type { Cell } from './Cell';
|
import type { Cell } from './Cell';
|
||||||
import type { State } from '../reducers';
|
import type { State } from '../reducers';
|
||||||
|
|
||||||
import { TILE_SIZE, TILE_ZOOM_LEVEL } from './constants';
|
import {
|
||||||
|
TILE_SIZE,
|
||||||
|
THREE_TILE_SIZE,
|
||||||
|
TILE_ZOOM_LEVEL,
|
||||||
|
} from './constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* http://stackoverflow.com/questions/4467539/javascript-modulo-not-behaving
|
* http://stackoverflow.com/questions/4467539/javascript-modulo-not-behaving
|
||||||
|
@ -15,13 +19,6 @@ export function mod(n: number, m: number): number {
|
||||||
return ((n % m) + m) % m;
|
return ((n % m) + m) % m;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sum(values: Array<number>): number {
|
|
||||||
let total = 0;
|
|
||||||
// TODO map reduce
|
|
||||||
values.forEach((value) => total += value);
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* returns random integer
|
* returns random integer
|
||||||
* @param min Minimum of random integer
|
* @param min Minimum of random integer
|
||||||
|
@ -41,13 +38,26 @@ export function clamp(n: number, min: number, max: number): number {
|
||||||
return Math.max(min, Math.min(n, max));
|
return Math.max(min, Math.min(n, max));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getChunkOfPixel(pixel: Cell, canvasSize: number = null): Cell {
|
export function getChunkOfPixel(
|
||||||
const target = pixel.map((x) => Math.floor((x + (canvasSize / 2)) / TILE_SIZE));
|
canvasSize: number = null,
|
||||||
return target;
|
x: number,
|
||||||
|
y: number,
|
||||||
|
z: number = null,
|
||||||
|
): Cell {
|
||||||
|
const tileSize = (z === null) ? TILE_SIZE : THREE_TILE_SIZE;
|
||||||
|
const cx = Math.floor((x + (canvasSize / 2)) / tileSize);
|
||||||
|
const cy = Math.floor((y + (canvasSize / 2)) / tileSize);
|
||||||
|
return [cx, cy];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTileOfPixel(tileScale: number, pixel: Cell, canvasSize: number = null): Cell {
|
export function getTileOfPixel(
|
||||||
const target = pixel.map((x) => Math.floor((x + canvasSize / 2) / TILE_SIZE * tileScale));
|
tileScale: number,
|
||||||
|
pixel: Cell,
|
||||||
|
canvasSize: number = null,
|
||||||
|
): Cell {
|
||||||
|
const target = pixel.map(
|
||||||
|
(x) => Math.floor((x + canvasSize / 2) / TILE_SIZE * tileScale),
|
||||||
|
);
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,11 +72,19 @@ export function getCanvasBoundaries(canvasSize: number): number {
|
||||||
return [canvasMinXY, canvasMaxXY];
|
return [canvasMinXY, canvasMaxXY];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOffsetOfPixel(x: number, y: number, canvasSize: number = null): number {
|
export function getOffsetOfPixel(
|
||||||
const modOffset = mod((canvasSize / 2), TILE_SIZE);
|
canvasSize: number = null,
|
||||||
const cx = mod(x + modOffset, TILE_SIZE);
|
x: number,
|
||||||
const cy = mod(y + modOffset, TILE_SIZE);
|
y: number,
|
||||||
return (cy * TILE_SIZE) + cx;
|
z: number = null,
|
||||||
|
): number {
|
||||||
|
const tileSize = (z === null) ? TILE_SIZE : THREE_TILE_SIZE;
|
||||||
|
let offset = (z === null) ? 0 : (z * tileSize * tileSize);
|
||||||
|
const modOffset = mod((canvasSize / 2), tileSize);
|
||||||
|
const cx = mod(x + modOffset, tileSize);
|
||||||
|
const cy = mod(y + modOffset, tileSize);
|
||||||
|
offset += (cy * tileSize) + cx;
|
||||||
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -134,29 +152,6 @@ export function worldToScreen(
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Get Color Index of specific pixel
|
|
||||||
* @param state State
|
|
||||||
* @param viewport Viewport HTML canvas
|
|
||||||
* @param coordinates Coords of pixel in World coordinates
|
|
||||||
* @return number of color Index
|
|
||||||
*/
|
|
||||||
export function getColorIndexOfPixel(
|
|
||||||
state: State,
|
|
||||||
coordinates: Cell,
|
|
||||||
): number {
|
|
||||||
const { chunks, canvasSize, canvasMaxTiledZoom } = state.canvas;
|
|
||||||
const [cx, cy] = getChunkOfPixel(coordinates, canvasSize);
|
|
||||||
const key = `${canvasMaxTiledZoom}:${cx}:${cy}`;
|
|
||||||
const chunk = chunks.get(key);
|
|
||||||
if (!chunk) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return chunk.getColorIndex(
|
|
||||||
getCellInsideChunk(coordinates),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function durationToString(
|
export function durationToString(
|
||||||
ms: number,
|
ms: number,
|
||||||
smallest: boolean = false,
|
smallest: boolean = false,
|
||||||
|
@ -166,6 +161,7 @@ export function durationToString(
|
||||||
if (seconds < 60 && smallest) {
|
if (seconds < 60 && smallest) {
|
||||||
timestring = seconds;
|
timestring = seconds;
|
||||||
} else {
|
} else {
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
timestring = `${Math.floor(seconds / 60)}:${(`0${seconds % 60}`).slice(-2)}`;
|
timestring = `${Math.floor(seconds / 60)}:${(`0${seconds % 60}`).slice(-2)}`;
|
||||||
}
|
}
|
||||||
return timestring;
|
return timestring;
|
||||||
|
@ -182,8 +178,10 @@ export function numberToString(num: number): string {
|
||||||
let postfixNum = 0;
|
let postfixNum = 0;
|
||||||
while (postfixNum < postfix.length) {
|
while (postfixNum < postfix.length) {
|
||||||
if (num < 10000) {
|
if (num < 10000) {
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
return `${Math.floor(num / 1000)}.${Math.floor((num % 1000) / 10)}${postfix[postfixNum]}`;
|
return `${Math.floor(num / 1000)}.${Math.floor((num % 1000) / 10)}${postfix[postfixNum]}`;
|
||||||
} if (num < 100000) {
|
} if (num < 100000) {
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
return `${Math.floor(num / 1000)}.${Math.floor((num % 1000) / 100)}${postfix[postfixNum]}`;
|
return `${Math.floor(num / 1000)}.${Math.floor((num % 1000) / 100)}${postfix[postfixNum]}`;
|
||||||
} if (num < 1000000) {
|
} if (num < 1000000) {
|
||||||
return Math.floor(num / 1000) + postfix[postfixNum];
|
return Math.floor(num / 1000) + postfix[postfixNum];
|
||||||
|
@ -203,6 +201,7 @@ export function numberToStringFull(num: number): string {
|
||||||
return `${Math.floor(num / 1000)}.${(`00${num % 1000}`).slice(-3)}`;
|
return `${Math.floor(num / 1000)}.${(`00${num % 1000}`).slice(-3)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
return `${Math.floor(num / 1000000)}.${(`00${Math.floor(num / 1000)}`).slice(-3)}.${(`00${num % 1000}`).slice(-3)}`;
|
return `${Math.floor(num / 1000000)}.${(`00${Math.floor(num / 1000)}`).slice(-3)}.${(`00${num % 1000}`).slice(-3)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,14 +42,15 @@ class RedisCanvas {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async setPixel(
|
static async setPixel(
|
||||||
|
canvasId: number,
|
||||||
|
color: number,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
color: number,
|
z: number = null,
|
||||||
canvasId: number,
|
|
||||||
) {
|
) {
|
||||||
const canvasSize = canvases[canvasId].size;
|
const canvasSize = canvases[canvasId].size;
|
||||||
const [i, j] = getChunkOfPixel([x, y], canvasSize);
|
const [i, j] = getChunkOfPixel(canvasSize, x, y, z);
|
||||||
const offset = getOffsetOfPixel(x, y, canvasSize);
|
const offset = getOffsetOfPixel(canvasSize, x, y, z);
|
||||||
RedisCanvas.setPixelInChunk(i, j, offset, color, canvasId);
|
RedisCanvas.setPixelInChunk(i, j, offset, color, canvasId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,16 +74,17 @@ class RedisCanvas {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getPixelIfExists(
|
static async getPixelIfExists(
|
||||||
|
canvasId: number,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
canvasId: number,
|
z: number = null,
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
// 1st and 2nd bit -> not used yet
|
// 1st and 2nd bit -> not used yet
|
||||||
// 3rd bit -> protected or not
|
// 3rd bit -> protected or not
|
||||||
// rest (5 bits) -> index of color
|
// rest (5 bits) -> index of color
|
||||||
const canvasSize = canvases[canvasId].size;
|
const canvasSize = canvases[canvasId].size;
|
||||||
const [i, j] = getChunkOfPixel([x, y], canvasSize);
|
const [i, j] = getChunkOfPixel(canvasSize, x, y, z);
|
||||||
const offset = getOffsetOfPixel(x, y, canvasSize);
|
const offset = getOffsetOfPixel(canvasSize, x, y, z);
|
||||||
const args = [
|
const args = [
|
||||||
`ch:${canvasId}:${i}:${j}`,
|
`ch:${canvasId}:${i}:${j}`,
|
||||||
'GET',
|
'GET',
|
||||||
|
@ -96,12 +98,13 @@ class RedisCanvas {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getPixel(
|
static async getPixel(
|
||||||
|
canvasId: number,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
canvasId: number,
|
z: number = null,
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const canvasAlpha = canvases[canvasId].alpha;
|
const canvasAlpha = canvases[canvasId].alpha;
|
||||||
const clr = RedisCanvas.getPixelIfExists(x, y, canvasId);
|
const clr = RedisCanvas.getPixelIfExists(canvasId, x, y, z);
|
||||||
return (clr == null) ? canvasAlpha : clr;
|
return (clr == null) ? canvasAlpha : clr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ import type { Cell } from '../core/Cell';
|
||||||
import Palette from '../core/Palette';
|
import Palette from '../core/Palette';
|
||||||
import {
|
import {
|
||||||
getMaxTiledZoom,
|
getMaxTiledZoom,
|
||||||
getChunkOfPixel,
|
|
||||||
getCellInsideChunk,
|
|
||||||
clamp,
|
clamp,
|
||||||
getIdFromObject,
|
getIdFromObject,
|
||||||
} from '../core/utils';
|
} from '../core/utils';
|
||||||
|
@ -19,20 +17,18 @@ import {
|
||||||
DEFAULT_CANVASES,
|
DEFAULT_CANVASES,
|
||||||
TILE_SIZE,
|
TILE_SIZE,
|
||||||
} from '../core/constants';
|
} from '../core/constants';
|
||||||
import ChunkRGB from '../ui/ChunkRGB';
|
|
||||||
|
|
||||||
export type CanvasState = {
|
export type CanvasState = {
|
||||||
canvasId: number,
|
canvasId: number,
|
||||||
canvasIdent: string,
|
canvasIdent: string,
|
||||||
|
is3D: boolean,
|
||||||
canvasSize: number,
|
canvasSize: number,
|
||||||
canvasMaxTiledZoom: number,
|
canvasMaxTiledZoom: number,
|
||||||
canvasStartDate: string,
|
canvasStartDate: string,
|
||||||
palette: Palette,
|
palette: Palette,
|
||||||
chunks: Map<string, ChunkRGB>,
|
|
||||||
view: Cell,
|
view: Cell,
|
||||||
scale: number,
|
scale: number,
|
||||||
viewscale: number,
|
viewscale: number,
|
||||||
requested: Set<string>,
|
|
||||||
fetchs: number,
|
fetchs: number,
|
||||||
isHistoricalView: boolean,
|
isHistoricalView: boolean,
|
||||||
historicalDate: string,
|
historicalDate: string,
|
||||||
|
@ -67,27 +63,30 @@ function getViewFromURL(canvases: Object) {
|
||||||
let colors;
|
let colors;
|
||||||
let canvasSize;
|
let canvasSize;
|
||||||
let canvasStartDate;
|
let canvasStartDate;
|
||||||
|
let is3D;
|
||||||
if (canvasId == null) {
|
if (canvasId == null) {
|
||||||
// if canvas informations are not available yet
|
// if canvas informations are not available yet
|
||||||
// aka /api/me didn't load yet
|
// aka /api/me didn't load yet
|
||||||
colors = canvases[DEFAULT_CANVAS_ID].colors;
|
colors = canvases[DEFAULT_CANVAS_ID].colors;
|
||||||
canvasSize = 1024;
|
canvasSize = 1024;
|
||||||
|
is3D = false;
|
||||||
canvasStartDate = null;
|
canvasStartDate = null;
|
||||||
} else {
|
} else {
|
||||||
const canvas = canvases[canvasId];
|
const canvas = canvases[canvasId];
|
||||||
colors = canvas.colors;
|
colors = canvas.colors;
|
||||||
canvasSize = canvas.size;
|
canvasSize = canvas.size;
|
||||||
|
is3D = !!canvas.v;
|
||||||
canvasStartDate = canvas.sd;
|
canvasStartDate = canvas.sd;
|
||||||
}
|
}
|
||||||
|
|
||||||
const x = parseInt(almost[1], 10);
|
const x = parseInt(almost[1], 10);
|
||||||
const y = parseInt(almost[2], 10);
|
const y = parseInt(almost[2], 10);
|
||||||
let urlscale = parseInt(almost[3], 10);
|
let urlscale = parseInt(almost[3], 10);
|
||||||
if (isNaN(x) || isNaN(y)) {
|
if (Number.isNaN(x) || Number.isNaN(y)) {
|
||||||
const thrown = 'NaN';
|
const thrown = 'NaN';
|
||||||
throw thrown;
|
throw thrown;
|
||||||
}
|
}
|
||||||
if (!urlscale || isNaN(urlscale)) {
|
if (!urlscale || Number.isNaN(urlscale)) {
|
||||||
urlscale = DEFAULT_SCALE;
|
urlscale = DEFAULT_SCALE;
|
||||||
} else {
|
} else {
|
||||||
urlscale = 2 ** (urlscale / 10);
|
urlscale = 2 ** (urlscale / 10);
|
||||||
|
@ -97,6 +96,7 @@ function getViewFromURL(canvases: Object) {
|
||||||
canvasId,
|
canvasId,
|
||||||
canvasIdent,
|
canvasIdent,
|
||||||
canvasSize,
|
canvasSize,
|
||||||
|
is3D,
|
||||||
canvasStartDate,
|
canvasStartDate,
|
||||||
canvasMaxTiledZoom: getMaxTiledZoom(canvasSize),
|
canvasMaxTiledZoom: getMaxTiledZoom(canvasSize),
|
||||||
palette: new Palette(colors, 0),
|
palette: new Palette(colors, 0),
|
||||||
|
@ -110,6 +110,7 @@ function getViewFromURL(canvases: Object) {
|
||||||
canvasId: DEFAULT_CANVAS_ID,
|
canvasId: DEFAULT_CANVAS_ID,
|
||||||
canvasIdent: canvases[DEFAULT_CANVAS_ID].ident,
|
canvasIdent: canvases[DEFAULT_CANVAS_ID].ident,
|
||||||
canvasSize: canvases[DEFAULT_CANVAS_ID].size,
|
canvasSize: canvases[DEFAULT_CANVAS_ID].size,
|
||||||
|
is3D: !!canvases[DEFAULT_CANVAS_ID].v,
|
||||||
canvasStartDate: null,
|
canvasStartDate: null,
|
||||||
canvasMaxTiledZoom: getMaxTiledZoom(canvases[DEFAULT_CANVAS_ID].size),
|
canvasMaxTiledZoom: getMaxTiledZoom(canvases[DEFAULT_CANVAS_ID].size),
|
||||||
palette: new Palette(canvases[DEFAULT_CANVAS_ID].colors, 0),
|
palette: new Palette(canvases[DEFAULT_CANVAS_ID].colors, 0),
|
||||||
|
@ -121,9 +122,7 @@ function getViewFromURL(canvases: Object) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: CanvasState = {
|
const initialState: CanvasState = {
|
||||||
chunks: new Map(),
|
|
||||||
...getViewFromURL(DEFAULT_CANVASES),
|
...getViewFromURL(DEFAULT_CANVASES),
|
||||||
requested: new Set(),
|
|
||||||
fetchs: 0,
|
fetchs: 0,
|
||||||
isHistoricalView: false,
|
isHistoricalView: false,
|
||||||
historicalDate: null,
|
historicalDate: null,
|
||||||
|
@ -131,36 +130,11 @@ const initialState: CanvasState = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default function gui(
|
export default function canvasReducer(
|
||||||
state: CanvasState = initialState,
|
state: CanvasState = initialState,
|
||||||
action: Action,
|
action: Action,
|
||||||
): CanvasState {
|
): CanvasState {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'PLACE_PIXEL': {
|
|
||||||
const {
|
|
||||||
chunks, canvasMaxTiledZoom, palette, canvasSize,
|
|
||||||
} = state;
|
|
||||||
const { coordinates, color } = action;
|
|
||||||
|
|
||||||
const [cx, cy] = getChunkOfPixel(coordinates, canvasSize);
|
|
||||||
const key = ChunkRGB.getKey(canvasMaxTiledZoom, cx, cy);
|
|
||||||
let chunk = chunks.get(key);
|
|
||||||
if (!chunk) {
|
|
||||||
chunk = new ChunkRGB(palette, [canvasMaxTiledZoom, cx, cy]);
|
|
||||||
chunks.set(chunk.key, chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
// redis prediction
|
|
||||||
chunk.setColor(
|
|
||||||
getCellInsideChunk(coordinates),
|
|
||||||
color,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
chunks,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'SET_SCALE': {
|
case 'SET_SCALE': {
|
||||||
let {
|
let {
|
||||||
view,
|
view,
|
||||||
|
@ -221,7 +195,7 @@ export default function gui(
|
||||||
...state,
|
...state,
|
||||||
scale: (scale < 1.0) ? 1.0 : scale,
|
scale: (scale < 1.0) ? 1.0 : scale,
|
||||||
viewscale: (viewscale < 1.0) ? 1.0 : viewscale,
|
viewscale: (viewscale < 1.0) ? 1.0 : viewscale,
|
||||||
isHistoricalView: !state.isHistoricalView,
|
isHistoricalView: !state.is3D && !state.isHistoricalView,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,85 +212,36 @@ export default function gui(
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'RELOAD_URL': {
|
case 'RELOAD_URL': {
|
||||||
const { canvasId, chunks, canvases } = state;
|
const { canvases } = state;
|
||||||
const nextstate = getViewFromURL(canvases);
|
const nextstate = getViewFromURL(canvases);
|
||||||
if (nextstate.canvasId !== canvasId) {
|
|
||||||
chunks.clear();
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
...nextstate,
|
...nextstate,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* set url coordinates
|
|
||||||
*/
|
|
||||||
case 'ON_VIEW_FINISH_CHANGE': {
|
|
||||||
const { view, viewscale, canvasIdent } = state;
|
|
||||||
let [x, y] = view;
|
|
||||||
x = Math.round(x);
|
|
||||||
y = Math.round(y);
|
|
||||||
const scale = Math.round(Math.log2(viewscale) * 10);
|
|
||||||
const newhash = `#${canvasIdent},${x},${y},${scale}`;
|
|
||||||
window.history.replaceState(undefined, undefined, newhash);
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'REQUEST_BIG_CHUNK': {
|
case 'REQUEST_BIG_CHUNK': {
|
||||||
const {
|
const {
|
||||||
palette, chunks, fetchs, requested,
|
fetchs,
|
||||||
} = state;
|
} = state;
|
||||||
const { center } = action;
|
|
||||||
|
|
||||||
const chunkRGB = new ChunkRGB(palette, center);
|
|
||||||
// chunkRGB.preLoad(chunks);
|
|
||||||
const { key } = chunkRGB;
|
|
||||||
chunks.set(key, chunkRGB);
|
|
||||||
|
|
||||||
requested.add(key);
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
chunks,
|
|
||||||
fetchs: fetchs + 1,
|
fetchs: fetchs + 1,
|
||||||
requested,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'RECEIVE_BIG_CHUNK': {
|
case 'RECEIVE_BIG_CHUNK': {
|
||||||
const { chunks, fetchs } = state;
|
const { fetchs } = state;
|
||||||
const { center, arrayBuffer } = action;
|
|
||||||
|
|
||||||
const key = ChunkRGB.getKey(...center);
|
|
||||||
const chunk = chunks.get(key);
|
|
||||||
if (!chunk) return state;
|
|
||||||
|
|
||||||
chunk.isBasechunk = true;
|
|
||||||
if (arrayBuffer.byteLength) {
|
|
||||||
const chunkArray = new Uint8Array(arrayBuffer);
|
|
||||||
chunk.fromBuffer(chunkArray);
|
|
||||||
} else {
|
|
||||||
chunk.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
chunks,
|
|
||||||
fetchs: fetchs + 1,
|
fetchs: fetchs + 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'RECEIVE_BIG_CHUNK_FAILURE': {
|
case 'RECEIVE_BIG_CHUNK_FAILURE': {
|
||||||
const { chunks, fetchs } = state;
|
const { fetchs } = state;
|
||||||
const { center } = action;
|
|
||||||
|
|
||||||
const key = ChunkRGB.getKey(...center);
|
|
||||||
const chunk = chunks.get(key);
|
|
||||||
if (!chunk) return state;
|
|
||||||
|
|
||||||
chunk.empty();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -324,52 +249,10 @@ export default function gui(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'RECEIVE_IMAGE_TILE': {
|
|
||||||
const { chunks, fetchs } = state;
|
|
||||||
const { center, tile } = action;
|
|
||||||
|
|
||||||
const key = ChunkRGB.getKey(...center);
|
|
||||||
const chunk = chunks.get(key);
|
|
||||||
if (!chunk) return state;
|
|
||||||
|
|
||||||
chunk.fromImage(tile);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
chunks,
|
|
||||||
fetchs: fetchs + 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'RECEIVE_PIXEL_UPDATE': {
|
|
||||||
const { chunks, canvasMaxTiledZoom } = state;
|
|
||||||
// i, j: Coordinates of chunk
|
|
||||||
// offset: Offset of pixel within said chunk
|
|
||||||
const {
|
|
||||||
i, j, offset, color,
|
|
||||||
} = action;
|
|
||||||
|
|
||||||
const key = ChunkRGB.getKey(canvasMaxTiledZoom, i, j);
|
|
||||||
const chunk = chunks.get(key);
|
|
||||||
|
|
||||||
// ignore because is not seen
|
|
||||||
if (!chunk) return state;
|
|
||||||
|
|
||||||
const ix = offset % TILE_SIZE;
|
|
||||||
const iy = Math.floor(offset / TILE_SIZE);
|
|
||||||
chunk.setColor([ix, iy], color);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
chunks,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'SELECT_CANVAS': {
|
case 'SELECT_CANVAS': {
|
||||||
let { canvasId } = action;
|
let { canvasId } = action;
|
||||||
const { canvases, chunks } = state;
|
const { canvases, isHistoricalView } = state;
|
||||||
|
|
||||||
chunks.clear();
|
|
||||||
let canvas = canvases[canvasId];
|
let canvas = canvases[canvasId];
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
canvasId = DEFAULT_CANVAS_ID;
|
canvasId = DEFAULT_CANVAS_ID;
|
||||||
|
@ -379,23 +262,25 @@ export default function gui(
|
||||||
size: canvasSize,
|
size: canvasSize,
|
||||||
sd: canvasStartDate,
|
sd: canvasStartDate,
|
||||||
ident: canvasIdent,
|
ident: canvasIdent,
|
||||||
|
v: is3D,
|
||||||
colors,
|
colors,
|
||||||
} = canvas;
|
} = canvas;
|
||||||
const canvasMaxTiledZoom = getMaxTiledZoom(canvasSize);
|
const canvasMaxTiledZoom = getMaxTiledZoom(canvasSize);
|
||||||
const palette = new Palette(colors, 0);
|
const palette = new Palette(colors, 0);
|
||||||
const view = (canvasId === 0) ? getGivenCoords() : [0, 0];
|
const view = (canvasId === 0) ? getGivenCoords() : [0, 0];
|
||||||
chunks.clear();
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
canvasId,
|
canvasId,
|
||||||
canvasIdent,
|
canvasIdent,
|
||||||
canvasSize,
|
canvasSize,
|
||||||
|
is3D,
|
||||||
canvasStartDate,
|
canvasStartDate,
|
||||||
canvasMaxTiledZoom,
|
canvasMaxTiledZoom,
|
||||||
palette,
|
palette,
|
||||||
view,
|
view,
|
||||||
viewscale: DEFAULT_SCALE,
|
viewscale: DEFAULT_SCALE,
|
||||||
scale: DEFAULT_SCALE,
|
scale: DEFAULT_SCALE,
|
||||||
|
isHistoricalView: !is3D && isHistoricalView,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,6 +296,7 @@ export default function gui(
|
||||||
const {
|
const {
|
||||||
size: canvasSize,
|
size: canvasSize,
|
||||||
sd: canvasStartDate,
|
sd: canvasStartDate,
|
||||||
|
v: is3D,
|
||||||
colors,
|
colors,
|
||||||
} = canvases[canvasId];
|
} = canvases[canvasId];
|
||||||
const canvasMaxTiledZoom = getMaxTiledZoom(canvasSize);
|
const canvasMaxTiledZoom = getMaxTiledZoom(canvasSize);
|
||||||
|
@ -421,6 +307,7 @@ export default function gui(
|
||||||
canvasId,
|
canvasId,
|
||||||
canvasIdent,
|
canvasIdent,
|
||||||
canvasSize,
|
canvasSize,
|
||||||
|
is3D,
|
||||||
canvasStartDate,
|
canvasStartDate,
|
||||||
canvasMaxTiledZoom,
|
canvasMaxTiledZoom,
|
||||||
palette,
|
palette,
|
||||||
|
|
|
@ -37,6 +37,7 @@ export default function modal(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'SELECT_CANVAS':
|
||||||
case 'HIDE_MODAL':
|
case 'HIDE_MODAL':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -157,6 +157,11 @@ router.post('/', upload.single('image'), async (req, res, next) => {
|
||||||
|
|
||||||
const canvas = canvases[canvasId];
|
const canvas = canvases[canvasId];
|
||||||
|
|
||||||
|
if (canvas.v) {
|
||||||
|
res.status(403).send('Can not upload Image to 3D canvas');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const canvasMaxXY = canvas.size / 2;
|
const canvasMaxXY = canvas.size / 2;
|
||||||
const canvasMinXY = -canvasMaxXY;
|
const canvasMinXY = -canvasMaxXY;
|
||||||
if (x < canvasMinXY || y < canvasMinXY
|
if (x < canvasMinXY || y < canvasMinXY
|
||||||
|
|
|
@ -6,14 +6,19 @@
|
||||||
import type { Request, Response } from 'express';
|
import type { Request, Response } from 'express';
|
||||||
|
|
||||||
import draw from '../../core/draw';
|
import draw from '../../core/draw';
|
||||||
import { blacklistDetector, cheapDetector, strongDetector } from '../../core/isProxy';
|
import {
|
||||||
|
blacklistDetector,
|
||||||
|
cheapDetector,
|
||||||
|
strongDetector,
|
||||||
|
} from '../../core/isProxy';
|
||||||
import verifyCaptcha from '../../utils/recaptcha';
|
import verifyCaptcha from '../../utils/recaptcha';
|
||||||
import logger from '../../core/logger';
|
import logger from '../../core/logger';
|
||||||
import redis from '../../data/redis';
|
import redis from '../../data/redis';
|
||||||
import { USE_PROXYCHECK, RECAPTCHA_SECRET, RECAPTCHA_TIME } from '../../core/config';
|
|
||||||
import {
|
import {
|
||||||
User,
|
USE_PROXYCHECK,
|
||||||
} from '../../data/models';
|
RECAPTCHA_SECRET,
|
||||||
|
RECAPTCHA_TIME,
|
||||||
|
} from '../../core/config';
|
||||||
|
|
||||||
|
|
||||||
async function validate(req: Request, res: Response, next) {
|
async function validate(req: Request, res: Response, next) {
|
||||||
|
@ -21,6 +26,10 @@ async function validate(req: Request, res: Response, next) {
|
||||||
const cn = parseInt(req.body.cn, 10);
|
const cn = parseInt(req.body.cn, 10);
|
||||||
const x = parseInt(req.body.x, 10);
|
const x = parseInt(req.body.x, 10);
|
||||||
const y = parseInt(req.body.y, 10);
|
const y = parseInt(req.body.y, 10);
|
||||||
|
let z = null;
|
||||||
|
if (req.body.z) {
|
||||||
|
z = parseInt(req.body.z, 10);
|
||||||
|
}
|
||||||
const clr = parseInt(req.body.clr, 10);
|
const clr = parseInt(req.body.clr, 10);
|
||||||
|
|
||||||
if (Number.isNaN(cn)) {
|
if (Number.isNaN(cn)) {
|
||||||
|
@ -33,6 +42,8 @@ async function validate(req: Request, res: Response, next) {
|
||||||
error = 'No color selected';
|
error = 'No color selected';
|
||||||
} else if (clr < 2 || clr > 31) {
|
} else if (clr < 2 || clr > 31) {
|
||||||
error = 'Invalid color selected';
|
error = 'Invalid color selected';
|
||||||
|
} else if (z !== null && Number.isNaN(z)) {
|
||||||
|
error = 'z is not a valid number';
|
||||||
}
|
}
|
||||||
if (error !== null) {
|
if (error !== null) {
|
||||||
res.status(400).json({ errors: [error] });
|
res.status(400).json({ errors: [error] });
|
||||||
|
@ -42,6 +53,7 @@ async function validate(req: Request, res: Response, next) {
|
||||||
req.body.cn = cn;
|
req.body.cn = cn;
|
||||||
req.body.x = x;
|
req.body.x = x;
|
||||||
req.body.y = y;
|
req.body.y = y;
|
||||||
|
req.body.z = z;
|
||||||
req.body.clr = clr;
|
req.body.clr = clr;
|
||||||
|
|
||||||
|
|
||||||
|
@ -111,7 +123,7 @@ async function checkHuman(req: Request, res: Response, next) {
|
||||||
// strongly check selective areas
|
// strongly check selective areas
|
||||||
async function checkProxy(req: Request, res: Response, next) {
|
async function checkProxy(req: Request, res: Response, next) {
|
||||||
const { trueIp: ip } = req;
|
const { trueIp: ip } = req;
|
||||||
if (USE_PROXYCHECK && ip != '0.0.0.1') {
|
if (USE_PROXYCHECK && ip !== '0.0.0.1') {
|
||||||
/*
|
/*
|
||||||
//one area uses stronger detector
|
//one area uses stronger detector
|
||||||
const { x, y } = req.body;
|
const { x, y } = req.body;
|
||||||
|
@ -146,6 +158,7 @@ async function checkProxy(req: Request, res: Response, next) {
|
||||||
|
|
||||||
// strongly check just specific areas for proxies
|
// strongly check just specific areas for proxies
|
||||||
// do not proxycheck the rest
|
// do not proxycheck the rest
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
async function checkProxySelective(req: Request, res: Response, next) {
|
async function checkProxySelective(req: Request, res: Response, next) {
|
||||||
const { trueIp: ip } = req;
|
const { trueIp: ip } = req;
|
||||||
if (USE_PROXYCHECK) {
|
if (USE_PROXYCHECK) {
|
||||||
|
@ -177,18 +190,16 @@ async function place(req: Request, res: Response) {
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
cn, x, y, clr,
|
cn, x, y, z, clr,
|
||||||
} = req.body;
|
} = req.body;
|
||||||
const { user, headers, trueIp } = req;
|
const { user, trueIp } = req;
|
||||||
const { ip } = user;
|
|
||||||
|
|
||||||
const isHashed = parseInt(req.body.a, 10) === (x + y + 8);
|
// eslint-disable-next-line max-len
|
||||||
|
logger.info(`${trueIp} / ${user.id} wants to place ${clr} in (${x}, ${y}, ${z}) on canvas ${cn}`);
|
||||||
logger.info(`${trueIp} / ${user.id} wants to place ${clr} in (${x}, ${y})`);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
errorTitle, error, success, waitSeconds, coolDownSeconds,
|
errorTitle, error, success, waitSeconds, coolDownSeconds,
|
||||||
} = await draw(user, cn, x, y, clr);
|
} = await draw(user, cn, clr, x, y, z);
|
||||||
logger.log('debug', success);
|
logger.log('debug', success);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
|
|
|
@ -7,10 +7,11 @@
|
||||||
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import type { Request, Response } from 'express';
|
import type { Request, Response } from 'express';
|
||||||
import sharp from 'sharp';
|
|
||||||
import { TILE_SIZE, HOUR } from '../core/constants';
|
|
||||||
import { TILE_FOLDER } from '../core/config';
|
import { TILE_FOLDER } from '../core/config';
|
||||||
import RedisCanvas from '../data/models/RedisCanvas';
|
import { HOUR } from '../core/constants';
|
||||||
|
// import sharp from 'sharp';
|
||||||
|
// import { TILE_SIZE, HOUR } from '../core/constants';
|
||||||
|
// import RedisCanvas from '../data/models/RedisCanvas';
|
||||||
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
@ -72,16 +73,17 @@ router.use('/', express.static(TILE_FOLDER, {
|
||||||
/*
|
/*
|
||||||
* catch File Not Found: Send empty tile
|
* catch File Not Found: Send empty tile
|
||||||
*/
|
*/
|
||||||
router.use('/:c([0-9]+)/:z([0-9]+)/:x([0-9]+)/:y([0-9]+).png', async (req: Request, res: Response) => {
|
router.use('/:c([0-9]+)/:z([0-9]+)/:x([0-9]+)/:y([0-9]+).png',
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
const { c: paramC } = req.params;
|
const { c: paramC } = req.params;
|
||||||
const c = parseInt(paramC, 10);
|
const c = parseInt(paramC, 10);
|
||||||
res.set({
|
res.set({
|
||||||
'Cache-Control': `public, s-maxage=${2 * 60 * 60}, max-age=${1 * 60 * 60}`, // seconds
|
'Cache-Control': `public, s-maxage=${2 * 3600}, max-age=${1 * 3600}`,
|
||||||
'Content-Type': 'image/png',
|
'Content-Type': 'image/png',
|
||||||
});
|
});
|
||||||
res.status(200);
|
res.status(200);
|
||||||
res.sendFile(`${TILE_FOLDER}/${c}/emptytile.png`);
|
res.sendFile(`${TILE_FOLDER}/${c}/emptytile.png`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -28,7 +28,8 @@ async function verifyClient(info, done) {
|
||||||
const { headers } = req;
|
const { headers } = req;
|
||||||
const ip = await getIPFromRequest(req);
|
const ip = await getIPFromRequest(req);
|
||||||
|
|
||||||
if (!headers.authorization || headers.authorization != `Bearer ${APISOCKET_KEY}`) {
|
if (!headers.authorization
|
||||||
|
|| headers.authorization != `Bearer ${APISOCKET_KEY}`) {
|
||||||
logger.warn(`API ws request from ${ip} authenticated`);
|
logger.warn(`API ws request from ${ip} authenticated`);
|
||||||
return done(false);
|
return done(false);
|
||||||
}
|
}
|
||||||
|
@ -83,7 +84,9 @@ class APISocketServer extends WebSocketEvents {
|
||||||
|
|
||||||
const sendmsg = JSON.stringify(['msg', name, msg]);
|
const sendmsg = JSON.stringify(['msg', name, msg]);
|
||||||
this.wss.clients.forEach((client) => {
|
this.wss.clients.forEach((client) => {
|
||||||
if (client !== ws && client.subChat && client.readyState === WebSocket.OPEN) {
|
if (client !== ws
|
||||||
|
&& client.subChat
|
||||||
|
&& client.readyState === WebSocket.OPEN) {
|
||||||
client.send(sendmsg);
|
client.send(sendmsg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -117,9 +120,9 @@ class APISocketServer extends WebSocketEvents {
|
||||||
});
|
});
|
||||||
this.wss.clients.forEach((client) => {
|
this.wss.clients.forEach((client) => {
|
||||||
if (client.subOnline && client.readyState === WebSocket.OPEN) {
|
if (client.subOnline && client.readyState === WebSocket.OPEN) {
|
||||||
frame.forEach((buffer) => {
|
frame.forEach((data) => {
|
||||||
try {
|
try {
|
||||||
client._socket.write(buffer);
|
client._socket.write(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('(!) Catched error on write apisocket:', error);
|
logger.error('(!) Catched error on write apisocket:', error);
|
||||||
}
|
}
|
||||||
|
@ -139,9 +142,9 @@ class APISocketServer extends WebSocketEvents {
|
||||||
});
|
});
|
||||||
this.wss.clients.forEach((client) => {
|
this.wss.clients.forEach((client) => {
|
||||||
if (client.subPxl && client.readyState === WebSocket.OPEN) {
|
if (client.subPxl && client.readyState === WebSocket.OPEN) {
|
||||||
frame.forEach((buffer) => {
|
frame.forEach((data) => {
|
||||||
try {
|
try {
|
||||||
client._socket.write(buffer);
|
client._socket.write(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('(!) Catched error on write apisocket:', error);
|
logger.error('(!) Catched error on write apisocket:', error);
|
||||||
}
|
}
|
||||||
|
@ -177,7 +180,7 @@ class APISocketServer extends WebSocketEvents {
|
||||||
if (clr < 0 || clr > 32) return;
|
if (clr < 0 || clr > 32) return;
|
||||||
// be aware that user null has no cd
|
// be aware that user null has no cd
|
||||||
if (!minecraftid && !ip) {
|
if (!minecraftid && !ip) {
|
||||||
setPixel(0, x, y, clr);
|
setPixel(0, clr, x, y);
|
||||||
ws.send(JSON.stringify(['retpxl', null, null, true, 0, 0]));
|
ws.send(JSON.stringify(['retpxl', null, null, true, 0, 0]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -185,7 +188,7 @@ class APISocketServer extends WebSocketEvents {
|
||||||
user.ip = ip;
|
user.ip = ip;
|
||||||
const {
|
const {
|
||||||
error, success, waitSeconds, coolDownSeconds,
|
error, success, waitSeconds, coolDownSeconds,
|
||||||
} = await drawUnsafe(user, 0, x, y, clr);
|
} = await drawUnsafe(user, 0, clr, x, y, null);
|
||||||
ws.send(JSON.stringify([
|
ws.send(JSON.stringify([
|
||||||
'retpxl',
|
'retpxl',
|
||||||
(minecraftid) || ip,
|
(minecraftid) || ip,
|
||||||
|
@ -229,7 +232,9 @@ class APISocketServer extends WebSocketEvents {
|
||||||
if (command == 'mcchat') {
|
if (command == 'mcchat') {
|
||||||
const [minecraftname, msg] = packet;
|
const [minecraftname, msg] = packet;
|
||||||
const user = this.mc.minecraftname2User(minecraftname);
|
const user = this.mc.minecraftname2User(minecraftname);
|
||||||
const chatname = (user.id) ? `[MC] ${user.regUser.name}` : `[MC] ${minecraftname}`;
|
const chatname = (user.id)
|
||||||
|
? `[MC] ${user.regUser.name}`
|
||||||
|
: `[MC] ${minecraftname}`;
|
||||||
webSockets.broadcastChatMessage(chatname, msg, false);
|
webSockets.broadcastChatMessage(chatname, msg, false);
|
||||||
this.broadcastChatMessage(chatname, msg, true, ws);
|
this.broadcastChatMessage(chatname, msg, true, ws);
|
||||||
return;
|
return;
|
||||||
|
@ -265,6 +270,7 @@ class APISocketServer extends WebSocketEvents {
|
||||||
|
|
||||||
ws.isAlive = false;
|
ws.isAlive = false;
|
||||||
ws.ping(() => {});
|
ws.ping(() => {});
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@ export default {
|
||||||
// CLIENT
|
// CLIENT
|
||||||
const i = data.getInt16(1);
|
const i = data.getInt16(1);
|
||||||
const j = data.getInt16(3);
|
const j = data.getInt16(3);
|
||||||
|
// const offset = (data.getUint8(5) << 16) | data.getUint16(6);
|
||||||
|
// const color = data.getUint8(8);
|
||||||
const offset = data.getUint16(5);
|
const offset = data.getUint16(5);
|
||||||
const color = data.getUint8(7);
|
const color = data.getUint8(7);
|
||||||
return {
|
return {
|
||||||
|
@ -26,11 +28,14 @@ export default {
|
||||||
dehydrate(i, j, offset, color): Buffer {
|
dehydrate(i, j, offset, color): Buffer {
|
||||||
// SERVER
|
// SERVER
|
||||||
if (!process.env.BROWSER) {
|
if (!process.env.BROWSER) {
|
||||||
const buffer = Buffer.allocUnsafe(1 + 2 + 2 + 2 + 1);
|
const buffer = Buffer.allocUnsafe(1 + 2 + 2 + 1 + 2 + 1);
|
||||||
buffer.writeUInt8(OP_CODE, 0);
|
buffer.writeUInt8(OP_CODE, 0);
|
||||||
|
|
||||||
buffer.writeInt16BE(i, 1);
|
buffer.writeInt16BE(i, 1);
|
||||||
buffer.writeInt16BE(j, 3);
|
buffer.writeInt16BE(j, 3);
|
||||||
|
// buffer.writeUInt8(offset >>> 16, 5);
|
||||||
|
// buffer.writeUInt16BE(offset & 0x00FFFF, 6);
|
||||||
|
// buffer.writeUInt8(color, 8);
|
||||||
buffer.writeUInt16BE(offset, 5);
|
buffer.writeUInt16BE(offset, 5);
|
||||||
buffer.writeUInt8(color, 7);
|
buffer.writeUInt8(color, 7);
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,10 @@ export default (store) => (next) => (action) => {
|
||||||
|
|
||||||
|
|
||||||
gainNode.gain.setValueAtTime(0.3, context.currentTime);
|
gainNode.gain.setValueAtTime(0.3, context.currentTime);
|
||||||
gainNode.gain.exponentialRampToValueAtTime(0.2, context.currentTime + 0.1);
|
gainNode.gain.exponentialRampToValueAtTime(
|
||||||
|
0.2,
|
||||||
|
context.currentTime + 0.1,
|
||||||
|
);
|
||||||
|
|
||||||
oscillatorNode.connect(gainNode);
|
oscillatorNode.connect(gainNode);
|
||||||
gainNode.connect(context.destination);
|
gainNode.connect(context.destination);
|
||||||
|
@ -46,11 +49,17 @@ export default (store) => (next) => (action) => {
|
||||||
// oscillatorNode.detune.value = -600
|
// oscillatorNode.detune.value = -600
|
||||||
|
|
||||||
oscillatorNode.frequency.setValueAtTime(1479.98, context.currentTime);
|
oscillatorNode.frequency.setValueAtTime(1479.98, context.currentTime);
|
||||||
oscillatorNode.frequency.exponentialRampToValueAtTime(493.88, context.currentTime + 0.01);
|
oscillatorNode.frequency.exponentialRampToValueAtTime(
|
||||||
|
493.88,
|
||||||
|
context.currentTime + 0.01,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
gainNode.gain.setValueAtTime(0.5, context.currentTime);
|
gainNode.gain.setValueAtTime(0.5, context.currentTime);
|
||||||
gainNode.gain.exponentialRampToValueAtTime(0.2, context.currentTime + 0.1);
|
gainNode.gain.exponentialRampToValueAtTime(
|
||||||
|
0.2,
|
||||||
|
context.currentTime + 0.1,
|
||||||
|
);
|
||||||
|
|
||||||
oscillatorNode.connect(gainNode);
|
oscillatorNode.connect(gainNode);
|
||||||
gainNode.connect(context.destination);
|
gainNode.connect(context.destination);
|
||||||
|
@ -68,9 +77,18 @@ export default (store) => (next) => (action) => {
|
||||||
oscillatorNode.type = 'sine';
|
oscillatorNode.type = 'sine';
|
||||||
oscillatorNode.detune.value = -900;
|
oscillatorNode.detune.value = -900;
|
||||||
oscillatorNode.frequency.setValueAtTime(600, context.currentTime);
|
oscillatorNode.frequency.setValueAtTime(600, context.currentTime);
|
||||||
oscillatorNode.frequency.setValueAtTime(1400, context.currentTime + 0.025);
|
oscillatorNode.frequency.setValueAtTime(
|
||||||
oscillatorNode.frequency.setValueAtTime(1200, context.currentTime + 0.05);
|
1400,
|
||||||
oscillatorNode.frequency.setValueAtTime(900, context.currentTime + 0.075);
|
context.currentTime + 0.025,
|
||||||
|
);
|
||||||
|
oscillatorNode.frequency.setValueAtTime(
|
||||||
|
1200,
|
||||||
|
context.currentTime + 0.05,
|
||||||
|
);
|
||||||
|
oscillatorNode.frequency.setValueAtTime(
|
||||||
|
900,
|
||||||
|
context.currentTime + 0.075,
|
||||||
|
);
|
||||||
|
|
||||||
const lfo = context.createOscillator();
|
const lfo = context.createOscillator();
|
||||||
lfo.type = 'sine';
|
lfo.type = 'sine';
|
||||||
|
@ -94,10 +112,16 @@ export default (store) => (next) => (action) => {
|
||||||
|
|
||||||
oscillatorNode.type = 'sine';
|
oscillatorNode.type = 'sine';
|
||||||
oscillatorNode.frequency.setValueAtTime(clrFreq, context.currentTime);
|
oscillatorNode.frequency.setValueAtTime(clrFreq, context.currentTime);
|
||||||
oscillatorNode.frequency.exponentialRampToValueAtTime(1400, context.currentTime + 0.2);
|
oscillatorNode.frequency.exponentialRampToValueAtTime(
|
||||||
|
1400,
|
||||||
|
context.currentTime + 0.2,
|
||||||
|
);
|
||||||
|
|
||||||
gainNode.gain.setValueAtTime(0.5, context.currentTime);
|
gainNode.gain.setValueAtTime(0.5, context.currentTime);
|
||||||
gainNode.gain.exponentialRampToValueAtTime(0.2, context.currentTime + 0.1);
|
gainNode.gain.exponentialRampToValueAtTime(
|
||||||
|
0.2,
|
||||||
|
context.currentTime + 0.1,
|
||||||
|
);
|
||||||
|
|
||||||
oscillatorNode.connect(gainNode);
|
oscillatorNode.connect(gainNode);
|
||||||
gainNode.connect(context.destination);
|
gainNode.connect(context.destination);
|
||||||
|
@ -114,11 +138,20 @@ export default (store) => (next) => (action) => {
|
||||||
|
|
||||||
oscillatorNode.type = 'sine';
|
oscillatorNode.type = 'sine';
|
||||||
oscillatorNode.frequency.setValueAtTime(349.23, context.currentTime);
|
oscillatorNode.frequency.setValueAtTime(349.23, context.currentTime);
|
||||||
oscillatorNode.frequency.setValueAtTime(523.25, context.currentTime + 0.1);
|
oscillatorNode.frequency.setValueAtTime(
|
||||||
oscillatorNode.frequency.setValueAtTime(698.46, context.currentTime + 0.2);
|
523.25,
|
||||||
|
context.currentTime + 0.1,
|
||||||
|
);
|
||||||
|
oscillatorNode.frequency.setValueAtTime(
|
||||||
|
698.46,
|
||||||
|
context.currentTime + 0.2,
|
||||||
|
);
|
||||||
|
|
||||||
gainNode.gain.setValueAtTime(0.5, context.currentTime);
|
gainNode.gain.setValueAtTime(0.5, context.currentTime);
|
||||||
gainNode.gain.exponentialRampToValueAtTime(0.2, context.currentTime + 0.15);
|
gainNode.gain.exponentialRampToValueAtTime(
|
||||||
|
0.2,
|
||||||
|
context.currentTime + 0.15,
|
||||||
|
);
|
||||||
|
|
||||||
oscillatorNode.connect(gainNode);
|
oscillatorNode.connect(gainNode);
|
||||||
gainNode.connect(context.destination);
|
gainNode.connect(context.destination);
|
||||||
|
@ -135,10 +168,16 @@ export default (store) => (next) => (action) => {
|
||||||
|
|
||||||
oscillatorNode.type = 'sine';
|
oscillatorNode.type = 'sine';
|
||||||
oscillatorNode.frequency.setValueAtTime(310, context.currentTime);
|
oscillatorNode.frequency.setValueAtTime(310, context.currentTime);
|
||||||
oscillatorNode.frequency.exponentialRampToValueAtTime(355, context.currentTime + 0.025);
|
oscillatorNode.frequency.exponentialRampToValueAtTime(
|
||||||
|
355,
|
||||||
|
context.currentTime + 0.025,
|
||||||
|
);
|
||||||
|
|
||||||
gainNode.gain.setValueAtTime(0.1, context.currentTime);
|
gainNode.gain.setValueAtTime(0.1, context.currentTime);
|
||||||
gainNode.gain.exponentialRampToValueAtTime(0.1, context.currentTime + 0.1);
|
gainNode.gain.exponentialRampToValueAtTime(
|
||||||
|
0.1,
|
||||||
|
context.currentTime + 0.1,
|
||||||
|
);
|
||||||
|
|
||||||
oscillatorNode.connect(gainNode);
|
oscillatorNode.connect(gainNode);
|
||||||
gainNode.connect(context.destination);
|
gainNode.connect(context.destination);
|
||||||
|
|
|
@ -35,6 +35,7 @@ export default () => (next) => (action) => {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
return next(action);
|
return next(action);
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function warn(error) {
|
function warn(error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.warn(error.message || error);
|
console.warn(error.message || error);
|
||||||
throw error; // To let the caller handle the rejection
|
throw error; // To let the caller handle the rejection
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,17 @@
|
||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import renderer from '../ui/Renderer';
|
import {
|
||||||
|
getRenderer,
|
||||||
|
initRenderer,
|
||||||
|
} from '../ui/renderer';
|
||||||
|
|
||||||
export default (store) => (next) => (action) => {
|
export default (store) => (next) => (action) => {
|
||||||
const { type } = action;
|
const { type } = action;
|
||||||
|
|
||||||
if (type == 'SET_HISTORICAL_TIME') {
|
if (type === 'SET_HISTORICAL_TIME') {
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
|
const renderer = getRenderer();
|
||||||
renderer.updateOldHistoricalTime(state.canvas.historicalTime);
|
renderer.updateOldHistoricalTime(state.canvas.historicalTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,15 +27,21 @@ export default (store) => (next) => (action) => {
|
||||||
case 'RELOAD_URL':
|
case 'RELOAD_URL':
|
||||||
case 'SELECT_CANVAS':
|
case 'SELECT_CANVAS':
|
||||||
case 'RECEIVE_ME': {
|
case 'RECEIVE_ME': {
|
||||||
|
const renderer = getRenderer();
|
||||||
|
const { is3D } = state.canvas;
|
||||||
|
if (is3D === renderer.is3D) {
|
||||||
renderer.updateCanvasData(state);
|
renderer.updateCanvasData(state);
|
||||||
|
} else {
|
||||||
|
initRenderer(store, is3D);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'SET_HISTORICAL_TIME':
|
case 'SET_HISTORICAL_TIME':
|
||||||
case 'REQUEST_BIG_CHUNK':
|
case 'REQUEST_BIG_CHUNK':
|
||||||
case 'RECEIVE_BIG_CHUNK':
|
case 'RECEIVE_BIG_CHUNK':
|
||||||
case 'RECEIVE_BIG_CHUNK_FAILURE':
|
case 'RECEIVE_BIG_CHUNK_FAILURE': {
|
||||||
case 'RECEIVE_IMAGE_TILE': {
|
const renderer = getRenderer();
|
||||||
renderer.forceNextRender = true;
|
renderer.forceNextRender = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -44,12 +54,26 @@ export default (store) => (next) => (action) => {
|
||||||
view,
|
view,
|
||||||
canvasSize,
|
canvasSize,
|
||||||
} = state.canvas;
|
} = state.canvas;
|
||||||
|
const renderer = getRenderer();
|
||||||
renderer.updateScale(viewscale, canvasMaxTiledZoom, view, canvasSize);
|
renderer.updateScale(viewscale, canvasMaxTiledZoom, view, canvasSize);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'RECEIVE_PIXEL_UPDATE': {
|
||||||
|
const {
|
||||||
|
i,
|
||||||
|
j,
|
||||||
|
offset,
|
||||||
|
color,
|
||||||
|
} = action;
|
||||||
|
const renderer = getRenderer();
|
||||||
|
renderer.renderPixel(i, j, offset, color);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'SET_VIEW_COORDINATES': {
|
case 'SET_VIEW_COORDINATES': {
|
||||||
const { view, canvasSize } = state.canvas;
|
const { view, canvasSize } = state.canvas;
|
||||||
|
const renderer = getRenderer();
|
||||||
renderer.updateView(view, canvasSize);
|
renderer.updateView(view, canvasSize);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@ const TITLE = 'PixelPlanet.fun';
|
||||||
let lastTitle = null;
|
let lastTitle = null;
|
||||||
|
|
||||||
export default (store) => (next) => (action) => {
|
export default (store) => (next) => (action) => {
|
||||||
|
const ret = next(action);
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'COOLDOWN_SET': {
|
case 'COOLDOWN_SET': {
|
||||||
const { coolDown } = store.getState().user;
|
const { coolDown } = store.getState().user;
|
||||||
|
@ -28,9 +30,26 @@ export default (store) => (next) => (action) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case 'SELECT_CANVAS':
|
||||||
|
case 'ON_VIEW_FINISH_CHANGE': {
|
||||||
|
const {
|
||||||
|
view,
|
||||||
|
viewscale,
|
||||||
|
canvasIdent,
|
||||||
|
} = store.getState().canvas;
|
||||||
|
let [x, y] = view;
|
||||||
|
x = Math.round(x);
|
||||||
|
y = Math.round(y);
|
||||||
|
const scale = Math.round(Math.log2(viewscale) * 10);
|
||||||
|
const newhash = `#${canvasIdent},${x},${y},${scale}`;
|
||||||
|
window.history.replaceState(undefined, undefined, newhash);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
return next(action);
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
201
src/ui/ChunkLoader2D.js
Normal file
201
src/ui/ChunkLoader2D.js
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
/*
|
||||||
|
* Fetching and storing of 2D chunks
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ChunkRGB from './ChunkRGB';
|
||||||
|
import { TILE_SIZE } from '../core/constants';
|
||||||
|
import {
|
||||||
|
loadingTiles,
|
||||||
|
loadImage,
|
||||||
|
} from './loadImage';
|
||||||
|
import {
|
||||||
|
requestBigChunk,
|
||||||
|
receiveBigChunk,
|
||||||
|
receiveBigChunkFailure,
|
||||||
|
} from '../actions';
|
||||||
|
import {
|
||||||
|
getCellInsideChunk,
|
||||||
|
getChunkOfPixel,
|
||||||
|
} from '../core/utils';
|
||||||
|
|
||||||
|
class ChunkLoader {
|
||||||
|
store = null;
|
||||||
|
canvasId: number;
|
||||||
|
canvasMaxTiledZoom: number;
|
||||||
|
palette;
|
||||||
|
chunks: Map<string, ChunkRGB>;
|
||||||
|
|
||||||
|
constructor(store) {
|
||||||
|
this.store = store;
|
||||||
|
const state = store.getState();
|
||||||
|
const {
|
||||||
|
canvasId,
|
||||||
|
canvasMaxTiledZoom,
|
||||||
|
palette,
|
||||||
|
} = state.canvas;
|
||||||
|
this.canvasId = canvasId;
|
||||||
|
this.canvasMaxTiledZoom = canvasMaxTiledZoom;
|
||||||
|
this.palette = palette;
|
||||||
|
this.chunks = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllChunks() {
|
||||||
|
return this.chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPixelUpdate(
|
||||||
|
cx: number,
|
||||||
|
cy: number,
|
||||||
|
offset: number,
|
||||||
|
color: number,
|
||||||
|
) {
|
||||||
|
const chunk = this.chunks.get(`${this.canvasMaxTiledZoom}:${cx}:${cy}`);
|
||||||
|
if (chunk) {
|
||||||
|
const ix = offset % TILE_SIZE;
|
||||||
|
const iy = Math.floor(offset / TILE_SIZE);
|
||||||
|
chunk.setColor([ix, iy], color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getColorIndexOfPixel(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
) {
|
||||||
|
const state: State = this.store.getState();
|
||||||
|
const { canvasSize } = state.canvas;
|
||||||
|
const [cx, cy] = getChunkOfPixel(canvasSize, x, y);
|
||||||
|
const key = `${this.canvasMaxTiledZoom}:${cx}:${cy}`;
|
||||||
|
const chunk = this.chunks.get(key);
|
||||||
|
if (!chunk) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return chunk.getColorIndex(
|
||||||
|
getCellInsideChunk([x, y]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getChunk(zoom, cx: number, cy: number, fetch: boolean) {
|
||||||
|
const chunkKey = `${zoom}:${cx}:${cy}`;
|
||||||
|
const chunk = this.chunks.get(chunkKey);
|
||||||
|
const { canvasId } = this;
|
||||||
|
if (chunk) {
|
||||||
|
if (chunk.ready) {
|
||||||
|
return chunk.image;
|
||||||
|
}
|
||||||
|
return loadingTiles.getTile(canvasId);
|
||||||
|
} if (fetch) {
|
||||||
|
// fetch chunk
|
||||||
|
const chunkRGB = new ChunkRGB(this.palette, chunkKey);
|
||||||
|
this.chunks.set(chunkKey, chunkRGB);
|
||||||
|
|
||||||
|
if (this.canvasMaxTiledZoom === zoom) {
|
||||||
|
this.fetchBaseChunk(zoom, cx, cy, chunkRGB);
|
||||||
|
} else {
|
||||||
|
this.fetchTile(zoom, cx, cy, chunkRGB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return loadingTiles.getTile(canvasId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getHistoricalChunk(cx, cy, fetch, historicalDate, historicalTime = null) {
|
||||||
|
let chunkKey = (historicalTime)
|
||||||
|
? `${historicalDate}${historicalTime}`
|
||||||
|
: historicalDate;
|
||||||
|
chunkKey += `:${cx}:${cy}`;
|
||||||
|
const chunk = this.chunks.get(chunkKey);
|
||||||
|
const { canvasId } = this;
|
||||||
|
if (chunk) {
|
||||||
|
if (chunk.ready) {
|
||||||
|
return chunk.image;
|
||||||
|
}
|
||||||
|
return (historicalTime) ? null : loadingTiles.getTile(canvasId);
|
||||||
|
} if (fetch) {
|
||||||
|
// fetch tile
|
||||||
|
const chunkRGB = new ChunkRGB(this.palette, chunkKey);
|
||||||
|
this.chunks.set(chunkKey, chunkRGB);
|
||||||
|
this.fetchHistoricalChunk(
|
||||||
|
cx,
|
||||||
|
cy,
|
||||||
|
historicalDate,
|
||||||
|
historicalTime,
|
||||||
|
chunkRGB,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (historicalTime) ? null : loadingTiles.getTile(canvasId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchHistoricalChunk(
|
||||||
|
cx: number,
|
||||||
|
cy: number,
|
||||||
|
historicalDate: string,
|
||||||
|
historicalTime: string,
|
||||||
|
chunkRGB,
|
||||||
|
) {
|
||||||
|
const { canvasId } = this;
|
||||||
|
const { key } = chunkRGB;
|
||||||
|
let url = `${window.backupurl}/${historicalDate}/`;
|
||||||
|
if (historicalTime) {
|
||||||
|
// incremential tiles
|
||||||
|
url += `${canvasId}/${historicalTime}/${cx}/${cy}.png`;
|
||||||
|
} else {
|
||||||
|
// full tiles
|
||||||
|
url += `${canvasId}/tiles/${cx}/${cy}.png`;
|
||||||
|
}
|
||||||
|
this.store.dispatch(requestBigChunk(key));
|
||||||
|
try {
|
||||||
|
const img = await loadImage(url);
|
||||||
|
chunkRGB.fromImage(img);
|
||||||
|
this.store.dispatch(receiveBigChunk(key));
|
||||||
|
} catch (error) {
|
||||||
|
this.store.dispatch(receiveBigChunkFailure(key, error));
|
||||||
|
if (historicalTime) {
|
||||||
|
chunkRGB.empty(true);
|
||||||
|
} else {
|
||||||
|
chunkRGB.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchBaseChunk(zoom, cx: number, cy: number, chunkRGB) {
|
||||||
|
const center = [zoom, cx, cy];
|
||||||
|
this.store.dispatch(requestBigChunk(center));
|
||||||
|
chunkRGB.isBasechunk = true;
|
||||||
|
try {
|
||||||
|
const url = `/chunks/${this.canvasId}/${cx}/${cy}.bmp`;
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (response.ok) {
|
||||||
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
if (arrayBuffer.byteLength) {
|
||||||
|
const chunkArray = new Uint8Array(arrayBuffer);
|
||||||
|
chunkRGB.fromBuffer(chunkArray);
|
||||||
|
} else {
|
||||||
|
throw new Error('Chunk response was invalid');
|
||||||
|
}
|
||||||
|
this.store.dispatch(receiveBigChunk(center));
|
||||||
|
} else {
|
||||||
|
throw new Error('Network response was not ok.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
chunkRGB.empty();
|
||||||
|
this.store.dispatch(receiveBigChunkFailure(center, error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchTile(zoom, cx: number, cy: number, chunkRGB) {
|
||||||
|
const center = [zoom, cx, cy];
|
||||||
|
this.store.dispatch(requestBigChunk(center));
|
||||||
|
try {
|
||||||
|
const url = `/tiles/${this.canvasId}/${zoom}/${cx}/${cy}.png`;
|
||||||
|
const img = await loadImage(url);
|
||||||
|
chunkRGB.fromImage(img);
|
||||||
|
this.store.dispatch(receiveBigChunk(center));
|
||||||
|
} catch (error) {
|
||||||
|
this.store.dispatch(receiveBigChunkFailure(center, error));
|
||||||
|
chunkRGB.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChunkLoader;
|
|
@ -7,7 +7,6 @@ import { TILE_SIZE } from '../core/constants';
|
||||||
|
|
||||||
|
|
||||||
class ChunkRGB {
|
class ChunkRGB {
|
||||||
cell: Cell;
|
|
||||||
key: string;
|
key: string;
|
||||||
image: HTMLCanvasElement;
|
image: HTMLCanvasElement;
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
|
@ -15,7 +14,7 @@ class ChunkRGB {
|
||||||
palette: Palette;
|
palette: Palette;
|
||||||
isBasechunk: boolean;
|
isBasechunk: boolean;
|
||||||
|
|
||||||
constructor(palette: Palette, cell: Cell) {
|
constructor(palette: Palette, key) {
|
||||||
// isBasechunk gets set to true by RECEIVE_BIG_CHUNK
|
// isBasechunk gets set to true by RECEIVE_BIG_CHUNK
|
||||||
// if true => chunk got requested from api/chunk and
|
// if true => chunk got requested from api/chunk and
|
||||||
// receives websocket pixel updates
|
// receives websocket pixel updates
|
||||||
|
@ -25,10 +24,8 @@ class ChunkRGB {
|
||||||
this.image = document.createElement('canvas');
|
this.image = document.createElement('canvas');
|
||||||
this.image.width = TILE_SIZE;
|
this.image.width = TILE_SIZE;
|
||||||
this.image.height = TILE_SIZE;
|
this.image.height = TILE_SIZE;
|
||||||
this.cell = cell;
|
this.key = key;
|
||||||
this.key = ChunkRGB.getKey(...cell);
|
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
this.isEmpty = false;
|
|
||||||
this.timestamp = Date.now();
|
this.timestamp = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,21 +86,15 @@ class ChunkRGB {
|
||||||
ctx.drawImage(img, 0, 0);
|
ctx.drawImage(img, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
empty() {
|
empty(transparent: boolean = false) {
|
||||||
this.ready = true;
|
this.ready = true;
|
||||||
this.isEmpty = true;
|
if (!transparent) {
|
||||||
const { image, palette } = this;
|
const { image, palette } = this;
|
||||||
const ctx = image.getContext('2d');
|
const ctx = image.getContext('2d');
|
||||||
// eslint-disable-next-line prefer-destructuring
|
// eslint-disable-next-line prefer-destructuring
|
||||||
ctx.fillStyle = palette.colors[0];
|
ctx.fillStyle = palette.colors[0];
|
||||||
ctx.fillRect(0, 0, TILE_SIZE, TILE_SIZE);
|
ctx.fillRect(0, 0, TILE_SIZE, TILE_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getKey(z: number, x: number, y: number) {
|
|
||||||
// this is also hardcoded into core/utils.js at getColorIndexOfPixel
|
|
||||||
// just to prevent whole ChunkRGB to get loaded into web.js
|
|
||||||
// ...could test that at some point if really neccessary
|
|
||||||
return `${z}:${x}:${y}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static getIndexFromCell([x, y]: Cell): number {
|
static getIndexFromCell([x, y]: Cell): number {
|
||||||
|
|
|
@ -47,7 +47,7 @@ class PixelNotify {
|
||||||
|
|
||||||
|
|
||||||
doRender() {
|
doRender() {
|
||||||
return (this.pixelList.length != 0);
|
return (this.pixelList.length !== 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,13 +75,18 @@ class PixelNotify {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const [sx, sy] = worldToScreen(state, $viewport, [x, y])
|
const [sx, sy] = worldToScreen(state, $viewport, [x, y])
|
||||||
.map((x) => x + this.scale / 2);
|
.map((z) => z + this.scale / 2);
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
const notRadius = timePasseded / PixelNotify.NOTIFICATION_TIME * this.notificationRadius;
|
const notRadius = timePasseded / PixelNotify.NOTIFICATION_TIME * this.notificationRadius;
|
||||||
const circleScale = notRadius / 100;
|
const circleScale = notRadius / 100;
|
||||||
viewportCtx.save();
|
viewportCtx.save();
|
||||||
viewportCtx.scale(circleScale, circleScale);
|
viewportCtx.scale(circleScale, circleScale);
|
||||||
viewportCtx.drawImage(this.notifcircle, sx / circleScale - 100, sy / circleScale - 100);
|
viewportCtx.drawImage(
|
||||||
|
this.notifcircle,
|
||||||
|
sx / circleScale - 100,
|
||||||
|
sy / circleScale - 100,
|
||||||
|
);
|
||||||
viewportCtx.restore();
|
viewportCtx.restore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
/*
|
/*
|
||||||
|
* Renders 2D canvases
|
||||||
*
|
*
|
||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
|
@ -11,21 +12,19 @@ import {
|
||||||
getTileOfPixel,
|
getTileOfPixel,
|
||||||
getPixelFromChunkOffset,
|
getPixelFromChunkOffset,
|
||||||
} from '../core/utils';
|
} from '../core/utils';
|
||||||
import {
|
|
||||||
fetchChunk,
|
|
||||||
fetchTile,
|
|
||||||
fetchHistoricalChunk,
|
|
||||||
} from '../actions';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
renderGrid,
|
renderGrid,
|
||||||
renderPlaceholder,
|
renderPlaceholder,
|
||||||
renderPotatoPlaceholder,
|
renderPotatoPlaceholder,
|
||||||
} from './renderelements';
|
} from './render2Delements';
|
||||||
import ChunkRGB from './ChunkRGB';
|
import {
|
||||||
import { loadingTiles } from './loadImage';
|
initControls,
|
||||||
|
removeControls,
|
||||||
|
} from '../controls/PixelPainterControls';
|
||||||
|
|
||||||
|
|
||||||
|
import ChunkLoader from './ChunkLoader2D';
|
||||||
import pixelNotify from './PixelNotify';
|
import pixelNotify from './PixelNotify';
|
||||||
|
|
||||||
// dimensions of offscreen canvas NOT whole canvas
|
// dimensions of offscreen canvas NOT whole canvas
|
||||||
|
@ -38,6 +37,11 @@ const SCALE_THREASHOLD = Math.min(
|
||||||
|
|
||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
|
is3D: false;
|
||||||
|
//
|
||||||
|
canvasId: number = null;
|
||||||
|
chunkLoader: Object = null;
|
||||||
|
//--
|
||||||
centerChunk: Cell;
|
centerChunk: Cell;
|
||||||
tiledScale: number;
|
tiledScale: number;
|
||||||
tiledZoom: number;
|
tiledZoom: number;
|
||||||
|
@ -53,7 +57,7 @@ class Renderer {
|
||||||
//--
|
//--
|
||||||
oldHistoricalTime: string;
|
oldHistoricalTime: string;
|
||||||
|
|
||||||
constructor() {
|
constructor(store) {
|
||||||
this.centerChunk = [null, null];
|
this.centerChunk = [null, null];
|
||||||
this.tiledScale = 0;
|
this.tiledScale = 0;
|
||||||
this.tiledZoom = 4;
|
this.tiledZoom = 4;
|
||||||
|
@ -64,20 +68,44 @@ class Renderer {
|
||||||
this.lastFetch = 0;
|
this.lastFetch = 0;
|
||||||
this.oldHistoricalTime = null;
|
this.oldHistoricalTime = null;
|
||||||
//--
|
//--
|
||||||
|
const viewport = document.createElement('canvas');
|
||||||
|
viewport.width = window.innerWidth;
|
||||||
|
viewport.height = window.innerHeight;
|
||||||
|
this.viewport = viewport;
|
||||||
|
document.body.appendChild(this.viewport);
|
||||||
|
//--
|
||||||
|
this.resizeHandle = this.resizeHandle.bind(this);
|
||||||
|
window.addEventListener('resize', this.resizeHandle);
|
||||||
|
//--
|
||||||
this.canvas = document.createElement('canvas');
|
this.canvas = document.createElement('canvas');
|
||||||
this.canvas.width = CANVAS_WIDTH;
|
this.canvas.width = CANVAS_WIDTH;
|
||||||
this.canvas.height = CANVAS_HEIGHT;
|
this.canvas.height = CANVAS_HEIGHT;
|
||||||
|
|
||||||
const context = this.canvas.getContext('2d');
|
const context = this.canvas.getContext('2d');
|
||||||
if (!context) return;
|
|
||||||
|
|
||||||
context.fillStyle = '#000000';
|
context.fillStyle = '#000000';
|
||||||
context.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
context.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
||||||
|
//--
|
||||||
|
this.setStore(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
destructor() {
|
||||||
|
removeControls(this.viewport);
|
||||||
|
window.removeEventListener('resize', this.resizeHandle);
|
||||||
|
this.viewport.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllChunks() {
|
||||||
|
return this.chunkLoader.getAllChunks();
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeHandle() {
|
||||||
|
this.viewport.width = window.innerWidth;
|
||||||
|
this.viewport.height = window.innerHeight;
|
||||||
|
this.forceNextRender = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// HAS to be set before any rendering can happen
|
// HAS to be set before any rendering can happen
|
||||||
setViewport(viewport: HTMLCanvasElement, store) {
|
setStore(store) {
|
||||||
this.viewport = viewport;
|
|
||||||
this.store = store;
|
this.store = store;
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
const {
|
const {
|
||||||
|
@ -87,8 +115,8 @@ class Renderer {
|
||||||
canvasSize,
|
canvasSize,
|
||||||
} = state.canvas;
|
} = state.canvas;
|
||||||
this.updateCanvasData(state);
|
this.updateCanvasData(state);
|
||||||
|
initControls(this, this.viewport, store);
|
||||||
this.updateScale(viewscale, canvasMaxTiledZoom, view, canvasSize);
|
this.updateScale(viewscale, canvasMaxTiledZoom, view, canvasSize);
|
||||||
this.forceNextRender = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCanvasData(state: State) {
|
updateCanvasData(state: State) {
|
||||||
|
@ -97,7 +125,14 @@ class Renderer {
|
||||||
viewscale,
|
viewscale,
|
||||||
view,
|
view,
|
||||||
canvasSize,
|
canvasSize,
|
||||||
|
canvasId,
|
||||||
} = state.canvas;
|
} = state.canvas;
|
||||||
|
if (canvasId !== this.canvasId) {
|
||||||
|
this.canvasId = canvasId;
|
||||||
|
if (canvasId !== null) {
|
||||||
|
this.chunkLoader = new ChunkLoader(this.store);
|
||||||
|
}
|
||||||
|
}
|
||||||
this.updateScale(viewscale, canvasMaxTiledZoom, view, canvasSize);
|
this.updateScale(viewscale, canvasMaxTiledZoom, view, canvasSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,6 +144,10 @@ class Renderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getColorIndexOfPixel(cx, cy) {
|
||||||
|
return this.chunkLoader.getColorIndexOfPixel(cx, cy);
|
||||||
|
}
|
||||||
|
|
||||||
updateScale(
|
updateScale(
|
||||||
viewscale: number,
|
viewscale: number,
|
||||||
canvasMaxTiledZoom: number,
|
canvasMaxTiledZoom: number,
|
||||||
|
@ -156,6 +195,7 @@ class Renderer {
|
||||||
scale,
|
scale,
|
||||||
isHistoricalView,
|
isHistoricalView,
|
||||||
} = state.canvas;
|
} = state.canvas;
|
||||||
|
this.chunkLoader.getPixelUpdate(i, j, offset, color);
|
||||||
|
|
||||||
if (scale < 0.8 || isHistoricalView) return;
|
if (scale < 0.8 || isHistoricalView) return;
|
||||||
const scaleM = (scale > SCALE_THREASHOLD) ? 1 : scale;
|
const scaleM = (scale > SCALE_THREASHOLD) ? 1 : scale;
|
||||||
|
@ -216,10 +256,7 @@ class Renderer {
|
||||||
} = this;
|
} = this;
|
||||||
const {
|
const {
|
||||||
viewscale: scale,
|
viewscale: scale,
|
||||||
canvasId,
|
|
||||||
canvasSize,
|
canvasSize,
|
||||||
canvasMaxTiledZoom,
|
|
||||||
chunks,
|
|
||||||
} = state.canvas;
|
} = state.canvas;
|
||||||
|
|
||||||
let { relScale } = this;
|
let { relScale } = this;
|
||||||
|
@ -264,8 +301,7 @@ class Renderer {
|
||||||
const [xc, yc] = chunkPosition; // center chunk
|
const [xc, yc] = chunkPosition; // center chunk
|
||||||
// CLEAN margin
|
// CLEAN margin
|
||||||
// draw new chunks. If not existing, just clear.
|
// draw new chunks. If not existing, just clear.
|
||||||
let chunk: ChunkRGB;
|
let chunk;
|
||||||
let key: string;
|
|
||||||
for (let dx = -CHUNK_RENDER_RADIUS_X; dx <= CHUNK_RENDER_RADIUS_X; dx += 1) {
|
for (let dx = -CHUNK_RENDER_RADIUS_X; dx <= CHUNK_RENDER_RADIUS_X; dx += 1) {
|
||||||
for (let dy = -CHUNK_RENDER_RADIUS_Y; dy <= CHUNK_RENDER_RADIUS_Y; dy += 1) {
|
for (let dy = -CHUNK_RENDER_RADIUS_Y; dy <= CHUNK_RENDER_RADIUS_Y; dy += 1) {
|
||||||
const cx = xc + dx;
|
const cx = xc + dx;
|
||||||
|
@ -278,33 +314,12 @@ class Renderer {
|
||||||
// if out of bounds
|
// if out of bounds
|
||||||
context.fillRect(x, y, TILE_SIZE, TILE_SIZE);
|
context.fillRect(x, y, TILE_SIZE, TILE_SIZE);
|
||||||
} else {
|
} else {
|
||||||
key = ChunkRGB.getKey(tiledZoom, cx, cy);
|
chunk = this.chunkLoader.getChunk(tiledZoom, cx, cy, fetch);
|
||||||
chunk = chunks.get(key);
|
|
||||||
if (chunk) {
|
if (chunk) {
|
||||||
// render new chunk
|
context.drawImage(chunk, x, y);
|
||||||
if (chunk.ready) {
|
|
||||||
context.drawImage(chunk.image, x, y);
|
|
||||||
if (fetch) chunk.timestamp = curTime;
|
|
||||||
} else if (loadingTiles.hasTiles) {
|
|
||||||
context.drawImage(loadingTiles.getTile(canvasId), x, y);
|
|
||||||
} else {
|
} else {
|
||||||
context.fillRect(x, y, TILE_SIZE, TILE_SIZE);
|
context.fillRect(x, y, TILE_SIZE, TILE_SIZE);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// we don't have that chunk
|
|
||||||
if (fetch) {
|
|
||||||
if (tiledZoom === canvasMaxTiledZoom) {
|
|
||||||
this.store.dispatch(fetchChunk(canvasId, [tiledZoom, cx, cy]));
|
|
||||||
} else {
|
|
||||||
this.store.dispatch(fetchTile(canvasId, [tiledZoom, cx, cy]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (loadingTiles.hasTiles) {
|
|
||||||
context.drawImage(loadingTiles.getTile(canvasId), x, y);
|
|
||||||
} else {
|
|
||||||
context.fillRect(x, y, TILE_SIZE, TILE_SIZE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -313,10 +328,15 @@ class Renderer {
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
if (!this.chunkLoader) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const state: State = this.store.getState();
|
const state: State = this.store.getState();
|
||||||
return (state.canvas.isHistoricalView)
|
if (state.canvas.isHistoricalView) {
|
||||||
? this.renderHistorical(state)
|
this.renderHistorical(state);
|
||||||
: this.renderMain(state);
|
} else {
|
||||||
|
this.renderMain(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -342,19 +362,29 @@ class Renderer {
|
||||||
view,
|
view,
|
||||||
viewscale,
|
viewscale,
|
||||||
canvasSize,
|
canvasSize,
|
||||||
canvasId,
|
|
||||||
} = state.canvas;
|
} = state.canvas;
|
||||||
|
|
||||||
if (!view || canvasId === null) return;
|
|
||||||
|
|
||||||
const [x, y] = view;
|
const [x, y] = view;
|
||||||
const [cx, cy] = this.centerChunk;
|
const [cx, cy] = this.centerChunk;
|
||||||
|
|
||||||
// if we have to render pixelnotify
|
// if we have to render pixelnotify
|
||||||
const doRenderPixelnotify = (viewscale >= 0.5 && showPixelNotify && pixelNotify.doRender());
|
const doRenderPixelnotify = (viewscale >= 0.5 && showPixelNotify && pixelNotify.doRender());
|
||||||
// if we have to render placeholder
|
// if we have to render placeholder
|
||||||
const doRenderPlaceholder = (viewscale >= 3 && placeAllowed && (hover || this.hover) && !isPotato);
|
const doRenderPlaceholder = (
|
||||||
const doRenderPotatoPlaceholder = (viewscale >= 3 && placeAllowed && (hover !== this.hover || this.forceNextRender || this.forceNextSubrender || doRenderPixelnotify) && isPotato);
|
viewscale >= 3
|
||||||
|
&& placeAllowed
|
||||||
|
&& (hover || this.hover)
|
||||||
|
&& !isPotato
|
||||||
|
);
|
||||||
|
const doRenderPotatoPlaceholder = (
|
||||||
|
viewscale >= 3
|
||||||
|
&& placeAllowed
|
||||||
|
&& (hover !== this.hover
|
||||||
|
|| this.forceNextRender
|
||||||
|
|| this.forceNextSubrender
|
||||||
|
|| doRenderPixelnotify
|
||||||
|
) && isPotato
|
||||||
|
);
|
||||||
//--
|
//--
|
||||||
// if we have nothing to render, return
|
// if we have nothing to render, return
|
||||||
// note: this.hover is used to, to render without the placeholder one last time when cursor leaves window
|
// note: this.hover is used to, to render without the placeholder one last time when cursor leaves window
|
||||||
|
@ -425,9 +455,7 @@ class Renderer {
|
||||||
} = this;
|
} = this;
|
||||||
const {
|
const {
|
||||||
viewscale,
|
viewscale,
|
||||||
canvasId,
|
|
||||||
canvasSize,
|
canvasSize,
|
||||||
chunks,
|
|
||||||
historicalDate,
|
historicalDate,
|
||||||
historicalTime,
|
historicalTime,
|
||||||
} = state.canvas;
|
} = state.canvas;
|
||||||
|
@ -479,8 +507,7 @@ class Renderer {
|
||||||
const [xc, yc] = chunkPosition; // center chunk
|
const [xc, yc] = chunkPosition; // center chunk
|
||||||
// CLEAN margin
|
// CLEAN margin
|
||||||
// draw chunks. If not existing, just clear.
|
// draw chunks. If not existing, just clear.
|
||||||
let chunk: ChunkRGB;
|
let chunk;
|
||||||
let key: string;
|
|
||||||
for (let dx = -CHUNK_RENDER_RADIUS_X; dx <= CHUNK_RENDER_RADIUS_X; dx += 1) {
|
for (let dx = -CHUNK_RENDER_RADIUS_X; dx <= CHUNK_RENDER_RADIUS_X; dx += 1) {
|
||||||
for (let dy = -CHUNK_RENDER_RADIUS_Y; dy <= CHUNK_RENDER_RADIUS_Y; dy += 1) {
|
for (let dy = -CHUNK_RENDER_RADIUS_Y; dy <= CHUNK_RENDER_RADIUS_Y; dy += 1) {
|
||||||
const cx = xc + dx;
|
const cx = xc + dx;
|
||||||
|
@ -494,55 +521,21 @@ class Renderer {
|
||||||
context.fillRect(x, y, TILE_SIZE, TILE_SIZE);
|
context.fillRect(x, y, TILE_SIZE, TILE_SIZE);
|
||||||
} else {
|
} else {
|
||||||
// full chunks
|
// full chunks
|
||||||
key = ChunkRGB.getKey(historicalDate, cx, cy);
|
chunk = this.chunkLoader.getHistoricalChunk(cx, cy, fetch, historicalDate);
|
||||||
chunk = chunks.get(key);
|
|
||||||
if (chunk) {
|
if (chunk) {
|
||||||
// render new chunk
|
context.drawImage(chunk, x, y);
|
||||||
if (chunk.ready) {
|
|
||||||
context.drawImage(chunk.image, x, y);
|
|
||||||
if (fetch) chunk.timestamp = curTime;
|
|
||||||
} else if (loadingTiles.hasTiles) {
|
|
||||||
context.drawImage(loadingTiles.getTile(canvasId), x, y);
|
|
||||||
} else {
|
} else {
|
||||||
context.fillRect(x, y, TILE_SIZE, TILE_SIZE);
|
context.fillRect(x, y, TILE_SIZE, TILE_SIZE);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// we don't have that chunk
|
|
||||||
if (fetch) {
|
|
||||||
this.store.dispatch(fetchHistoricalChunk(canvasId, [cx, cy], historicalDate, null));
|
|
||||||
}
|
|
||||||
if (loadingTiles.hasTiles) {
|
|
||||||
context.drawImage(loadingTiles.getTile(canvasId), x, y);
|
|
||||||
} else {
|
|
||||||
context.fillRect(x, y, TILE_SIZE, TILE_SIZE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// incremential chunks
|
// incremential chunks
|
||||||
if (historicalTime === '0000') continue;
|
if (historicalTime === '0000') continue;
|
||||||
key = ChunkRGB.getKey(`${historicalDate}${historicalTime}`, cx, cy);
|
chunk = this.chunkLoader.getHistoricalChunk(cx, cy, fetch, historicalDate, historicalTime);
|
||||||
chunk = chunks.get(key);
|
|
||||||
if (chunk) {
|
if (chunk) {
|
||||||
// render new chunk
|
context.drawImage(chunk, x, y);
|
||||||
if (!chunk.ready && oldHistoricalTime) {
|
} else if (oldHistoricalTime) {
|
||||||
// redraw previous incremential chunk if new one is not there yet
|
chunk = this.chunkLoader.getHistoricalChunk(cx, cy, false, historicalDate, oldHistoricalTime);
|
||||||
key = ChunkRGB.getKey(`${historicalDate}${oldHistoricalTime}`, cx, cy);
|
if (chunk) {
|
||||||
chunk = chunks.get(key);
|
context.drawImage(chunk, x, y);
|
||||||
}
|
|
||||||
if (chunk && chunk.ready && !chunk.isEmpty) {
|
|
||||||
context.drawImage(chunk.image, x, y);
|
|
||||||
if (fetch) chunk.timestamp = curTime;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (fetch) {
|
|
||||||
// we don't have that chunk
|
|
||||||
this.store.dispatch(fetchHistoricalChunk(canvasId, [cx, cy], historicalDate, historicalTime));
|
|
||||||
}
|
|
||||||
if (oldHistoricalTime) {
|
|
||||||
key = ChunkRGB.getKey(`${historicalDate}${oldHistoricalTime}`, cx, cy);
|
|
||||||
chunk = chunks.get(key);
|
|
||||||
if (chunk && chunk.ready && !chunk.isEmpty) {
|
|
||||||
context.drawImage(chunk.image, x, y);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -612,5 +605,4 @@ class Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const renderer = new Renderer();
|
export default Renderer;
|
||||||
export default renderer;
|
|
279
src/ui/Renderer3D.js
Normal file
279
src/ui/Renderer3D.js
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
/*
|
||||||
|
* 3D Renderer for VoxelCanvas
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import VoxelPainterControls from '../controls/VoxelPainterControls';
|
||||||
|
import {
|
||||||
|
setHover,
|
||||||
|
} from '../actions';
|
||||||
|
|
||||||
|
|
||||||
|
class Renderer {
|
||||||
|
is3D = true;
|
||||||
|
//--
|
||||||
|
store;
|
||||||
|
//--
|
||||||
|
scene: Object;
|
||||||
|
camera: Object;
|
||||||
|
rollOverMesh: Object;
|
||||||
|
voxel: Object;
|
||||||
|
voxelMaterials: Array<Object>;
|
||||||
|
objects: Array<Object>;
|
||||||
|
plane: Object;
|
||||||
|
//--
|
||||||
|
controls: Object;
|
||||||
|
threeRenderer: Object;
|
||||||
|
//--
|
||||||
|
mouse;
|
||||||
|
raycaster;
|
||||||
|
pressTime: number;
|
||||||
|
|
||||||
|
constructor(store) {
|
||||||
|
this.store = store;
|
||||||
|
const state = store.getState();
|
||||||
|
this.objects = [];
|
||||||
|
|
||||||
|
// camera
|
||||||
|
const camera = new THREE.PerspectiveCamera(
|
||||||
|
45,
|
||||||
|
window.innerWidth / window.innerHeight,
|
||||||
|
1,
|
||||||
|
2000,
|
||||||
|
);
|
||||||
|
camera.position.set(100, 160, 260);
|
||||||
|
camera.lookAt(0, 0, 0);
|
||||||
|
this.camera = camera;
|
||||||
|
|
||||||
|
// scene
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
scene.background = new THREE.Color(0xf0f0f0);
|
||||||
|
this.scene = scene;
|
||||||
|
|
||||||
|
// hover helper
|
||||||
|
const rollOverGeo = new THREE.BoxBufferGeometry(10, 10, 10);
|
||||||
|
const rollOverMaterial = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0xff0000,
|
||||||
|
opacity: 0.5,
|
||||||
|
transparent: true,
|
||||||
|
});
|
||||||
|
this.rollOverMesh = new THREE.Mesh(rollOverGeo, rollOverMaterial);
|
||||||
|
scene.add(this.rollOverMesh);
|
||||||
|
|
||||||
|
// cubes
|
||||||
|
this.voxel = new THREE.BoxBufferGeometry(10, 10, 10);
|
||||||
|
this.initCubeMaterials(state);
|
||||||
|
|
||||||
|
// grid
|
||||||
|
const gridHelper = new THREE.GridHelper(1000, 100, 0x555555, 0x555555);
|
||||||
|
scene.add(gridHelper);
|
||||||
|
|
||||||
|
//
|
||||||
|
this.raycaster = new THREE.Raycaster();
|
||||||
|
this.mouse = new THREE.Vector2();
|
||||||
|
|
||||||
|
// Plane Floor
|
||||||
|
const geometry = new THREE.PlaneBufferGeometry(5000, 5000);
|
||||||
|
geometry.rotateX(-Math.PI / 2);
|
||||||
|
const plane = new THREE.Mesh(
|
||||||
|
geometry,
|
||||||
|
new THREE.MeshLambertMaterial({ color: 0xcae3ff }),
|
||||||
|
);
|
||||||
|
scene.add(plane);
|
||||||
|
this.plane = plane;
|
||||||
|
this.objects.push(plane);
|
||||||
|
|
||||||
|
// lights
|
||||||
|
const ambientLight = new THREE.AmbientLight(0x606060);
|
||||||
|
scene.add(ambientLight);
|
||||||
|
|
||||||
|
const directionalLight = new THREE.DirectionalLight(0xffffff);
|
||||||
|
directionalLight.position.set(1, 0.75, 0.5).normalize();
|
||||||
|
scene.add(directionalLight);
|
||||||
|
|
||||||
|
// renderer
|
||||||
|
const threeRenderer = new THREE.WebGLRenderer({ antialias: true });
|
||||||
|
threeRenderer.setPixelRatio(window.devicePixelRatio);
|
||||||
|
threeRenderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
|
document.body.appendChild(threeRenderer.domElement);
|
||||||
|
this.threeRenderer = threeRenderer;
|
||||||
|
|
||||||
|
// controls
|
||||||
|
const controls = new VoxelPainterControls(camera, threeRenderer.domElement);
|
||||||
|
controls.enableDamping = true;
|
||||||
|
controls.dampingFactor = 0.75;
|
||||||
|
controls.maxPolarAngle = Math.PI / 2;
|
||||||
|
controls.minDistance = 100.00;
|
||||||
|
controls.maxDistance = 1000.00;
|
||||||
|
this.controls = controls;
|
||||||
|
|
||||||
|
const { domElement } = threeRenderer;
|
||||||
|
|
||||||
|
this.onDocumentMouseMove = this.onDocumentMouseMove.bind(this);
|
||||||
|
this.onDocumentMouseDown = this.onDocumentMouseDown.bind(this);
|
||||||
|
this.onDocumentMouseUp = this.onDocumentMouseUp.bind(this);
|
||||||
|
this.onWindowResize = this.onWindowResize.bind(this);
|
||||||
|
domElement.addEventListener('mousemove', this.onDocumentMouseMove, false);
|
||||||
|
domElement.addEventListener('mousedown', this.onDocumentMouseDown, false);
|
||||||
|
domElement.addEventListener('mouseup', this.onDocumentMouseUp, false);
|
||||||
|
window.addEventListener('resize', this.onWindowResize, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
destructor() {
|
||||||
|
window.addEventListener('resize', this.onWindowResize, false);
|
||||||
|
this.threeRenderer.dispose();
|
||||||
|
const { domElement } = this.threeRenderer;
|
||||||
|
this.threeRenderer = null;
|
||||||
|
domElement.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
static getAllChunks() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
initCubeMaterials(state) {
|
||||||
|
const { palette } = state.canvas;
|
||||||
|
const { colors } = palette;
|
||||||
|
const cubeMaterials = [];
|
||||||
|
for (let index = 0; index < colors.length; index++) {
|
||||||
|
const material = new THREE.MeshLambertMaterial({
|
||||||
|
color: colors[index],
|
||||||
|
});
|
||||||
|
cubeMaterials.push(material);
|
||||||
|
}
|
||||||
|
this.voxelMaterials = cubeMaterials;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.threeRenderer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.controls.update();
|
||||||
|
this.threeRenderer.render(this.scene, this.camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowResize() {
|
||||||
|
this.camera.aspect = window.innerWidth / window.innerHeight;
|
||||||
|
this.camera.updateProjectionMatrix();
|
||||||
|
this.threeRenderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDocumentMouseMove(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const {
|
||||||
|
clientX,
|
||||||
|
clientY,
|
||||||
|
} = event;
|
||||||
|
const {
|
||||||
|
innerWidth,
|
||||||
|
innerHeight,
|
||||||
|
} = window;
|
||||||
|
const {
|
||||||
|
camera,
|
||||||
|
objects,
|
||||||
|
raycaster,
|
||||||
|
mouse,
|
||||||
|
rollOverMesh,
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
mouse.set(
|
||||||
|
(clientX / innerWidth) * 2 - 1,
|
||||||
|
-(clientY / innerHeight) * 2 + 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
raycaster.setFromCamera(mouse, camera);
|
||||||
|
const intersects = raycaster.intersectObjects(objects);
|
||||||
|
if (intersects.length > 0) {
|
||||||
|
const intersect = intersects[0];
|
||||||
|
rollOverMesh.position
|
||||||
|
.copy(intersect.point)
|
||||||
|
.add(intersect.face.normal);
|
||||||
|
rollOverMesh.position
|
||||||
|
.divideScalar(10)
|
||||||
|
.floor()
|
||||||
|
.multiplyScalar(10)
|
||||||
|
.addScalar(5);
|
||||||
|
}
|
||||||
|
const hover = rollOverMesh.position
|
||||||
|
.toArray()
|
||||||
|
.map((u) => Math.floor(u / 10));
|
||||||
|
this.store.dispatch(setHover(hover));
|
||||||
|
}
|
||||||
|
|
||||||
|
onDocumentMouseDown() {
|
||||||
|
this.pressTime = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
onDocumentMouseUp(event) {
|
||||||
|
if (Date.now() - this.pressTime > 600) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
const {
|
||||||
|
clientX,
|
||||||
|
clientY,
|
||||||
|
} = event;
|
||||||
|
const {
|
||||||
|
innerWidth,
|
||||||
|
innerHeight,
|
||||||
|
} = window;
|
||||||
|
const {
|
||||||
|
camera,
|
||||||
|
objects,
|
||||||
|
raycaster,
|
||||||
|
mouse,
|
||||||
|
plane,
|
||||||
|
voxel,
|
||||||
|
voxelMaterials,
|
||||||
|
store,
|
||||||
|
scene,
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
mouse.set(
|
||||||
|
(clientX / innerWidth) * 2 - 1,
|
||||||
|
-(clientY / innerHeight) * 2 + 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
raycaster.setFromCamera(mouse, camera);
|
||||||
|
|
||||||
|
const intersects = raycaster.intersectObjects(objects);
|
||||||
|
if (intersects.length > 0) {
|
||||||
|
const intersect = intersects[0];
|
||||||
|
|
||||||
|
switch (event.button) {
|
||||||
|
case 0: {
|
||||||
|
// left mouse button
|
||||||
|
const state = store.getState();
|
||||||
|
const { selectedColor } = state.gui;
|
||||||
|
const newVoxel = new THREE.Mesh(
|
||||||
|
voxel,
|
||||||
|
voxelMaterials[selectedColor],
|
||||||
|
);
|
||||||
|
newVoxel.position.copy(intersect.point)
|
||||||
|
.add(intersect.face.normal);
|
||||||
|
newVoxel.position.divideScalar(10)
|
||||||
|
.floor()
|
||||||
|
.multiplyScalar(10)
|
||||||
|
.addScalar(5);
|
||||||
|
scene.add(newVoxel);
|
||||||
|
objects.push(newVoxel);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
// right mouse button
|
||||||
|
if (intersect.object !== plane) {
|
||||||
|
scene.remove(intersect.object);
|
||||||
|
objects.splice(objects.indexOf(intersect.object), 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Renderer;
|
|
@ -23,6 +23,7 @@ adTagParams.set('videoad_start_delay', 0);
|
||||||
adTagParams.set('description_url', 'http://pixelplanet.fun/');
|
adTagParams.set('description_url', 'http://pixelplanet.fun/');
|
||||||
adTagParams.set('max_ad_duration', 20000);
|
adTagParams.set('max_ad_duration', 20000);
|
||||||
if (__DEV__) adTagParams.set('adtest', 'on');
|
if (__DEV__) adTagParams.set('adtest', 'on');
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
const adTagUrl = `https://googleads.g.doubleclick.net/pagead/ads?${adTagParams.toString()}`;
|
const adTagUrl = `https://googleads.g.doubleclick.net/pagead/ads?${adTagParams.toString()}`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -84,7 +85,7 @@ function init() {
|
||||||
if (typeof google === 'undefined') return;
|
if (typeof google === 'undefined') return;
|
||||||
outstreamContainer = document.getElementById('outstreamContainer');
|
outstreamContainer = document.getElementById('outstreamContainer');
|
||||||
|
|
||||||
adsController = new google.outstream.AdsController(
|
adsController = new window.google.outstream.AdsController(
|
||||||
outstreamContainer,
|
outstreamContainer,
|
||||||
onAdLoaded,
|
onAdLoaded,
|
||||||
onDone,
|
onDone,
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
/*
|
|
||||||
* keypress actions
|
|
||||||
* @flow
|
|
||||||
*/
|
|
||||||
import keycode from 'keycode';
|
|
||||||
|
|
||||||
import store from './store';
|
|
||||||
import {
|
|
||||||
toggleGrid,
|
|
||||||
togglePixelNotify,
|
|
||||||
toggleMute,
|
|
||||||
moveNorth,
|
|
||||||
moveWest,
|
|
||||||
moveSouth,
|
|
||||||
moveEast,
|
|
||||||
zoomIn,
|
|
||||||
zoomOut,
|
|
||||||
onViewFinishChange,
|
|
||||||
} from '../actions';
|
|
||||||
|
|
||||||
|
|
||||||
function onKeyPress(event: KeyboardEvent) {
|
|
||||||
// ignore key presses if modal is open or chat is used
|
|
||||||
if (event.target.nodeName == 'INPUT' || event.target.nodeName == 'TEXTAREA') return;
|
|
||||||
|
|
||||||
switch (keycode(event)) {
|
|
||||||
case 'up':
|
|
||||||
case 'w':
|
|
||||||
store.dispatch(moveNorth());
|
|
||||||
break;
|
|
||||||
case 'left':
|
|
||||||
case 'a':
|
|
||||||
store.dispatch(moveWest());
|
|
||||||
break;
|
|
||||||
case 'down':
|
|
||||||
case 's':
|
|
||||||
store.dispatch(moveSouth());
|
|
||||||
break;
|
|
||||||
case 'right':
|
|
||||||
case 'd':
|
|
||||||
store.dispatch(moveEast());
|
|
||||||
break;
|
|
||||||
case 'g':
|
|
||||||
store.dispatch(toggleGrid());
|
|
||||||
return;
|
|
||||||
case 'c':
|
|
||||||
store.dispatch(togglePixelNotify());
|
|
||||||
return;
|
|
||||||
case 'space':
|
|
||||||
if ($viewport) $viewport.click();
|
|
||||||
return;
|
|
||||||
case 'm':
|
|
||||||
store.dispatch(toggleMute());
|
|
||||||
return;
|
|
||||||
case '+':
|
|
||||||
case 'e':
|
|
||||||
store.dispatch(zoomIn());
|
|
||||||
return;
|
|
||||||
case '-':
|
|
||||||
case 'q':
|
|
||||||
store.dispatch(zoomOut());
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
store.dispatch(onViewFinishChange());
|
|
||||||
}
|
|
||||||
|
|
||||||
export default onKeyPress;
|
|
|
@ -21,10 +21,8 @@ export function loadImage(url) {
|
||||||
*/
|
*/
|
||||||
class LoadingTiles {
|
class LoadingTiles {
|
||||||
tiles: Object;
|
tiles: Object;
|
||||||
hasTiles: boolean;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.hasTiles = false;
|
|
||||||
this.tiles = {};
|
this.tiles = {};
|
||||||
this.loadLoadingTile(0);
|
this.loadLoadingTile(0);
|
||||||
}
|
}
|
||||||
|
@ -33,7 +31,7 @@ class LoadingTiles {
|
||||||
if (typeof this.tiles[canvasId] === 'undefined') {
|
if (typeof this.tiles[canvasId] === 'undefined') {
|
||||||
this.loadLoadingTile(canvasId);
|
this.loadLoadingTile(canvasId);
|
||||||
}
|
}
|
||||||
return this.tiles[canvasId] || this.tiles[0];
|
return this.tiles[canvasId] || this.tiles[0] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadLoadingTile(canvasId: number) {
|
async loadLoadingTile(canvasId: number) {
|
||||||
|
@ -43,11 +41,7 @@ class LoadingTiles {
|
||||||
this.tiles[canvasId] = null;
|
this.tiles[canvasId] = null;
|
||||||
const img = await loadImage(`./loading${canvasId}.png`);
|
const img = await loadImage(`./loading${canvasId}.png`);
|
||||||
this.tiles[canvasId] = img;
|
this.tiles[canvasId] = img;
|
||||||
if (canvasId === 0) {
|
|
||||||
this.hasTiles = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const loadingTiles = new LoadingTiles();
|
export const loadingTiles = new LoadingTiles();
|
||||||
|
|
|
@ -20,8 +20,7 @@ export function renderPlaceholder(
|
||||||
const { selectedColor, hover } = state.gui;
|
const { selectedColor, hover } = state.gui;
|
||||||
const { palette } = state.canvas;
|
const { palette } = state.canvas;
|
||||||
|
|
||||||
const worldPos = screenToWorld(state, $viewport, hover);
|
const [sx, sy] = worldToScreen(state, $viewport, hover);
|
||||||
const [sx, sy] = worldToScreen(state, $viewport, worldPos);
|
|
||||||
|
|
||||||
viewportCtx.save();
|
viewportCtx.save();
|
||||||
viewportCtx.translate(sx + (scale / 2), sy + (scale / 2));
|
viewportCtx.translate(sx + (scale / 2), sy + (scale / 2));
|
||||||
|
@ -55,8 +54,7 @@ export function renderPotatoPlaceholder(
|
||||||
const { selectedColor, hover } = state.gui;
|
const { selectedColor, hover } = state.gui;
|
||||||
const { palette } = state.canvas;
|
const { palette } = state.canvas;
|
||||||
|
|
||||||
const worldPos = screenToWorld(state, $viewport, hover);
|
const [sx, sy] = worldToScreen(state, $viewport, hover);
|
||||||
const [sx, sy] = worldToScreen(state, $viewport, worldPos);
|
|
||||||
|
|
||||||
viewportCtx.save();
|
viewportCtx.save();
|
||||||
viewportCtx.fillStyle = '#000';
|
viewportCtx.fillStyle = '#000';
|
39
src/ui/renderer.js
Normal file
39
src/ui/renderer.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Manage renderers and switch between them
|
||||||
|
* A renderer will create it's own viewport and append it
|
||||||
|
* to document.body.
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Renderer2D from './Renderer2D';
|
||||||
|
|
||||||
|
let renderer = {
|
||||||
|
render: () => null,
|
||||||
|
destructor: () => null,
|
||||||
|
renderPixel: () => null,
|
||||||
|
updateCanvasData: () => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
function animationLoop() {
|
||||||
|
renderer.render();
|
||||||
|
window.requestAnimationFrame(animationLoop);
|
||||||
|
}
|
||||||
|
animationLoop();
|
||||||
|
|
||||||
|
export async function initRenderer(store, is3D: boolean) {
|
||||||
|
renderer.destructor();
|
||||||
|
if (is3D) {
|
||||||
|
/* eslint-disable-next-line max-len */
|
||||||
|
const module = await import(/* webpackChunkName: "voxel" */ '../ui/Renderer3D');
|
||||||
|
const Renderer3D = module.default;
|
||||||
|
renderer = new Renderer3D(store);
|
||||||
|
} else {
|
||||||
|
renderer = new Renderer2D(store);
|
||||||
|
}
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRenderer() {
|
||||||
|
return renderer;
|
||||||
|
}
|
204
src/voxel.js
204
src/voxel.js
|
@ -1,204 +0,0 @@
|
||||||
import * as THREE from 'three';
|
|
||||||
import { VoxelPainterControls } from './controls/VoxelPainterControls';
|
|
||||||
// import { OrbitControls } from './controls/OrbitControls';
|
|
||||||
import store from './ui/store';
|
|
||||||
|
|
||||||
var camera, scene, renderer;
|
|
||||||
var plane;
|
|
||||||
var mouse, raycaster;
|
|
||||||
|
|
||||||
var rollOverMesh, rollOverMaterial;
|
|
||||||
var cubeGeo, cubeMaterial;
|
|
||||||
|
|
||||||
var controls;
|
|
||||||
|
|
||||||
var objects = [];
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
// quit 2d rendering
|
|
||||||
const canvas = document.getElementById('gameWindow').remove();
|
|
||||||
|
|
||||||
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
|
|
||||||
camera.position.set( 100, 160, 260 );
|
|
||||||
camera.lookAt( 0, 0, 0 );
|
|
||||||
|
|
||||||
scene = new THREE.Scene();
|
|
||||||
scene.background = new THREE.Color( 0xf0f0f0 );
|
|
||||||
|
|
||||||
// roll-over helpers
|
|
||||||
|
|
||||||
var rollOverGeo = new THREE.BoxBufferGeometry( 10, 10, 10 );
|
|
||||||
rollOverMaterial = new THREE.MeshBasicMaterial( { color: 0xff0000, opacity: 0.5, transparent: true } );
|
|
||||||
rollOverMesh = new THREE.Mesh( rollOverGeo, rollOverMaterial );
|
|
||||||
scene.add( rollOverMesh );
|
|
||||||
|
|
||||||
// cubes
|
|
||||||
cubeGeo = new THREE.BoxBufferGeometry( 10, 10, 10 );
|
|
||||||
cubeMaterial = new THREE.MeshLambertMaterial( { color: 0xfeb74c } );
|
|
||||||
|
|
||||||
// grid
|
|
||||||
var gridHelper = new THREE.GridHelper( 1000, 100, 0x555555, 0x555555 );
|
|
||||||
scene.add( gridHelper );
|
|
||||||
|
|
||||||
//
|
|
||||||
raycaster = new THREE.Raycaster();
|
|
||||||
mouse = new THREE.Vector2();
|
|
||||||
|
|
||||||
|
|
||||||
// Floor with random cold color triangles
|
|
||||||
var floorGeometry = new THREE.PlaneBufferGeometry( 1000, 1000, 100, 100 );
|
|
||||||
floorGeometry.rotateX( - Math.PI / 2 );
|
|
||||||
// vertex displacement
|
|
||||||
var vertex = new THREE.Vector3();
|
|
||||||
var color = new THREE.Color();
|
|
||||||
var position = floorGeometry.attributes.position;
|
|
||||||
for ( var i = 0, l = position.count; i < l; i ++ ) {
|
|
||||||
vertex.fromBufferAttribute( position, i );
|
|
||||||
vertex.x += Math.random() * 20 - 10;
|
|
||||||
vertex.y += Math.random() * 2;
|
|
||||||
vertex.z += Math.random() * 20 - 10;
|
|
||||||
position.setXYZ( i, vertex.x, vertex.y, vertex.z );
|
|
||||||
}
|
|
||||||
floorGeometry = floorGeometry.toNonIndexed(); // ensure each face has unique vertices
|
|
||||||
position = floorGeometry.attributes.position;
|
|
||||||
var colors = [];
|
|
||||||
for ( var i = 0, l = position.count; i < l; i ++ ) {
|
|
||||||
color.setHSL( Math.random() * 0.3 + 0.5, 0.75, Math.random() * 0.25 + 0.75 );
|
|
||||||
colors.push( color.r, color.g, color.b );
|
|
||||||
}
|
|
||||||
floorGeometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
|
|
||||||
var floorMaterial = new THREE.MeshBasicMaterial( { vertexColors: THREE.VertexColors } );
|
|
||||||
plane = new THREE.Mesh( floorGeometry, floorMaterial );
|
|
||||||
scene.add( plane );
|
|
||||||
objects.push( plane );
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Plane Floor
|
|
||||||
var geometry = new THREE.PlaneBufferGeometry( 5000, 5000 );
|
|
||||||
geometry.rotateX( - Math.PI / 2 );
|
|
||||||
plane = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial({ color: 0x009900 }) );
|
|
||||||
scene.add( plane );
|
|
||||||
objects.push( plane );
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
// lights
|
|
||||||
|
|
||||||
var ambientLight = new THREE.AmbientLight( 0x606060 );
|
|
||||||
scene.add( ambientLight );
|
|
||||||
|
|
||||||
var directionalLight = new THREE.DirectionalLight( 0xffffff );
|
|
||||||
directionalLight.position.set( 1, 0.75, 0.5 ).normalize();
|
|
||||||
scene.add( directionalLight );
|
|
||||||
|
|
||||||
renderer = new THREE.WebGLRenderer( { antialias: true } );
|
|
||||||
renderer.setPixelRatio( window.devicePixelRatio );
|
|
||||||
renderer.setSize( window.innerWidth, window.innerHeight );
|
|
||||||
document.body.appendChild( renderer.domElement );
|
|
||||||
|
|
||||||
controls = new VoxelPainterControls(camera, renderer.domElement);
|
|
||||||
controls.enableDamping = true;
|
|
||||||
controls.dampingFactor = 0.75;
|
|
||||||
controls.maxPolarAngle = Math.PI / 2;
|
|
||||||
controls.minDistance = 100.00;
|
|
||||||
controls.maxDistance = 1000.00;
|
|
||||||
/*controls.rotateSpeed = 1.5;
|
|
||||||
controls.zoomSpeed = 10.0;
|
|
||||||
controls.panSpeed = 0.3;
|
|
||||||
controls.keys = [65, 83, 68]; // ASD
|
|
||||||
controls.dynamicDampingFactor = 0.2;
|
|
||||||
*/
|
|
||||||
|
|
||||||
renderer.domElement.addEventListener( 'mousemove', onDocumentMouseMove, false );
|
|
||||||
renderer.domElement.addEventListener( 'mousedown', onDocumentMouseDown, false );
|
|
||||||
renderer.domElement.addEventListener( 'mouseup', onDocumentMouseUp, false );
|
|
||||||
//document.addEventListener( 'keydown', onDocumentKeyDown, false );
|
|
||||||
//document.addEventListener( 'keyup', onDocumentKeyUp, false );
|
|
||||||
//
|
|
||||||
window.addEventListener( 'resize', onWindowResize, false );
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
init();
|
|
||||||
|
|
||||||
window.animationLoop = () => {
|
|
||||||
controls.update();
|
|
||||||
renderer.render( scene, camera );
|
|
||||||
requestAnimationFrame(window.animationLoop);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onWindowResize() {
|
|
||||||
|
|
||||||
camera.aspect = window.innerWidth / window.innerHeight;
|
|
||||||
camera.updateProjectionMatrix();
|
|
||||||
renderer.setSize( window.innerWidth, window.innerHeight );
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDocumentMouseMove( event ) {
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
mouse.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1 );
|
|
||||||
|
|
||||||
raycaster.setFromCamera( mouse, camera );
|
|
||||||
|
|
||||||
var intersects = raycaster.intersectObjects( objects );
|
|
||||||
|
|
||||||
if ( intersects.length > 0 ) {
|
|
||||||
|
|
||||||
var intersect = intersects[ 0 ];
|
|
||||||
|
|
||||||
rollOverMesh.position.copy( intersect.point ).add( intersect.face.normal );
|
|
||||||
rollOverMesh.position.divideScalar( 10 ).floor().multiplyScalar( 10 ).addScalar( 5 );
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var pressTime = 0;
|
|
||||||
function onDocumentMouseDown( event ) {
|
|
||||||
pressTime = Date.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDocumentMouseUp( event ) {
|
|
||||||
if (Date.now() - pressTime > 600) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
mouse.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1 );
|
|
||||||
|
|
||||||
raycaster.setFromCamera( mouse, camera );
|
|
||||||
|
|
||||||
var intersects = raycaster.intersectObjects( objects );
|
|
||||||
|
|
||||||
if ( intersects.length > 0 ) {
|
|
||||||
var intersect = intersects[ 0 ];
|
|
||||||
|
|
||||||
switch ( event.button ) {
|
|
||||||
case 0:
|
|
||||||
// left mouse button
|
|
||||||
const state = store.getState();
|
|
||||||
const clri = state.gui.selectedColor;
|
|
||||||
const clr = state.canvas.palette.colors[clri];
|
|
||||||
const material = new THREE.MeshLambertMaterial( { color: clr } );
|
|
||||||
|
|
||||||
console.log("set voxel");
|
|
||||||
var voxel = new THREE.Mesh( cubeGeo, material );
|
|
||||||
voxel.position.copy( intersect.point ).add( intersect.face.normal );
|
|
||||||
voxel.position.divideScalar( 10 ).floor().multiplyScalar( 10 ).addScalar( 5 );
|
|
||||||
scene.add( voxel );
|
|
||||||
|
|
||||||
objects.push( voxel );
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
// right mouse button
|
|
||||||
console.log("remove voxel");
|
|
||||||
if ( intersect.object !== plane ) {
|
|
||||||
scene.remove( intersect.object );
|
|
||||||
objects.splice( objects.indexOf( intersect.object ), 1 );
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
199
src/voxelSA.js
199
src/voxelSA.js
|
@ -1,199 +0,0 @@
|
||||||
import * as THREE from 'three';
|
|
||||||
import { VoxelPainterControls } from './controls/VoxelPainterControls';
|
|
||||||
// import { OrbitControls } from './controls/OrbitControls';
|
|
||||||
|
|
||||||
var camera, scene, renderer;
|
|
||||||
var plane;
|
|
||||||
var mouse, raycaster;
|
|
||||||
|
|
||||||
var rollOverMesh, rollOverMaterial;
|
|
||||||
var cubeGeo, cubeMaterial;
|
|
||||||
|
|
||||||
var controls;
|
|
||||||
|
|
||||||
var objects = [];
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
init();
|
|
||||||
render();
|
|
||||||
});
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
|
|
||||||
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
|
|
||||||
camera.position.set( 100, 160, 260 );
|
|
||||||
camera.lookAt( 0, 0, 0 );
|
|
||||||
|
|
||||||
scene = new THREE.Scene();
|
|
||||||
scene.background = new THREE.Color( 0xf0f0f0 );
|
|
||||||
|
|
||||||
// roll-over helpers
|
|
||||||
|
|
||||||
var rollOverGeo = new THREE.BoxBufferGeometry( 10, 10, 10 );
|
|
||||||
rollOverMaterial = new THREE.MeshBasicMaterial( { color: 0xff0000, opacity: 0.5, transparent: true } );
|
|
||||||
rollOverMesh = new THREE.Mesh( rollOverGeo, rollOverMaterial );
|
|
||||||
scene.add( rollOverMesh );
|
|
||||||
|
|
||||||
// cubes
|
|
||||||
cubeGeo = new THREE.BoxBufferGeometry( 10, 10, 10 );
|
|
||||||
cubeMaterial = new THREE.MeshLambertMaterial( { color: 0xfeb74c } );
|
|
||||||
|
|
||||||
// grid
|
|
||||||
var gridHelper = new THREE.GridHelper( 1000, 100, 0x555555, 0x555555 );
|
|
||||||
scene.add( gridHelper );
|
|
||||||
|
|
||||||
//
|
|
||||||
raycaster = new THREE.Raycaster();
|
|
||||||
mouse = new THREE.Vector2();
|
|
||||||
|
|
||||||
|
|
||||||
// Floor with random cold color triangles
|
|
||||||
var floorGeometry = new THREE.PlaneBufferGeometry( 1000, 1000, 100, 100 );
|
|
||||||
floorGeometry.rotateX( - Math.PI / 2 );
|
|
||||||
// vertex displacement
|
|
||||||
var vertex = new THREE.Vector3();
|
|
||||||
var color = new THREE.Color();
|
|
||||||
var position = floorGeometry.attributes.position;
|
|
||||||
for ( var i = 0, l = position.count; i < l; i ++ ) {
|
|
||||||
vertex.fromBufferAttribute( position, i );
|
|
||||||
vertex.x += Math.random() * 20 - 10;
|
|
||||||
vertex.y += Math.random() * 2;
|
|
||||||
vertex.z += Math.random() * 20 - 10;
|
|
||||||
position.setXYZ( i, vertex.x, vertex.y, vertex.z );
|
|
||||||
}
|
|
||||||
floorGeometry = floorGeometry.toNonIndexed(); // ensure each face has unique vertices
|
|
||||||
position = floorGeometry.attributes.position;
|
|
||||||
var colors = [];
|
|
||||||
for ( var i = 0, l = position.count; i < l; i ++ ) {
|
|
||||||
color.setHSL( Math.random() * 0.3 + 0.5, 0.75, Math.random() * 0.25 + 0.75 );
|
|
||||||
colors.push( color.r, color.g, color.b );
|
|
||||||
}
|
|
||||||
floorGeometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
|
|
||||||
var floorMaterial = new THREE.MeshBasicMaterial( { vertexColors: THREE.VertexColors } );
|
|
||||||
plane = new THREE.Mesh( floorGeometry, floorMaterial );
|
|
||||||
scene.add( plane );
|
|
||||||
objects.push( plane );
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Plane Floor
|
|
||||||
var geometry = new THREE.PlaneBufferGeometry( 5000, 5000 );
|
|
||||||
geometry.rotateX( - Math.PI / 2 );
|
|
||||||
plane = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial({ color: 0x009900 }) );
|
|
||||||
scene.add( plane );
|
|
||||||
objects.push( plane );
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
// lights
|
|
||||||
|
|
||||||
var ambientLight = new THREE.AmbientLight( 0x606060 );
|
|
||||||
scene.add( ambientLight );
|
|
||||||
|
|
||||||
var directionalLight = new THREE.DirectionalLight( 0xffffff );
|
|
||||||
directionalLight.position.set( 1, 0.75, 0.5 ).normalize();
|
|
||||||
scene.add( directionalLight );
|
|
||||||
|
|
||||||
renderer = new THREE.WebGLRenderer( { antialias: true } );
|
|
||||||
renderer.setPixelRatio( window.devicePixelRatio );
|
|
||||||
renderer.setSize( window.innerWidth, window.innerHeight );
|
|
||||||
document.body.appendChild( renderer.domElement );
|
|
||||||
|
|
||||||
controls = new VoxelPainterControls(camera, renderer.domElement);
|
|
||||||
controls.enableDamping = true;
|
|
||||||
controls.dampingFactor = 0.75;
|
|
||||||
controls.maxPolarAngle = Math.PI / 2;
|
|
||||||
controls.minDistance = 100.00;
|
|
||||||
controls.maxDistance = 1000.00;
|
|
||||||
/*controls.rotateSpeed = 1.5;
|
|
||||||
controls.zoomSpeed = 10.0;
|
|
||||||
controls.panSpeed = 0.3;
|
|
||||||
controls.keys = [65, 83, 68]; // ASD
|
|
||||||
controls.dynamicDampingFactor = 0.2;
|
|
||||||
*/
|
|
||||||
|
|
||||||
renderer.domElement.addEventListener( 'mousemove', onDocumentMouseMove, false );
|
|
||||||
renderer.domElement.addEventListener( 'mousedown', onDocumentMouseDown, false );
|
|
||||||
renderer.domElement.addEventListener( 'mouseup', onDocumentMouseUp, false );
|
|
||||||
//document.addEventListener( 'keydown', onDocumentKeyDown, false );
|
|
||||||
//document.addEventListener( 'keyup', onDocumentKeyUp, false );
|
|
||||||
//
|
|
||||||
window.addEventListener( 'resize', onWindowResize, false );
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
controls.update();
|
|
||||||
renderer.render( scene, camera );
|
|
||||||
requestAnimationFrame(render);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onWindowResize() {
|
|
||||||
|
|
||||||
camera.aspect = window.innerWidth / window.innerHeight;
|
|
||||||
camera.updateProjectionMatrix();
|
|
||||||
renderer.setSize( window.innerWidth, window.innerHeight );
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDocumentMouseMove( event ) {
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
mouse.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1 );
|
|
||||||
|
|
||||||
raycaster.setFromCamera( mouse, camera );
|
|
||||||
|
|
||||||
var intersects = raycaster.intersectObjects( objects );
|
|
||||||
|
|
||||||
if ( intersects.length > 0 ) {
|
|
||||||
|
|
||||||
var intersect = intersects[ 0 ];
|
|
||||||
|
|
||||||
rollOverMesh.position.copy( intersect.point ).add( intersect.face.normal );
|
|
||||||
rollOverMesh.position.divideScalar( 10 ).floor().multiplyScalar( 10 ).addScalar( 5 );
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var pressTime = 0;
|
|
||||||
function onDocumentMouseDown( event ) {
|
|
||||||
pressTime = Date.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDocumentMouseUp( event ) {
|
|
||||||
if (Date.now() - pressTime > 600) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
mouse.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1 );
|
|
||||||
|
|
||||||
raycaster.setFromCamera( mouse, camera );
|
|
||||||
|
|
||||||
var intersects = raycaster.intersectObjects( objects );
|
|
||||||
|
|
||||||
if ( intersects.length > 0 ) {
|
|
||||||
var intersect = intersects[ 0 ];
|
|
||||||
|
|
||||||
switch ( event.button ) {
|
|
||||||
case 0:
|
|
||||||
// left mouse button
|
|
||||||
console.log("set voxel");
|
|
||||||
var voxel = new THREE.Mesh( cubeGeo, cubeMaterial );
|
|
||||||
voxel.position.copy( intersect.point ).add( intersect.face.normal );
|
|
||||||
voxel.position.divideScalar( 10 ).floor().multiplyScalar( 10 ).addScalar( 5 );
|
|
||||||
scene.add( voxel );
|
|
||||||
|
|
||||||
objects.push( voxel );
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
// right mouse button
|
|
||||||
console.log("remove voxel");
|
|
||||||
if ( intersect.object !== plane ) {
|
|
||||||
scene.remove( intersect.object );
|
|
||||||
objects.splice( objects.indexOf( intersect.object ), 1 );
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user