Skip to content

Commit

Permalink
feat(perturbation-theory): change precision dynamically
Browse files Browse the repository at this point in the history
  • Loading branch information
JMaio committed Feb 16, 2021
1 parent c970135 commit 220afa0
Show file tree
Hide file tree
Showing 14 changed files with 375 additions and 203 deletions.
269 changes: 153 additions & 116 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Grid, ThemeProvider } from '@material-ui/core';
import { Grid } from '@material-ui/core';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useSpring } from 'react-spring';
import './App.css';
Expand All @@ -8,15 +8,25 @@ import {
useHashNavigator,
ViewerURLManager,
} from './common/routing';
import { settingsDefinitionsType } from './common/settings';
import {
SpringAnimatedValueWithSetter,
SpringControl,
springControlKeys,
ViewerControlSprings,
ViewerLocation,
ViewerRotationControl,
ViewerXYControl,
ViewerZoomControl,
} from './common/types';
import { useWindowSize, warpToPoint } from './common/utils';
import { springsConfigs, viewerOrigin } from './common/values';
import {
deepZoomPrecision,
defaultPrecision,
PrecisionFormatter,
springsConfigs,
viewerOrigin,
} from './common/values';
import CoordinateInterface from './components/info/CoordinateInterface';
import FirstTimeInfo from './components/info/FirstTimeInfo';
import InfoDialog from './components/info/InfoDialog';
Expand All @@ -25,14 +35,23 @@ import JuliaRenderer from './components/render/JuliaRenderer';
import MandelbrotRenderer from './components/render/MandelbrotRenderer';
import MandelbrotRendererDeep from './components/render/MandelbrotRendererDeep';
import ViewChanger from './components/render/ViewChanger';
import ServiceWorkerWrapper from './components/ServiceWorkerWrapper';
import SettingsProvider, { SettingsContext } from './components/settings/SettingsContext';
import SettingsMenu from './components/settings/SettingsMenu';
import theme from './theme/theme';

