diff --git a/src/client.js b/src/client.js index 6e5ac22..07b3e02 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 3750225..f850bc5 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 8b1d480..7425807 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 = () => { {(!is3D) && } - {(is3D && isOnMobile) && } + {(isOnMobile) && } )} diff --git a/src/components/Mobile3DControls.jsx b/src/components/buttons/MovementControls.jsx similarity index 97% rename from src/components/Mobile3DControls.jsx rename to src/components/buttons/MovementControls.jsx index be8c06e..ced438b 100644 --- a/src/components/Mobile3DControls.jsx +++ b/src/components/buttons/MovementControls.jsx @@ -5,7 +5,7 @@ import React from 'react'; -import { getRenderer } from '../ui/rendererFactory'; +import { getRenderer } from '../../ui/rendererFactory'; const btnStyle = { fontSize: 34, @@ -53,7 +53,7 @@ function cancelMovement() { renderer.controls.moveDown = false; } -const Mobile3DControls = () => ( +const MovementControls = () => (
(
); -export default Mobile3DControls; +export default MovementControls; diff --git a/src/controls/PixelPainterControls.js b/src/controls/PixelPainterControls.js index 5a39b06..c095751 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 4e1bfcf..fc2e35f 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 975781e..e08b0de 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 9fb12f1..e76c574 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 296c5e2..3a6ee29 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 64075c7..64b3603 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 4fd046f..028bfd0 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 b360526..7ced116 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 5decd89..fd94ed2 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 8c5cf3d..8f91207 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 109a854..2fe5290 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 397ea96..8bd2001 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 92a0aa7..42852f4 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 8b57337..ca5d604 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 0673ebb..c9297d0 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 2430529..6acf700 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 8ab37ec..fac58f0 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 de2c38f..2ee8648 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 79c9a05..bdd9792 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 fcc2155..663bd5a 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; }