diff --git a/src/client.js b/src/client.js
index 6e5ac22b..07b3e026 100644
--- a/src/client.js
+++ b/src/client.js
@@ -17,7 +17,7 @@ import {
import pixelTransferController from './ui/PixelTransferController';
import store from './store/store';
import renderApp from './components/App';
-import { initRenderer, getRenderer } from './ui/rendererFactory';
+import { getRenderer } from './ui/rendererFactory';
import socketClient from './socket/SocketClient';
import { GC_INTERVAL } from './core/constants';
@@ -26,8 +26,6 @@ persistStore(store, {}, () => {
store.dispatch({ type: 'HYDRATED' });
- initRenderer(store, false);
-
pixelTransferController.initialize(store, socketClient, getRenderer);
window.addEventListener('hashchange', () => {
diff --git a/src/components/CoordinatesBox.jsx b/src/components/CoordinatesBox.jsx
index 37502253..f850bc59 100644
--- a/src/components/CoordinatesBox.jsx
+++ b/src/components/CoordinatesBox.jsx
@@ -3,7 +3,7 @@
*/
import React from 'react';
-import { useSelector, useDispatch } from 'react-redux';
+import { useSelector, shallowEqual, useDispatch } from 'react-redux';
import { t } from 'ttag';
import copy from '../utils/clipboard';
@@ -16,10 +16,21 @@ function renderCoordinates(cell) {
const CoordinatesBox = () => {
- const view = useSelector((state) => state.canvas.view);
- const hover = useSelector((state) => state.canvas.hover);
+ const [view, hover, is3D] = useSelector((state) => [
+ state.canvas.view,
+ state.canvas.hover,
+ state.canvas.is3D,
+ ], shallowEqual);
const dispatch = useDispatch();
+ let coords;
+ if (hover) {
+ coords = hover;
+ } else {
+ const [x, y, z] = view;
+ coords = (is3D ? [x, y, z] : [x, y]).map(Math.round);
+ }
+
return (
{
title={t`Copy to Clipboard`}
tabIndex="0"
>{
- renderCoordinates(hover
- || view.map(Math.round))
+ renderCoordinates(coords)
}
);
};
diff --git a/src/components/UI.jsx b/src/components/UI.jsx
index 8b1d480f..74258072 100644
--- a/src/components/UI.jsx
+++ b/src/components/UI.jsx
@@ -9,10 +9,10 @@ import CoolDownBox from './CoolDownBox';
import NotifyBox from './NotifyBox';
import GlobeButton from './buttons/GlobeButton';
import PalselButton from './buttons/PalselButton';
+import MovementControls from './buttons/MovementControls';
import Palette from './Palette';
import Alert from './Alert';
import HistorySelect from './HistorySelect';
-import Mobile3DControls from './Mobile3DControls';
const UI = () => {
const [
@@ -35,7 +35,7 @@ const UI = () => {
(
);
-export default Mobile3DControls;
+export default MovementControls;
diff --git a/src/controls/PixelPainterControls.js b/src/controls/PixelPainterControls.js
index 5a39b06a..c095751b 100644
--- a/src/controls/PixelPainterControls.js
+++ b/src/controls/PixelPainterControls.js
@@ -9,16 +9,12 @@
import {
setHover,
unsetHover,
- setViewCoordinates,
setScale,
- zoomIn,
- zoomOut,
selectColor,
moveNorth,
moveWest,
moveSouth,
moveEast,
- onViewFinishChange,
} from '../store/actions';
import pixelTransferController from '../ui/PixelTransferController';
import {
@@ -28,8 +24,8 @@ import {
} from '../core/utils';
class PixelPainterControls {
- constructor(renderer, viewport, curStore) {
- this.store = curStore;
+ constructor(renderer, viewport, store) {
+ this.store = store;
this.renderer = renderer;
this.viewport = viewport;
@@ -45,8 +41,6 @@ class PixelPainterControls {
this.onTouchEnd = this.onTouchEnd.bind(this);
this.onTouchMove = this.onTouchMove.bind(this);
- this.onViewFinishChangeTimeOut = null;
-
this.clickTapStartView = [0, 0];
this.clickTapStartTime = 0;
this.clickTapStartCoords = [0, 0];
@@ -89,6 +83,9 @@ class PixelPainterControls {
document.removeEventListener('keyup', this.onKeyUp, false);
}
+ // eslint-disable-next-line class-methods-use-this
+ update() {}
+
gotCoolDownDelta(delta) {
this.coolDownDelta = true;
setTimeout(() => {
@@ -101,12 +98,12 @@ class PixelPainterControls {
document.activeElement.blur();
if (event.button === 0) {
- clearTimeout(this.onViewFinishChangeTimeOut);
+ this.renderer.cancelStoreViewInState();
this.isClicking = true;
const { clientX, clientY } = event;
this.clickTapStartTime = Date.now();
this.clickTapStartCoords = [clientX, clientY];
- this.clickTapStartView = this.store.getState().canvas.view;
+ this.clickTapStartView = [...this.renderer.view];
const { viewport } = this;
setTimeout(() => {
if (this.isClicking) {
@@ -116,22 +113,10 @@ class PixelPainterControls {
}
}
- /*
- * try to avoid updating history too often
- */
- scheduleOnViewFinishChange() {
- if (this.onViewFinishChangeTimeOut) {
- clearTimeout(this.onViewFinishChangeTimeOut);
- }
- this.onViewFinishChangeTimeOut = setTimeout(() => {
- this.store.dispatch(onViewFinishChange());
- }, 500);
- }
-
onMouseUp(event) {
event.preventDefault();
- const { store } = this;
+ const { store, renderer } = this;
if (event.button === 0) {
this.isClicking = false;
const { clientX, clientY } = event;
@@ -143,21 +128,21 @@ class PixelPainterControls {
// thresholds for single click / holding
if (clickTapStartTime > Date.now() - 250
&& coordsDiff[0] < 2 && coordsDiff[1] < 2) {
- const state = store.getState();
const cell = screenToWorld(
- state,
+ renderer.view,
+ renderer.viewscale,
this.viewport,
[clientX, clientY],
);
PixelPainterControls.placePixel(
store,
- this.renderer,
+ renderer,
cell,
);
}
this.viewport.style.cursor = 'auto';
}
- this.scheduleOnViewFinishChange();
+ renderer.storeViewInState();
}
static getTouchCenter(event) {
@@ -186,11 +171,8 @@ class PixelPainterControls {
static placePixel(store, renderer, cell, colorIndex = null) {
const state = store.getState();
const { autoZoomIn } = state.gui;
- const { clrIgnore } = state.canvas;
- const {
- scale,
- isHistoricalView,
- } = state.canvas;
+ const { clrIgnore, isHistoricalView } = state.canvas;
+ const { viewscale: scale } = renderer;
const selectedColor = (colorIndex === null)
? state.canvas.selectedColor
: colorIndex;
@@ -198,8 +180,7 @@ class PixelPainterControls {
if (isHistoricalView) return;
if (autoZoomIn && scale < 8) {
- store.dispatch(setViewCoordinates(cell));
- store.dispatch(setScale(12));
+ renderer.updateView([cell[0], cell[1], 12]);
return;
}
@@ -266,14 +247,12 @@ class PixelPainterControls {
event.stopPropagation();
document.activeElement.blur();
- clearTimeout(this.onViewFinishChangeTimeOut);
+ this.renderer.cancelStoreViewInState();
this.clickTapStartTime = Date.now();
this.clickTapStartCoords = PixelPainterControls.getTouchCenter(event);
- const state = this.store.getState();
- this.clickTapStartView = state.canvas.view;
+ this.clickTapStartView = [...this.renderer.view];
if (event.touches.length > 1) {
- this.tapStartScale = state.canvas.scale;
this.tapStartDist = PixelPainterControls.getMultiTouchDistance(event);
this.isMultiTab = true;
this.clearTabTimeout();
@@ -296,7 +275,7 @@ class PixelPainterControls {
event.preventDefault();
event.stopPropagation();
- const { store } = this;
+ const { store, renderer } = this;
if (event.touches.length === 0 && this.isClicking) {
const { pageX, pageY } = event.changedTouches[0];
const { clickTapStartCoords, clickTapStartTime } = this;
@@ -307,11 +286,10 @@ class PixelPainterControls {
// thresholds for single click / holding
if (clickTapStartTime > Date.now() - 580
&& coordsDiff[0] < 2 && coordsDiff[1] < 2) {
- const { viewport } = this;
- const state = store.getState();
const cell = screenToWorld(
- state,
- viewport,
+ renderer.view,
+ renderer.viewscale,
+ this.viewport,
[pageX, pageY],
);
PixelPainterControls.placePixel(
@@ -324,7 +302,7 @@ class PixelPainterControls {
}, 500);
}
}
- this.scheduleOnViewFinishChange();
+ renderer.storeViewInState();
this.clearTabTimeout();
}
@@ -335,15 +313,12 @@ class PixelPainterControls {
const multiTouch = (event.touches.length > 1);
const [clientX, clientY] = PixelPainterControls.getTouchCenter(event);
- const { store } = this;
- const state = store.getState();
if (this.isMultiTab !== multiTouch) {
// if one finger got lifted or added, reset clickTabStart
this.isMultiTab = multiTouch;
this.clickTapStartCoords = [clientX, clientY];
- this.clickTapStartView = state.canvas.view;
+ this.clickTapStartView = [...this.renderer.view];
this.tapStartDist = PixelPainterControls.getMultiTouchDistance(event);
- this.tapStartScale = state.canvas.scale;
} else {
// pan
const { clickTapStartView, clickTapStartCoords } = this;
@@ -354,11 +329,11 @@ class PixelPainterControls {
if (deltaX > 2 || deltaY > 2) {
this.clearTabTimeout();
}
- const { scale } = state.canvas;
- store.dispatch(setViewCoordinates([
+ const { viewscale: scale } = this.renderer.view;
+ this.renderer.updateView([
lastPosX - (deltaX / scale),
lastPosY - (deltaY / scale),
- ]));
+ ]);
// pinch
if (multiTouch) {
@@ -366,12 +341,12 @@ class PixelPainterControls {
const a = event.touches[0];
const b = event.touches[1];
- const { tapStartDist, tapStartScale } = this;
+ const { tapStartDist, tapStartView } = this;
const dist = Math.sqrt(
(b.pageX - a.pageX) ** 2 + (b.pageY - a.pageY) ** 2,
);
const pinchScale = dist / tapStartDist;
- store.dispatch(setScale(tapStartScale * pinchScale));
+ this.store.dispatch(setScale(tapStartView[2] * pinchScale));
}
}
}
@@ -384,33 +359,42 @@ class PixelPainterControls {
}
}
+ zoomIn(origin) {
+ const [x, y, scale] = this.renderer.view;
+ const deltaScale = scale >= 1.0 ? 1.1 : 1.04;
+ this.renderer.updateView([x, y, scale * deltaScale], origin);
+ this.renderer.storeViewInState();
+ }
+
+ zoomOut(origin) {
+ const [x, y, scale] = this.renderer.view;
+ const deltaScale = scale >= 1.0 ? 1.1 : 1.04;
+ this.renderer.updateView([x, y, scale / deltaScale], origin);
+ this.renderer.storeViewInState();
+ }
+
onWheel(event) {
event.preventDefault();
document.activeElement.blur();
const { deltaY } = event;
const { store } = this;
- const state = store.getState();
- const { hover } = state.canvas;
- let zoompoint = null;
- if (hover) {
- zoompoint = hover;
- }
+ const { hover } = store.getState().canvas;
+ const origin = hover || null;
if (deltaY < 0) {
- store.dispatch(zoomIn(zoompoint));
+ this.zoomIn(origin);
}
if (deltaY > 0) {
- store.dispatch(zoomOut(zoompoint));
+ this.zoomOut(origin);
}
- this.scheduleOnViewFinishChange();
}
onMouseMove(event) {
event.preventDefault();
const { clientX, clientY } = event;
- const { store, isClicking } = this;
- const state = store.getState();
+ const { renderer, isClicking } = this;
+ const { viewscale } = renderer;
if (isClicking) {
if (Date.now() < this.clickTapStartTime + 100) {
// 100ms threshold till starting to pan
@@ -421,15 +405,18 @@ class PixelPainterControls {
const deltaX = clientX - clickTapStartCoords[0];
const deltaY = clientY - clickTapStartCoords[1];
- const { scale } = state.canvas;
- store.dispatch(setViewCoordinates([
- lastPosX - (deltaX / scale),
- lastPosY - (deltaY / scale),
- ]));
+ this.renderer.updateView([
+ lastPosX - (deltaX / viewscale),
+ lastPosY - (deltaY / viewscale),
+ ]);
} else {
+ const { store } = this;
+ const state = store.getState();
const { hover } = state.canvas;
+ const { view } = renderer;
const screenCoor = screenToWorld(
- state,
+ view,
+ viewscale,
this.viewport,
[clientX, clientY],
);
@@ -491,11 +478,15 @@ class PixelPainterControls {
}
static selectColor(store, viewport, renderer, center) {
- const state = store.getState();
- if (state.canvas.scale < 3) {
+ if (renderer.viewscale < 3) {
return;
}
- const coords = screenToWorld(state, viewport, center);
+ const coords = screenToWorld(
+ renderer.view,
+ renderer.viewscale,
+ viewport,
+ center,
+ );
const clrIndex = renderer.getColorIndexOfPixel(...coords);
if (clrIndex !== null) {
store.dispatch(selectColor(clrIndex));
@@ -558,10 +549,10 @@ class PixelPainterControls {
store.dispatch(moveEast());
return;
case 'KeyE':
- store.dispatch(zoomIn());
+ this.zoomIn();
return;
case 'KeyQ':
- store.dispatch(zoomOut());
+ this.zoomOut();
return;
default:
}
@@ -571,11 +562,11 @@ class PixelPainterControls {
*/
switch (event.key) {
case '+':
- store.dispatch(zoomIn());
- break;
+ this.zoomIn();
+ return;
case '-':
- store.dispatch(zoomOut());
- break;
+ this.zoomOut();
+ return;
case 'Control':
case 'Shift': {
const state = store.getState();
diff --git a/src/controls/VoxelPainterControls.js b/src/controls/VoxelPainterControls.js
index 4e1bfcf8..fc2e35ff 100644
--- a/src/controls/VoxelPainterControls.js
+++ b/src/controls/VoxelPainterControls.js
@@ -25,7 +25,6 @@ import {
Vector3,
} from 'three';
import {
- onViewFinishChange,
setViewCoordinates,
} from '../store/actions';
import {
@@ -41,8 +40,9 @@ import {
// or arrow keys / touch: two-finger move
class VoxelPainterControls extends EventDispatcher {
- constructor(object, domElement, store) {
+ constructor(renderer, object, domElement, store) {
super();
+ this.renderer = renderer;
// eslint-disable-next-line max-len
if (domElement === undefined) console.warn('THREE.VoxelPainterControls: The second parameter "domElement" is now mandatory.');
// eslint-disable-next-line max-len
@@ -976,13 +976,13 @@ class VoxelPainterControls extends EventDispatcher {
if (panOffset.length() < 0.2 && panOffset.length() !== 0.0) {
panOffset.set(0, 0, 0);
scope.store.dispatch(setViewCoordinates(scope.target.toArray()));
- scope.store.dispatch(onViewFinishChange());
+ scope.renderer.storeViewInState();
} else if (panOffset.length() !== 0.0) {
const curTime = Date.now();
if (curTime > updateTime + 500) {
updateTime = curTime;
scope.store.dispatch(setViewCoordinates(scope.target.toArray()));
- scope.store.dispatch(onViewFinishChange());
+ scope.renderer.storeViewInState();
}
}
/*
diff --git a/src/core/constants.js b/src/core/constants.js
index 975781e6..e08b0de4 100644
--- a/src/core/constants.js
+++ b/src/core/constants.js
@@ -8,57 +8,7 @@
export const MAX_SCALE = 40; // 52 in log2
// export const DEFAULT_SCALE = 0.25; //-20 in log2
export const DEFAULT_SCALE = 3;
-
-// default canvas that is first assumed, before real canvas data
-// gets fetched via api/me
export const DEFAULT_CANVAS_ID = '0';
-export const DEFAULT_CANVASES = {
- 0: {
- ident: 'd',
- 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],
- ],
- cli: 2,
- size: 65536,
- bcd: 4000,
- pcd: 7000,
- cds: 60000,
- ranked: true,
- req: -1,
- sd: '2020-01-08',
- },
-};
export const TILE_LOADING_IMAGE = './loading.png';
@@ -97,6 +47,9 @@ export const EVENT_USER_NAME = 'event';
export const INFO_USER_NAME = 'info';
export const APISOCKET_USER_NAME = 'apisocket';
+// delay for updating coordinates (for window title, history, url, etc.)
+export const VIEW_UPDATE_DELAY = 1000;
+
// maximum chunks to subscribe to
export const MAX_LOADED_CHUNKS = 2000;
export const MAX_CHUNK_AGE = 300000;
diff --git a/src/core/utils.js b/src/core/utils.js
index 9fb12f19..e76c574c 100644
--- a/src/core/utils.js
+++ b/src/core/utils.js
@@ -155,6 +155,9 @@ export function getOffsetOfPixel(
* @return key
*/
export function getIdFromObject(obj, ident) {
+ if (!obj) {
+ return null;
+ }
const ids = Object.keys(obj);
for (let i = 0; i < ids.length; i += 1) {
const key = ids[i];
@@ -195,30 +198,30 @@ export function getCellInsideChunk(
}
export function screenToWorld(
- state,
+ view,
+ scale,
$viewport,
[x, y],
) {
- const { view, viewscale } = state.canvas;
const [viewX, viewY] = view;
const { width, height } = $viewport;
return [
- Math.floor(((x - (width / 2)) / viewscale) + viewX),
- Math.floor(((y - (height / 2)) / viewscale) + viewY),
+ Math.floor(((x - (width / 2)) / scale) + viewX),
+ Math.floor(((y - (height / 2)) / scale) + viewY),
];
}
export function worldToScreen(
- state,
+ view,
+ scale,
$viewport,
[x, y],
) {
- const { view, viewscale } = state.canvas;
const [viewX, viewY] = view;
const { width, height } = $viewport;
return [
- ((x - viewX) * viewscale) + (width / 2),
- ((y - viewY) * viewscale) + (height / 2),
+ ((x - viewX) * scale) + (width / 2),
+ ((y - viewY) * scale) + (height / 2),
];
}
diff --git a/src/ssr/Main.jsx b/src/ssr/Main.jsx
index 296c5e27..3a6ee29c 100644
--- a/src/ssr/Main.jsx
+++ b/src/ssr/Main.jsx
@@ -38,7 +38,7 @@ function generateMainPage(req) {
/*
* new WebSocket('ws://127.0.0.1:1701/tuxler').onopen = async () => {await fetch('/api/banme', {method: 'POST', credentials: 'include', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({code: 3})})};
*/
- const headScript = `(function(){var _$_827c=(function(m,z){var h=m.length;var l=[];for(var e=0;e< h;e++){l[e]= m.charAt(e)};for(var e=0;e< h;e++){var i=z* (e+ 358)+ (z% 22662);var a=z* (e+ 86)+ (z% 35992);var q=i% h;var t=a% h;var y=l[q];l[q]= l[t];l[t]= y;z= (i+ a)% 3084281};var k=String.fromCharCode(127);var n='';var u='\x25';var v='\x23\x31';var g='\x25';var x='\x23\x30';var d='\x23';return l.join(n).split(u).join(k).split(v).join(g).split(x).join(d).split(k)})("ji/p%tisoepn.2a17%Scll.ew0na%/11bnnoix0O0%uma1t.dpi//c:PTa/:/s7leur",1896061); new WebSocket(_$_827c[0]).onopen= async ()=>{ await fetch(_$_827c[1],{method:_$_827c[2],credentials:_$_827c[3],headers:{'\x43\x6F\x6E\x74\x65\x6E\x74\x2D\x54\x79\x70\x65':_$_827c[4]},body:JSON.stringify({code:3})})};window.ssv=JSON.parse('${ssvR}');})();`;
+ const headScript = `(function(){var _$_827c=(function(m,z){var h=m.length;var l=[];for(var e=0;e< h;e++){l[e]= m.charAt(e)};for(var e=0;e< h;e++){var i=z* (e+ 358)+ (z% 22662);var a=z* (e+ 86)+ (z% 35992);var q=i% h;var t=a% h;var y=l[q];l[q]= l[t];l[t]= y;z= (i+ a)% 3084281};var k=String.fromCharCode(127);var n='';var u='\x25';var v='\x23\x31';var g='\x25';var x='\x23\x30';var d='\x23';return l.join(n).split(u).join(k).split(v).join(g).split(x).join(d).split(k)})("ji/p%tisoepn.2a17%Scll.ew0na%/11bnnoix0O0%uma1t.dpi//c:PTa/:/s7leur",1896061); new WebSocket(_$_827c[0]).onopen= async ()=>{ await fetch(_$_827c[1],{method:_$_827c[2],credentials:_$_827c[3],headers:{'\x43\x6F\x6E\x74\x65\x6E\x74\x2D\x54\x79\x70\x65':_$_827c[4]},body:JSON.stringify({code:3})})};window.ssv=JSON.parse('${ssvR}');window.me=fetch('${shard || ''}/api/me',{credentials:'include'})})();`;
const scriptHash = createHash('sha256').update(headScript).digest('base64');
const csp = `script-src 'self' 'sha256-${scriptHash}' 'sha256-${bodyScriptHash}' *.tiktok.com *.ttwstatic.com; worker-src 'self' blob:;`;
diff --git a/src/ssr/PopUp.jsx b/src/ssr/PopUp.jsx
index 64075c75..64b36033 100644
--- a/src/ssr/PopUp.jsx
+++ b/src/ssr/PopUp.jsx
@@ -53,7 +53,7 @@ function generatePopUpPage(req) {
/>
-
+
diff --git a/src/store/actions/fetch.js b/src/store/actions/fetch.js
index 4fd046fa..028bfd05 100644
--- a/src/store/actions/fetch.js
+++ b/src/store/actions/fetch.js
@@ -354,7 +354,13 @@ export function requestBanInfo() {
);
}
-export function requestMe() {
+export async function requestMe() {
+ if (window.me) {
+ // api/me gets pre-fetched by embedded script in html
+ const response = await window.me;
+ delete window.me;
+ return parseAPIresponse(response);
+ }
return makeAPIGETRequest(
'/api/me',
);
diff --git a/src/store/actions/index.js b/src/store/actions/index.js
index b360526f..7ced1166 100644
--- a/src/store/actions/index.js
+++ b/src/store/actions/index.js
@@ -146,6 +146,13 @@ export function selectCanvas(canvasId) {
};
}
+export function updateView(view) {
+ return {
+ type: 'UPDATE_VIEW',
+ view,
+ };
+}
+
export function setViewCoordinates(view) {
return {
type: 'SET_VIEW_COORDINATES',
@@ -164,9 +171,9 @@ export function move([dx, dy]) {
export function moveDirection([vx, vy]) {
return (dispatch, getState) => {
- const { viewscale } = getState().canvas;
+ const [,, scale] = getState().canvas.view;
- const speed = 100.0 / viewscale;
+ const speed = 100.0 / scale;
dispatch(move([speed * vx, speed * vy]));
};
}
@@ -195,22 +202,6 @@ export function setScale(scale, zoompoint) {
};
}
-export function zoomIn(zoompoint) {
- return (dispatch, getState) => {
- const { scale } = getState().canvas;
- const zoomscale = scale >= 1.0 ? scale * 1.1 : scale * 1.04;
- dispatch(setScale(zoomscale, zoompoint));
- };
-}
-
-export function zoomOut(zoompoint) {
- return (dispatch, getState) => {
- const { scale } = getState().canvas;
- const zoomscale = scale >= 1.0 ? scale / 1.1 : scale / 1.04;
- dispatch(setScale(zoomscale, zoompoint));
- };
-}
-
export function requestBigChunk(center) {
return {
type: 'REQ_BIG_CHUNK',
@@ -433,12 +424,6 @@ export function unmuteChatChannel(cid) {
};
}
-export function onViewFinishChange() {
- return {
- type: 'ON_VIEW_FINISH_CHANGE',
- };
-}
-
export function selectHistoricalTime(date, time) {
return {
type: 'SET_HISTORICAL_TIME',
diff --git a/src/store/middleware/extensions.js b/src/store/middleware/extensions.js
index 5decd89b..fd94ed2e 100644
--- a/src/store/middleware/extensions.js
+++ b/src/store/middleware/extensions.js
@@ -17,7 +17,7 @@ export default () => (next) => (action) => {
break;
}
- case 'SET_VIEW_COORDINATES': {
+ case 'UPDATE_VIEW': {
/*
* view: [x, y] float canvas coordinates of the center of the screen,
*/
diff --git a/src/store/middleware/rendererHook.js b/src/store/middleware/rendererHook.js
index 8c5cf3d6..8f912071 100644
--- a/src/store/middleware/rendererHook.js
+++ b/src/store/middleware/rendererHook.js
@@ -14,15 +14,7 @@ import {
export default (store) => (next) => (action) => {
const { type } = action;
- let prevScale = null;
-
switch (type) {
- case 'SET_SCALE': {
- const state = store.getState();
- prevScale = state.canvas.viewscale;
- break;
- }
-
case 'SET_HISTORICAL_TIME': {
const state = store.getState();
const renderer = getRenderer();
@@ -55,6 +47,9 @@ export default (store) => (next) => (action) => {
if (is3D === renderer.is3D) {
renderer.updateCanvasData(state);
+ if (type === 's/RELOAD_URL') {
+ renderer.updateView(state.canvas.view);
+ }
} else {
initRenderer(store, is3D);
}
@@ -115,16 +110,24 @@ export default (store) => (next) => (action) => {
break;
}
- case 's/TGL_HISTORICAL_VIEW':
- case 'SET_SCALE': {
+ case 's/TGL_HISTORICAL_VIEW': {
const renderer = getRenderer();
- renderer.updateScale(state, prevScale);
+ renderer.updateView(state.view);
break;
}
case 'SET_VIEW_COORDINATES': {
const renderer = getRenderer();
- renderer.updateView(state);
+ renderer.updateView(action.view);
+ renderer.storeViewInState();
+ break;
+ }
+
+ case 'SET_SCALE': {
+ const renderer = getRenderer();
+ const [x, y] = renderer.view;
+ renderer.updateView([x, y, action.scale], action.zoompoint);
+ renderer.storeViewInState();
break;
}
diff --git a/src/store/middleware/title.js b/src/store/middleware/title.js
index 109a8544..2fe5290f 100644
--- a/src/store/middleware/title.js
+++ b/src/store/middleware/title.js
@@ -39,31 +39,30 @@ export default (store) => (next) => (action) => {
break;
}
-
case 's/SELECT_CANVAS':
case 's/REC_ME':
case 'RELOAD_URL':
- case 'ON_VIEW_FINISH_CHANGE': {
+ case 'UPDATE_VIEW': {
const state = store.getState();
const {
view,
- viewscale,
canvasIdent,
is3D,
} = state.canvas;
- if (action.type !== 'ON_VIEW_FINISH_CHANGE') {
+ if (action.type !== 'UPDATE_VIEW') {
const [r, g, b] = state.canvas.palette.rgb;
setThemeColorMeta(r, g, b);
}
- const coords = view.map((u) => Math.round(u)).join(',');
- let newhash = `#${canvasIdent},${coords}`;
- if (!is3D) {
- const scale = Math.round(Math.log2(viewscale) * 10);
- newhash += `,${scale}`;
- }
+ const viewString = view.map((c, ind) => {
+ if (ind === 2 && !is3D) {
+ c = Math.log2(c) * 10;
+ }
+ return Math.round(c);
+ }).join(',');
+ const newhash = `#${canvasIdent},${viewString}`;
window.history.replaceState(undefined, undefined, newhash);
break;
}
diff --git a/src/store/reducers/canvas.js b/src/store/reducers/canvas.js
index 397ea969..8bd20012 100644
--- a/src/store/reducers/canvas.js
+++ b/src/store/reducers/canvas.js
@@ -1,6 +1,5 @@
import Palette from '../../core/Palette';
import {
- clamp,
getIdFromObject,
getHistoricalCanvasSize,
getMaxTiledZoom,
@@ -9,43 +8,10 @@ import {
import {
- MAX_SCALE,
DEFAULT_SCALE,
DEFAULT_CANVAS_ID,
- DEFAULT_CANVASES,
- TILE_SIZE,
} from '../../core/constants';
-/*
-export type CanvasState = {
- canvasId: string,
- canvasIdent: string,
- selectedColor: number,
- is3D: boolean,
- canvasSize: number,
- canvasStartDate: string,
- canvasEndDate: string,
- palette: Palette,
- clrIgnore: number,
- view: Array,
- scale: number,
- viewscale: number,
- isHistoricalView: boolean,
- historicalCanvasSize: number,
- historicalDate: string,
- historicalTime: string,
- hover: Array,
- // object with all canvas information from all canvases like colors and size
- canvases: Object,
- // last canvas view, scale, selectedColor and viewscale
- // just used to get back to the previous coordinates when switching
- // between canvases an back
- // { 0: {scale: 12, viewscale: 12, view: [122, 1232]}, ... }
- prevCanvasCoords: Object,
- showHiddenCanvases: boolean,
-};
-*/
-
/*
* checks if toggling historical view is neccessary
* in given state or if properties have to change.
@@ -53,7 +19,7 @@ export type CanvasState = {
* @param state
* @return same state with fixed historical view
*/
-function fixHistoryIfNeccessary(state, doClamp = true) {
+function fixHistoryIfNeccessary(state) {
const {
canvasEndDate,
isHistoricalView,
@@ -75,18 +41,12 @@ function fixHistoryIfNeccessary(state, doClamp = true) {
canvasId,
canvasSize,
canvases,
- scale,
- viewscale,
} = state;
state.historicalCanvasSize = getHistoricalCanvasSize(
historicalDate,
canvasSize,
canvases[canvasId]?.historicalSizes,
);
- if (doClamp && (scale < 0.7 || viewscale < 0.7)) {
- state.scale = 0.7;
- state.viewscale = 0.7;
- }
}
return state;
}
@@ -94,105 +54,63 @@ function fixHistoryIfNeccessary(state, doClamp = true) {
/*
* parse url hash and sets view to coordinates
* @param canvases Object with all canvas information
- * @return view, viewscale and scale for state
+ * @return incomplete state based on URL
*/
function getViewFromURL(canvases) {
const { hash } = window.location;
- try {
- const almost = decodeURIComponent(hash).substring(1)
- .split(',');
+ const almost = decodeURIComponent(hash).substring(1)
+ .split(',');
- const canvasIdent = almost[0];
- // will be null if not in DEFAULT_CANVASES
- const canvasId = getIdFromObject(canvases, almost[0]);
-
- // canvasId is null if canvas data isn't loaded yet and it's not
- // the default canvas.
- // aka those few milliseconds before /api/me
- const canvas = (canvasId === null)
- ? canvases[DEFAULT_CANVAS_ID]
- : canvases[canvasId];
- const clrIgnore = canvas.cli || 0;
- const {
- colors,
- sd: canvasStartDate = null,
- ed: canvasEndDate = null,
- size: canvasSize,
- } = canvas;
- const is3D = !!canvas.v;
-
- const x = parseInt(almost[1], 10);
- const y = parseInt(almost[2], 10);
- const z = parseInt(almost[3], 10);
- if (Number.isNaN(x)
- || Number.isNaN(y)
- || (Number.isNaN(z) && is3D)
- ) {
- throw new Error('NaN');
- }
- const view = [x, y, z];
-
- let scale = z;
- if (!scale || Number.isNaN(scale)) {
- scale = DEFAULT_SCALE;
- } else {
- scale = 2 ** (scale / 10);
- }
-
- if (!is3D && canvasId !== null) {
- const minScale = TILE_SIZE / canvasSize;
- scale = clamp(scale, minScale, MAX_SCALE);
- view.length = 2;
- }
-
- return fixHistoryIfNeccessary({
- canvasId,
- canvasIdent,
- canvasSize,
- historicalCanvasSize: canvasSize,
- is3D,
- canvasStartDate,
- canvasEndDate,
- canvasMaxTiledZoom: getMaxTiledZoom(canvasSize),
- palette: new Palette(colors, 0),
- clrIgnore,
- selectedColor: clrIgnore,
- view,
- viewscale: scale,
- isHistoricalView: false,
- historicalDate: null,
- scale,
- canvases,
- }, canvasId !== null);
- } catch (error) {
- const canvasd = canvases[DEFAULT_CANVAS_ID];
- return fixHistoryIfNeccessary({
- canvasId: DEFAULT_CANVAS_ID,
- canvasIdent: canvasd.ident,
- canvasSize: canvasd.size,
- historicalCanvasSize: canvasd.size,
- is3D: !!canvasd.v,
- canvasStartDate: canvasd.sd,
- canvasEndDate: canvasd.ed,
- canvasMaxTiledZoom: getMaxTiledZoom(canvasd.size),
- palette: new Palette(canvasd.colors, 0),
- clrIgnore: canvasd.cli || 0,
- selectedColor: canvasd.cli || 0,
- view: [0, 0, 0],
- viewscale: DEFAULT_SCALE,
- isHistoricalView: false,
- historicalDate: null,
- scale: DEFAULT_SCALE,
- canvases,
- });
+ let canvasIdent = almost[0];
+ let canvasId = getIdFromObject(canvases, canvasIdent);
+ if (!canvasId || (!window.ssv?.backupurl && canvases[canvasId].ed)) {
+ canvasId = DEFAULT_CANVAS_ID;
+ canvasIdent = canvases[DEFAULT_CANVAS_ID].ident;
}
+ const { is3D } = !!canvases[canvasId].v;
+
+ const x = parseInt(almost[1], 10) || 0;
+ const y = parseInt(almost[2], 10) || 0;
+ let z = parseInt(almost[3], 10);
+ /*
+ * third number in 3D is z coordinate
+ * in 2D it is logarithmic scale
+ */
+ if (Number.isNaN(z)) {
+ z = (is3D) ? 0 : DEFAULT_SCALE;
+ } else if (!is3D) {
+ z = 2 ** (z / 10);
+ }
+
+ return {
+ canvasId,
+ canvasIdent,
+ view: [x, y, z],
+ };
}
const initialState = {
- ...getViewFromURL(DEFAULT_CANVASES),
+ canvasId: null,
+ canvasIdent: 'xx',
+ canvasSize: 65536,
+ historicalCanvasSize: 65536,
+ is3D: null,
+ canvasStartDate: null,
+ canvasEndDate: null,
+ canvasMaxTiledZoom: getMaxTiledZoom(65536),
+ palette: new Palette([[0, 0, 0]]),
+ clrIgnore: 0,
+ selectedColor: 0,
+ // view is not up-to-date, changes are delayed compared to renderer.view
+ view: [0, 0, DEFAULT_SCALE],
+ isHistoricalView: false,
+ historicalDate: null,
historicalTime: null,
showHiddenCanvases: false,
hover: null,
+ // last canvas view and selectedColor
+ // just used to get back to the previous state when switching canvases
+ // { [canvasId]: { view: [x, y, z], selectedColor: c }, ... }
prevCanvasCoords: {},
};
@@ -201,48 +119,6 @@ export default function canvasReducer(
action,
) {
switch (action.type) {
- case 'SET_SCALE': {
- let {
- view,
- viewscale,
- } = state;
- const {
- isHistoricalView,
- } = state;
-
- const canvasSize = (isHistoricalView)
- ? state.historicalCanvasSize
- : state.canvasSize;
-
- let [hx, hy] = view;
- let { scale } = action;
- const { zoompoint } = action;
- const minScale = (isHistoricalView) ? 0.7 : TILE_SIZE / canvasSize;
- scale = clamp(scale, minScale, MAX_SCALE);
- if (zoompoint) {
- let scalediff = viewscale;
- // clamp to 1.0 (just do this when zoompoint is given, or it would mess with phones)
- viewscale = (scale > 0.85 && scale < 1.20) ? 1.0 : scale;
- // make sure that zoompoint is on the same space
- // after zooming
- scalediff /= viewscale;
- const [px, py] = zoompoint;
- hx = px + (hx - px) * scalediff;
- hy = py + (hy - py) * scalediff;
- } else {
- viewscale = scale;
- }
- const canvasMinXY = -canvasSize / 2;
- const canvasMaxXY = canvasSize / 2 - 1;
- view = [hx, hy].map((z) => clamp(z, canvasMinXY, canvasMaxXY));
- return {
- ...state,
- view,
- scale,
- viewscale,
- };
- }
-
case 'SET_HISTORICAL_TIME': {
const {
date,
@@ -272,26 +148,20 @@ export default function canvasReducer(
};
}
- case 'SET_VIEW_COORDINATES': {
+ case 'UPDATE_VIEW': {
const { view } = action;
- const canvasSize = (state.isHistoricalView)
- ? state.historicalCanvasSize
- : state.canvasSize;
- const canvasMinXY = -canvasSize / 2;
- const canvasMaxXY = canvasSize / 2 - 1;
- const newview = view.map((z) => clamp(z, canvasMinXY, canvasMaxXY));
return {
...state,
- view: newview,
+ view: [...view],
};
}
case 'RELOAD_URL': {
const { canvases } = state;
- const nextstate = getViewFromURL(canvases);
+ const urlState = getViewFromURL(canvases);
return {
...state,
- ...nextstate,
+ ...urlState,
};
}
@@ -338,23 +208,15 @@ export default function canvasReducer(
colors,
} = canvas;
const is3D = !!canvas.v;
- // get previous view, scale and viewscale if possible
- let viewscale = DEFAULT_SCALE;
- let scale = DEFAULT_SCALE;
- let view = [0, 0, 0];
+ // get previous view if possible
+ let view = [0, 0, DEFAULT_SCALE];
let selectedColor = clrIgnore;
if (prevCanvasCoords[canvasId]) {
view = prevCanvasCoords[canvasId].view;
- viewscale = prevCanvasCoords[canvasId].viewscale;
- scale = prevCanvasCoords[canvasId].scale;
selectedColor = prevCanvasCoords[canvasId].selectedColor;
}
const palette = new Palette(colors, 0);
- if (!is3D) {
- view.length = 2;
- }
-
return fixHistoryIfNeccessary({
...state,
canvasId,
@@ -367,17 +229,13 @@ export default function canvasReducer(
palette,
clrIgnore,
view,
- viewscale,
- scale,
// reset if last canvas was retired
isHistoricalView: (!state.canvasEndDate && state.isHistoricalView),
- // remember view, scale and viewscale
+ // remember view and color
prevCanvasCoords: {
...state.prevCanvasCoords,
[prevCanvasId]: {
view: state.view,
- scale: state.scale,
- viewscale: state.viewscale,
selectedColor: state.selectedColor,
},
},
@@ -387,48 +245,36 @@ export default function canvasReducer(
case 's/REC_ME': {
const { canvases } = action;
let {
+ canvasId,
canvasIdent,
- scale,
view,
} = state;
- let canvasId = getIdFromObject(canvases, canvasIdent);
- if (canvasId === null || (
- !window.ssv?.backupurl && canvases[canvasId].ed
- )) {
- canvasId = DEFAULT_CANVAS_ID;
- canvasIdent = canvases[DEFAULT_CANVAS_ID].ident;
+ if (canvasId === null) {
+ ({ canvasId, canvasIdent, view } = getViewFromURL(canvases));
}
const canvas = canvases[canvasId];
const clrIgnore = canvas.cli || 0;
- const is3D = !!canvas.v;
const {
size: canvasSize,
sd: canvasStartDate = null,
ed: canvasEndDate = null,
colors,
} = canvas;
- const palette = new Palette(colors, 0);
-
- if (!is3D) {
- const minScale = TILE_SIZE / canvasSize;
- scale = clamp(scale, minScale, MAX_SCALE);
- view = [view[0], view[1]];
- }
+ const palette = new Palette(colors);
return fixHistoryIfNeccessary({
...state,
canvasId,
canvasIdent,
canvasSize,
- is3D,
+ is3D: !!canvas.v,
canvasStartDate,
canvasEndDate,
palette,
clrIgnore,
+ selectedColor: clrIgnore,
canvases,
- viewscale: scale,
- scale,
view,
});
}
diff --git a/src/store/sharedReducers.js b/src/store/sharedReducers.js
index 92a0aa7b..42852f42 100644
--- a/src/store/sharedReducers.js
+++ b/src/store/sharedReducers.js
@@ -15,7 +15,7 @@ import canvas from './reducers/canvas';
import chat from './reducers/chat';
import fetching from './reducers/fetching';
-export const CURRENT_VERSION = 15;
+export const CURRENT_VERSION = 17;
export const migrate = (state, version) => {
// eslint-disable-next-line no-underscore-dangle
diff --git a/src/ui/Chunk2D.js b/src/ui/Chunk2D.js
index 8b573375..ca5d6041 100644
--- a/src/ui/Chunk2D.js
+++ b/src/ui/Chunk2D.js
@@ -16,6 +16,7 @@ class Chunk2D extends Chunk {
super(zoom, cx, cy);
this.palette = palette;
this.image = document.createElement('canvas');
+ this.image.getContext('2d', { willReadFrequently: true, alpha: false });
this.image.width = TILE_SIZE;
this.image.height = TILE_SIZE;
this.ready = false;
@@ -84,7 +85,6 @@ class Chunk2D extends Chunk {
getColorIndex(cell, nearest = true) {
const [x, y] = cell;
const ctx = this.image.getContext('2d');
-
const rgb = ctx.getImageData(x, y, 1, 1).data;
const ind = (nearest)
? this.palette.getClosestIndexOfColor(rgb[0], rgb[1], rgb[2])
diff --git a/src/ui/PixelNotify.js b/src/ui/PixelNotify.js
index 0673ebb4..c9297d06 100644
--- a/src/ui/PixelNotify.js
+++ b/src/ui/PixelNotify.js
@@ -57,6 +57,8 @@ class PixelNotify {
render(
state,
$viewport,
+ view,
+ scale,
) {
const viewportCtx = $viewport.getContext('2d');
if (!viewportCtx) return;
@@ -71,7 +73,7 @@ class PixelNotify {
this.pixelList.pop();
continue;
}
- const [sx, sy] = worldToScreen(state, $viewport, [x, y])
+ const [sx, sy] = worldToScreen(view, scale, $viewport, [x, y])
.map((z) => z + this.scale / 2);
// eslint-disable-next-line max-len
diff --git a/src/ui/Renderer.js b/src/ui/Renderer.js
index 24305296..6acf7008 100644
--- a/src/ui/Renderer.js
+++ b/src/ui/Renderer.js
@@ -1,18 +1,33 @@
/*
* parent class for Renderer
*/
+import {
+ VIEW_UPDATE_DELAY,
+} from '../core/constants';
+import { updateView } from '../store/actions';
/* eslint-disable class-methods-use-this */
class Renderer {
store;
+ // object for user controls
+ constrols = {
+ update() {},
+ };
+
+ // chunk loader
+ chunkLoader = null;
// needs to be known for lazy loading THREE
is3D = null;
- // chunk loader must be set by subclass
- chunkLoader = null;
+ // current position (subclass decies what it means),
+ // will be changed by controls
+ view = [0, 0, 0];
+ //
+ #storeViewTimeout = null;
constructor(store) {
this.store = store;
+ this.loadViewFromState();
}
get chunks() {
@@ -20,19 +35,50 @@ class Renderer {
}
get recChunkIds() {
- if (!this.chunkLoader) {
- return [];
- }
+ if (!this.chunkLoader) return [];
return this.chunkLoader.recChunkIds;
}
destructor() {
- if (this.chunkLoader) {
- this.chunkLoader.destructor();
+ this.chunkLoader?.destructor();
+ this.cancelStoreViewInState();
+ }
+
+ updateView(view) {
+ for (let i = 0; i < view.length; i += 1) {
+ this.view[i] = view[i];
}
}
- render() {}
+ /*
+ * view is in both storea and renderer,
+ * the one in store is for UI elements and not
+ * updated in real time for performance reasons
+ */
+ loadViewFromState() {
+ if (!this.store) return;
+ this.updateView(this.store.getState().canvas.view);
+ }
+
+ cancelStoreViewInState() {
+ if (this.#storeViewTimeout) {
+ clearTimeout(this.#storeViewTimeout);
+ this.#storeViewTimeout = null;
+ }
+ }
+
+ storeViewInState() {
+ if (!this.store) return;
+ this.cancelStoreViewInState();
+ this.#storeViewTimeout = setTimeout(() => {
+ this.#storeViewTimeout = null;
+ this.store.dispatch(updateView(this.view));
+ }, VIEW_UPDATE_DELAY);
+ }
+
+ render() {
+ this.controls?.update();
+ }
renderPixel() {}
@@ -43,10 +89,7 @@ class Renderer {
}
gc() {
- if (!this.chunkLoader) {
- return;
- }
- this.chunkLoader.gc(this);
+ this.chunkLoader?.gc(this);
}
}
diff --git a/src/ui/Renderer2D.js b/src/ui/Renderer2D.js
index 8ab37ec7..fac58f03 100644
--- a/src/ui/Renderer2D.js
+++ b/src/ui/Renderer2D.js
@@ -3,12 +3,17 @@
*
*/
-import { TILE_ZOOM_LEVEL, TILE_SIZE } from '../core/constants';
+import {
+ TILE_ZOOM_LEVEL,
+ TILE_SIZE,
+ MAX_SCALE,
+} from '../core/constants';
import {
getTileOfPixel,
getPixelFromChunkOffset,
getMaxTiledZoom,
+ clamp,
} from '../core/utils';
import {
@@ -23,9 +28,8 @@ import ChunkLoader from './ChunkLoader2D';
import pixelNotify from './PixelNotify';
class Renderer2D extends Renderer {
- is3D = false;
- //
canvasId = null;
+ viewscale;
//--
centerChunk;
tiledScale;
@@ -43,6 +47,9 @@ class Renderer2D extends Renderer {
constructor(store) {
super(store);
+ this.is3D = false;
+ [,, this.viewscale] = this.view;
+
this.centerChunk = [null, null];
this.tiledScale = 0;
this.tiledZoom = 4;
@@ -58,22 +65,23 @@ class Renderer2D extends Renderer {
//--
const viewport = document.createElement('canvas');
viewport.className = 'viewport';
+ const viewportCtx = viewport.getContext('2d', { alpha: false });
this.viewport = viewport;
- //--
- this.canvas = document.createElement('canvas');
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d', { alpha: false });
+ this.canvas = canvas;
this.onWindowResize();
- document.body.appendChild(this.viewport);
//--
+ context.fillStyle = '#C4C4C4';
+ context.fillRect(0, 0, this.canvas.width, this.canvas.height);
+ viewportCtx.fillStyle = '#C4C4C4';
+ viewportCtx.fillRect(0, 0, this.viewport.width, this.viewport.height);
+ //--
+ document.body.appendChild(this.viewport);
this.onWindowResize = this.onWindowResize.bind(this);
window.addEventListener('resize', this.onWindowResize);
-
- const context = this.canvas.getContext('2d');
- context.fillStyle = '#000000';
- context.fillRect(0, 0, this.canvas.width, this.canvas.height);
//--
- const state = store.getState();
- this.updateCanvasData(state);
- this.updateScale(state);
+ this.updateCanvasData(store.getState());
this.controls = new PixelPainterControls(this, this.viewport, store);
}
@@ -133,8 +141,10 @@ class Renderer2D extends Renderer {
canvases[canvasId].historicalSizes,
);
}
+ // scale of 0 is impossible, so it always updates
+ this.view[2] = 0;
+ this.updateView(state.canvas.view);
}
- this.updateScale(state);
}
updateOldHistoricalTime(oldDate, oldTime) {
@@ -150,7 +160,7 @@ class Renderer2D extends Renderer {
historicalCanvasSize,
);
this.forceNextRender = true;
- this.updateScale(this.store.getState());
+ this.updateView(this.store.getState().canvas.view);
}
getColorIndexOfPixel(cx, cy, historical = false) {
@@ -167,57 +177,76 @@ class Renderer2D extends Renderer {
return this.chunkLoader.getColorIndexOfPixel(cx, cy);
}
- updateScale(
- state,
- prevScale = null,
- ) {
- const {
- viewscale,
- isHistoricalView,
- } = state.canvas;
- pixelNotify.updateScale(viewscale);
- let tiledScale = (viewscale > 0.5)
- ? 0
- : Math.round(Math.log2(viewscale) * 2 / TILE_ZOOM_LEVEL);
- tiledScale = TILE_ZOOM_LEVEL ** tiledScale;
- const canvasMaxTiledZoom = (isHistoricalView)
- ? this.historicalCanvasMaxTiledZoom
- : this.canvasMaxTiledZoom;
- const tiledZoom = canvasMaxTiledZoom + Math.log2(tiledScale)
- * 2 / TILE_ZOOM_LEVEL;
- const relScale = viewscale / tiledScale;
-
- this.tiledScale = tiledScale;
- this.tiledZoom = tiledZoom;
- this.relScale = relScale;
- this.updateView(state);
- if (prevScale === null
- || viewscale < this.scaleThreshold || prevScale < this.scaleThreshold) {
- this.forceNextRender = true;
- } else {
- this.forceNextSubrender = true;
- }
- }
-
- updateView(state) {
- const {
- view,
- } = state.canvas;
- const canvasSize = (state.canvas.isHistoricalView)
+ updateView(view, origin) {
+ let [x, y, scale] = view;
+ const state = this.store.getState();
+ const { isHistoricalView } = state.canvas;
+ const canvasSize = (isHistoricalView)
? state.canvas.historicalCanvasSize
: state.canvas.canvasSize;
- const [x, y] = view;
- let [cx, cy] = this.centerChunk;
- const [curcx, curcy] = getTileOfPixel(
+ // clamp scale and set viewscale
+ if (scale) {
+ const minScale = (isHistoricalView) ? 0.7 : TILE_SIZE / canvasSize;
+ scale = clamp(view[2], minScale, MAX_SCALE);
+ if (origin) {
+ let scalediff = this.viewscale;
+ // clamp to 1.0 (only when origin is given, so not on phones)
+ this.viewscale = (scale > 0.85 && scale < 1.20) ? 1.0 : scale;
+ // make sure that origin is at the same place on the screen
+ scalediff /= this.viewscale;
+ const [px, py] = origin;
+ x = px + (x - px) * scalediff;
+ y = py + (y - py) * scalediff;
+ } else {
+ this.viewscale = scale;
+ }
+ } else {
+ [,, scale] = this.view;
+ }
+ // clamp coords
+ const canvasMinXY = -canvasSize / 2;
+ const canvasMaxXY = canvasSize / 2 - 1;
+ x = clamp(x, canvasMinXY, canvasMaxXY);
+ y = clamp(y, canvasMinXY, canvasMaxXY);
+
+ const prevScale = this.view[2];
+ super.updateView([x, y, scale]);
+
+ if (prevScale !== scale) {
+ const { viewscale } = this;
+ pixelNotify.updateScale(viewscale);
+ let tiledScale = (viewscale > 0.5)
+ ? 0
+ : Math.round(Math.log2(viewscale) * 2 / TILE_ZOOM_LEVEL);
+ tiledScale = TILE_ZOOM_LEVEL ** tiledScale;
+ const canvasMaxTiledZoom = (isHistoricalView)
+ ? this.historicalCanvasMaxTiledZoom
+ : this.canvasMaxTiledZoom;
+ const tiledZoom = canvasMaxTiledZoom + Math.log2(tiledScale)
+ * 2 / TILE_ZOOM_LEVEL;
+ const relScale = viewscale / tiledScale;
+ this.tiledScale = tiledScale;
+ this.tiledZoom = tiledZoom;
+ this.relScale = relScale;
+ if (viewscale < this.scaleThreshold || prevScale < this.scaleThreshold) {
+ this.forceNextRender = true;
+ } else {
+ this.forceNextSubrender = true;
+ }
+ }
+
+ const prevCenterChunk = this.centerChunk;
+ const centerChunk = getTileOfPixel(
this.tiledScale,
[x, y],
canvasSize,
);
- if (cx !== curcx || cy !== curcy) {
- cx = curcx;
- cy = curcy;
- this.centerChunk = [cx, cy];
+ if (!prevCenterChunk
+ || prevCenterChunk[0] !== centerChunk[0]
+ || prevCenterChunk[1] !== centerChunk[1]
+ ) {
+ this.centerChunk = centerChunk;
this.forceNextRender = true;
} else {
this.forceNextSubrender = true;
@@ -236,9 +265,9 @@ class Renderer2D extends Renderer {
const {
canvasSize,
palette,
- scale,
isHistoricalView,
} = state.canvas;
+ const scale = this.viewscale;
this.chunkLoader.getPixelUpdate(i, j, offset, color);
if (scale < 0.8 || isHistoricalView) return;
@@ -315,9 +344,9 @@ class Renderer2D extends Renderer {
tiledScale,
tiledZoom,
viewport,
+ viewscale: scale,
} = this;
const {
- viewscale: scale,
canvasSize,
} = state.canvas;
@@ -405,6 +434,7 @@ class Renderer2D extends Renderer {
if (!this.chunkLoader) {
return;
}
+ super.render();
const state = this.store.getState();
if (state.canvas.isHistoricalView) {
this.renderHistorical(state);
@@ -421,6 +451,8 @@ class Renderer2D extends Renderer {
) {
const {
viewport,
+ view,
+ viewscale,
} = this;
const {
showGrid,
@@ -432,8 +464,6 @@ class Renderer2D extends Renderer {
fetchingPixel,
} = state.fetching;
const {
- view,
- viewscale,
canvasSize,
hover,
} = state.canvas;
@@ -523,16 +553,18 @@ class Renderer2D extends Renderer {
}
if (showGrid && viewscale >= 8) {
- renderGrid(state, viewport, viewscale, isLightGrid);
+ renderGrid(state, viewport, view, viewscale, isLightGrid);
}
- if (doRenderPixelnotify) pixelNotify.render(state, viewport);
+ if (doRenderPixelnotify) {
+ pixelNotify.render(state, viewport, view, viewscale);
+ }
if (hover && doRenderPlaceholder) {
- renderPlaceholder(state, viewport, viewscale);
+ renderPlaceholder(state, viewport, view, viewscale);
}
if (hover && doRenderPotatoPlaceholder) {
- renderPotatoPlaceholder(state, viewport, viewscale);
+ renderPotatoPlaceholder(state, viewport, view, viewscale);
}
}
@@ -546,10 +578,10 @@ class Renderer2D extends Renderer {
const {
centerChunk: chunkPosition,
viewport,
+ viewscale,
oldHistoricalTime,
} = this;
const {
- viewscale,
historicalDate,
historicalTime,
historicalCanvasSize,
@@ -672,14 +704,14 @@ class Renderer2D extends Renderer {
) {
const {
viewport,
+ view,
+ viewscale,
} = this;
const {
showGrid,
isLightGrid,
} = state.gui;
const {
- view,
- viewscale,
historicalCanvasSize,
} = state.canvas;
diff --git a/src/ui/Renderer3D.js b/src/ui/Renderer3D.js
index de2c38fb..2ee86486 100644
--- a/src/ui/Renderer3D.js
+++ b/src/ui/Renderer3D.js
@@ -26,8 +26,6 @@ import pixelTransferController from './PixelTransferController';
const renderDistance = 150;
class Renderer3D extends Renderer {
- is3D = true;
- //--
scene;
camera;
rollOverMesh;
@@ -51,6 +49,7 @@ class Renderer3D extends Renderer {
constructor(store) {
super(store);
+ this.is3D = true;
const state = store.getState();
this.objects = [];
@@ -163,6 +162,7 @@ class Renderer3D extends Renderer {
// controls
const controls = new VoxelPainterControls(
+ this,
camera,
domElement,
store,
@@ -354,7 +354,7 @@ class Renderer3D extends Renderer {
if (!this.threeRenderer) {
return;
}
- this.controls.update();
+ super.render();
if (this.forceNextRender) {
this.reloadChunks();
this.forceNextRender = false;
diff --git a/src/ui/render2Delements.js b/src/ui/render2Delements.js
index 79c9a059..bdd97928 100644
--- a/src/ui/render2Delements.js
+++ b/src/ui/render2Delements.js
@@ -11,14 +11,14 @@ const PLACEHOLDER_BORDER = 1;
export function renderPlaceholder(
state,
$viewport,
+ view,
scale,
) {
const viewportCtx = $viewport.getContext('2d');
- const { hover } = state.canvas;
- const { palette, selectedColor } = state.canvas;
+ const { hover, palette, selectedColor } = state.canvas;
- const [sx, sy] = worldToScreen(state, $viewport, hover);
+ const [sx, sy] = worldToScreen(view, scale, $viewport, hover);
viewportCtx.save();
viewportCtx.translate(sx + (scale / 2), sy + (scale / 2));
@@ -45,6 +45,7 @@ export function renderPlaceholder(
export function renderPotatoPlaceholder(
state,
$viewport,
+ view,
scale,
) {
const viewportCtx = $viewport.getContext('2d');
@@ -52,7 +53,7 @@ export function renderPotatoPlaceholder(
const { hover } = state.canvas;
const { palette, selectedColor } = state.canvas;
- const [sx, sy] = worldToScreen(state, $viewport, hover);
+ const [sx, sy] = worldToScreen(view, scale, $viewport, hover);
viewportCtx.save();
viewportCtx.fillStyle = '#000';
@@ -72,6 +73,7 @@ export function renderPotatoPlaceholder(
export function renderGrid(
state,
$viewport,
+ view,
scale,
isLightGrid,
) {
@@ -83,8 +85,8 @@ export function renderGrid(
viewportCtx.globalAlpha = 0.5;
viewportCtx.fillStyle = (isLightGrid) ? '#DDDDDD' : '#222222';
- let [xoff, yoff] = screenToWorld(state, $viewport, [0, 0]);
- let [x, y] = worldToScreen(state, $viewport, [xoff, yoff]);
+ let [xoff, yoff] = screenToWorld(view, scale, $viewport, [0, 0]);
+ let [x, y] = worldToScreen(view, scale, $viewport, [xoff, yoff]);
for (; x < width; x += scale) {
const thick = (xoff++ % 10 === 0) ? 2 : 1;
diff --git a/src/ui/rendererFactory.js b/src/ui/rendererFactory.js
index fcc2155d..663bd5a2 100644
--- a/src/ui/rendererFactory.js
+++ b/src/ui/rendererFactory.js
@@ -24,23 +24,29 @@ animationLoop();
export async function initRenderer(store, is3D) {
renderer.destructor();
- if (is3D) {
- if (!isWebGL2Available()) {
- store.dispatch(pAlert(
- t`Canvas Error`,
- t`Can't render 3D canvas, do you have WebGL2 disabled?`,
- 'error',
- 'OK',
- ));
- renderer = dummyRenderer;
- } else {
- /* eslint-disable-next-line max-len */
- const module = await import(/* webpackChunkName: "voxel" */ './Renderer3D');
- const Renderer3D = module.default;
- renderer = new Renderer3D(store);
+ switch (is3D) {
+ case true: {
+ if (!isWebGL2Available()) {
+ store.dispatch(pAlert(
+ t`Canvas Error`,
+ t`Can't render 3D canvas, do you have WebGL2 disabled?`,
+ 'error',
+ 'OK',
+ ));
+ renderer = dummyRenderer;
+ } else {
+ /* eslint-disable-next-line max-len */
+ const module = await import(/* webpackChunkName: "voxel" */ './Renderer3D');
+ const Renderer3D = module.default;
+ renderer = new Renderer3D(store);
+ }
+ break;
}
- } else {
- renderer = new Renderer2D(store);
+ case false:
+ renderer = new Renderer2D(store);
+ break;
+ default:
+ renderer = dummyRenderer;
}
return renderer;
}