function App(): JSX.Element {
function App({ settings }: { settings: settingsDefinitionsType }): JSX.Element {
const size = useWindowSize();
const vertical = size.w < size.h;

const DPR = window.devicePixelRatio;
const currentDPR = settings.useDPR ? DPR : 1;

// set application float precision based on whether deep zoom is enabled
const [precision, setPrecision] = useState(defaultPrecision);
const precisionFormatter = PrecisionFormatter(precision);

// update the new precision
useEffect(() => {
setPrecision(settings.deepZoom ? deepZoomPrecision : defaultPrecision);
}, [settings.deepZoom]);

// if app is started with a hash location, assume
// it should be the starting position
Expand All @@ -43,63 +62,98 @@ function App(): JSX.Element {
// non-reloading hash update
const updateBrowserHash = useHashNavigator();

// why is this a memo?
// does making it a memo mean it won't slow down initial rendering?
const urlManager = useMemo(() => new ViewerURLManager(), []);

// generic callback
/**
* generic callback.
* called when the viewer has moved and the hash url needs to be updated
*/
const updateHash = useCallback(
(name: string, v: Partial<ViewerLocation>) => {
urlManager.updateViewer(name, v);
// console.log('deepzoom:', settings.deepZoom);
urlManager.updateViewer(name, v, precisionFormatter);
const u = urlManager.asFullHashURL();
// console.debug(`Updated # for ${name} => ${u}`);
updateBrowserHash(u);
},
[updateBrowserHash, urlManager],
[precisionFormatter, updateBrowserHash, urlManager],
);

// The 'updateM' function makes the dependencies of useEffect Hook (at line 127) change on
// every render. To fix this, wrap the definition of 'updateM' in its own useCallback() Hook
// callbacks for springs to update url when animation stops
const updateM = (v: Partial<ViewerLocation>) => updateHash('m', v);
const updateJ = (v: Partial<ViewerLocation>) => updateHash('j', v);
const updateM = useCallback((v: Partial<ViewerLocation>) => updateHash('m', v), [
updateHash,
]);
const updateJ = useCallback((v: Partial<ViewerLocation>) => updateHash('j', v), [
updateHash,
]);

const mandelbrotControls: ViewerControlSprings = {
xyCtrl: useSpring<ViewerXYControl>(() => ({
xy: viewerOrigin.xy,
config: springsConfigs.default.xy,
config: springsConfigs(precision).default.xyCtrl,
onRest: updateM,
})),

zoomCtrl: useSpring<ViewerZoomControl>(() => ({
z: viewerOrigin.z,
minZoom: 0.5,
maxZoom: 100000000000,
config: springsConfigs.default.zoom,
// Numeric literals with absolute values equal to 2^53 or greater are too large
// to be represented accurately as integers. [ts(80008)]
maxZoom: 1e16,
// 100_000_000_000_000_000,
config: springsConfigs(precision).default.zoomCtrl,
onRest: updateM,
})),

rotCtrl: useSpring<ViewerRotationControl>(() => ({
theta: viewerOrigin.theta, // all angles in rad
config: springsConfigs.default.rot,
config: springsConfigs(precision).default.rotCtrl,
onRest: updateM,
})),
};

// make sure that the spring "onRest" is updated if the precision changes
useEffect(() => {
Object.entries(mandelbrotControls).forEach(
([k, [, set]]: [string, SpringAnimatedValueWithSetter<SpringControl>]) => {
try {
const key = k as springControlKeys;
set({
onRest: updateM,
// hacky way to grab the config
config: springsConfigs(precision).default[key],
});
} catch (error) {
// guess not
}
},
);
// explicitly not adding mandelbrotControls to the deps list
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [precision, updateM]);

const juliaControls: ViewerControlSprings = {
xyCtrl: useSpring<ViewerXYControl>(() => ({
xy: viewerOrigin.xy,
config: springsConfigs.default.xy,
config: springsConfigs(precision).default.xyCtrl,
onRest: updateJ,
})),

zoomCtrl: useSpring<ViewerZoomControl>(() => ({
z: viewerOrigin.z,
minZoom: 0.5,
maxZoom: 2000,
config: springsConfigs.default.zoom,
config: springsConfigs(precision).default.zoomCtrl,
onRest: updateJ,
})),

rotCtrl: useSpring<ViewerRotationControl>(() => ({
theta: viewerOrigin.theta, // all angles in rad
config: springsConfigs.default.rot,
config: springsConfigs(precision).default.rotCtrl,
onRest: updateJ,
})),
};
Expand All @@ -111,15 +165,15 @@ function App(): JSX.Element {
console.log(`Warping to requested url => ${currentLocation()}`);

// warp to the newly parsed locations
warpToPoint(mandelbrotControls, urlManager.vs['m'].v);
warpToPoint(juliaControls, urlManager.vs['j'].v);
warpToPoint(mandelbrotControls, urlManager.vs['m'].v, precision);
warpToPoint(juliaControls, urlManager.vs['j'].v, precision);
// this update process should only trigger when the hash location changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loc]);

const reset = () => {
warpToPoint(mandelbrotControls, viewerOrigin);
warpToPoint(juliaControls, viewerOrigin);
warpToPoint(mandelbrotControls, viewerOrigin, precision);
warpToPoint(juliaControls, viewerOrigin, precision);
};

const [showInfo, setShowInfo] = useState(false);
Expand All @@ -133,106 +187,89 @@ function App(): JSX.Element {
true,
true,
]);
// const [showMandelbrot, setShowMandelbrot] = useState(true);
// const [showJulia, setShowJulia] = useState(true);
// const [showMandelbrot, showJulia] = viewerState;

// Wrap the Typography component with animated first
// const AnimatedTypography = animated(Typography)
// <AnimatedTypography></AnimatedTypography>
// const AnimatedGrid = animated(Grid);
return (
<>
<Grid container>
<Grid
item
container
direction={vertical ? 'column-reverse' : 'row'}
alignItems={vertical ? 'flex-end' : 'flex-start'}
justify="center"
className="fullSize"
style={{
position: 'absolute',
}}
>
<CoordinateInterface
show={settings.showCoordinates}
mandelbrot={mandelbrotControls}
precision={precision}
precisionFormatter={precisionFormatter}
/>
<Grid
item
xs
className="renderer"
style={{
// flex-grow takes up more space in a ratio format
flexGrow: showMandelbrot ? 1 : 0, // percentFlex.m.interpolate((x) => x),
}}
>
{settings.deepZoom ? (
<MandelbrotRendererDeep
controls={mandelbrotControls}
DPR={currentDPR}
precision={precision}
{...settings}
/>
) : (
<MandelbrotRenderer
controls={mandelbrotControls}
DPR={currentDPR}
precision={precision}
{...settings}
/>
)}
</Grid>

// const [showDeep, setDeep] = useState(false);
// const toggleDeep = () => setDeep((p) => !p);
<Grid item style={{ width: 0, height: 0, zIndex: 1 }}>
<ViewChanger vertical={vertical} changeFunc={setViewerState} />
</Grid>

return (
<ThemeProvider theme={theme}>
<ServiceWorkerWrapper />
<SettingsProvider>
<Grid container>
<SettingsContext.Consumer>
{({ settings }) => {
const currentDPR = settings.useDPR ? DPR : 1;
const vertical = size.w < size.h;
return (
// JSX expressions must have one parent element
<Grid
item
container
direction={vertical ? 'column-reverse' : 'row'}
alignItems={vertical ? 'flex-end' : 'flex-start'}
justify="center"
className="fullSize"
style={{
position: 'absolute',
}}
>
<CoordinateInterface
show={settings.showCoordinates}
mandelbrot={mandelbrotControls}
/>
<Grid
item
xs
className="renderer"
style={{
// flex-grow takes up more space in a ratio format
flexGrow: showMandelbrot ? 1 : 0, // percentFlex.m.interpolate((x) => x),
}}
>
{settings.deepZoom ? (
<MandelbrotRendererDeep
controls={mandelbrotControls}
DPR={currentDPR}
{...settings}
/>
) : (
<MandelbrotRenderer
controls={mandelbrotControls}
DPR={currentDPR}
{...settings}
/>
)}
</Grid>

<Grid item style={{ width: 0, height: 0, zIndex: 1 }}>
<ViewChanger vertical={vertical} changeFunc={setViewerState} />
</Grid>

<Grid
item
xs
className="renderer"
style={{
// flex-grow takes up more space in a ratio format
flexGrow: showJulia ? 1 : 0, // percentFlex.j.interpolate((x) => x),
}}
>
<JuliaRenderer
c={mandelbrotControls.xyCtrl[0].xy}
controls={juliaControls}
DPR={currentDPR}
{...settings}
/>
</Grid>

{/* Settings menu uses SettingsContext so must be within provider */}
<SettingsMenu
reset={reset}
toggleInfo={toggleInfo}
helpState={[openHelp, toggleHelp]}
/>
</Grid>
);
<Grid
item
xs
className="renderer"
style={{
// flex-grow takes up more space in a ratio format
flexGrow: showJulia ? 1 : 0, // percentFlex.j.interpolate((x) => x),
}}
</SettingsContext.Consumer>
>
<JuliaRenderer
c={mandelbrotControls.xyCtrl[0].xy}
controls={juliaControls}
DPR={currentDPR}
precision={precision}
{...settings}
/>
</Grid>

{/* Settings menu uses SettingsContext so must be within provider */}
<SettingsMenu
reset={reset}
toggleInfo={toggleInfo}
helpState={[openHelp, toggleHelp]}
/>
</Grid>
<InfoDialog ctrl={[showInfo, setShowInfo]} />
);
</Grid>
<InfoDialog ctrl={[showInfo, setShowInfo]} />

{/* FirstTimeInfo requires access to settings */}
<FirstTimeInfo ctrl={[openHelp, toggleHelp]} />
</SettingsProvider>
</ThemeProvider>
{/* FirstTimeInfo requires access to settings */}
<FirstTimeInfo ctrl={[openHelp, toggleHelp]} />
</>
);
}

Expand Down
Loading

0 comments on commit 220afa0

Please sign in to comment.