prefetch /api/me

move scale into view and move view from store into renderer
(breaks WASD and 3D is unfinished)
This commit is contained in:
HF 2024-01-19 23:07:47 +01:00
parent 1077831c23
commit 0254f7d820
24 changed files with 407 additions and 528 deletions

View File

@ -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', () => {

View File

@ -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 (
<div
className="coorbox"
@ -31,8 +42,7 @@ const CoordinatesBox = () => {
title={t`Copy to Clipboard`}
tabIndex="0"
>{
renderCoordinates(hover
|| view.map(Math.round))
renderCoordinates(coords)
}</div>
);
};

View File

@ -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 = () => {
<PalselButton />
<Palette />
{(!is3D) && <GlobeButton />}
{(is3D && isOnMobile) && <Mobile3DControls />}
{(isOnMobile) && <MovementControls />}
<CoolDownBox />
</>
)}

View File

@ -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 = () => (
<div>
<div
className="actionbuttons"
@ -220,4 +220,4 @@ const Mobile3DControls = () => (
</div>
);
export default Mobile3DControls;
export default MovementControls;

View File

@ -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();

View File

@ -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();
}
}
/*

View File

@ -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;

View File

@ -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),
];
}

View File

@ -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:;`;

View File

@ -53,7 +53,7 @@ function generatePopUpPage(req) {
/>
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
<script>window.ssv=JSON.parse('${ssvR}')</script>
<script>window.ssv=JSON.parse('${ssvR}');window.me=fetch('${shard || ''}/api/me',{credentials:'include'})</script>
<link rel="stylesheet" type="text/css" id="globcss" href="${getCssAssets().default}" />
</head>
<body>

View File

@ -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',
);

View File

@ -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',

View File

@ -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,
*/

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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,
});
}

View File

@ -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

View File

@ -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])

View File

@ -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

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}