Skip to content

Commit

Permalink
feat: handle pausing visualizer
Browse files Browse the repository at this point in the history
  • Loading branch information
VmMad committed Feb 15, 2024
1 parent 0dbc932 commit c93e586
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 87 deletions.
69 changes: 19 additions & 50 deletions client/src/features/visualizer-threejs/Emitter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,10 @@ import React, { RefObject, Dispatch, SetStateAction, useEffect, useRef } from "r
import * as THREE from "three";
import { useConfigStore, useTangleStore } from "./store";
import { useRenderTangle } from "./useRenderTangle";
import { getTangleDistances, getSinusoidalPosition } from "./utils";
import { getTangleDistances, getEmitterPosition } from "./utils";
import { CanvasElement } from "./enums";
import {
EMITTER_SPEED_MULTIPLIER,
EMITTER_DEPTH,
EMITTER_HEIGHT,
EMITTER_WIDTH,
MAX_SINUSOIDAL_AMPLITUDE,
SINUSOIDAL_AMPLITUDE_ACCUMULATOR,
HALF_WAVE_PERIOD_SECONDS,
INITIAL_SINUSOIDAL_AMPLITUDE,
} from "./constants";
import useVisualizerTimer from "~/helpers/nova/hooks/useVisualizerTimer";
import { EMITTER_DEPTH, EMITTER_HEIGHT, EMITTER_WIDTH } from "./constants";

interface EmitterProps {
readonly setRunListeners: Dispatch<SetStateAction<boolean>>;
Expand All @@ -26,18 +18,15 @@ const Emitter: React.FC<EmitterProps> = ({ setRunListeners, emitterRef }: Emitte
const setZoom = useTangleStore((s) => s.setZoom);
const get = useThree((state) => state.get);
const currentZoom = useThree((state) => state.camera.zoom);
const groupRef = useRef<THREE.Group>(null);
const camera = get().camera;

const { xTangleDistance, yTangleDistance } = getTangleDistances();
const isPlaying = useConfigStore((state) => state.isPlaying);
const setIsPlaying = useConfigStore((state) => state.setIsPlaying);
const setInitialTime = useConfigStore((state) => state.setInitialTime);
const getVisualizerTimeDiff = useVisualizerTimer();

const animationTime = useRef<number>(0);
const currentAmplitude = useRef<number>(INITIAL_SINUSOIDAL_AMPLITUDE);

const previousRealTime = useRef<number>(0);
const previousPeakTime = useRef<number>(0);
const groupRef = useRef<THREE.Group>(null);

useEffect(() => {
setZoom(currentZoom);
Expand All @@ -47,6 +36,7 @@ const Emitter: React.FC<EmitterProps> = ({ setRunListeners, emitterRef }: Emitte
if (emitterRef?.current) {
setIsPlaying(true);
setRunListeners(true);
setInitialTime(Date.now());
}

return () => {
Expand All @@ -55,47 +45,26 @@ const Emitter: React.FC<EmitterProps> = ({ setRunListeners, emitterRef }: Emitte
};
}, [emitterRef?.current]);

useFrame(() => {
if (camera && groupRef.current) {
camera.position.x = groupRef.current.position.x;
}
});

function updateAnimationTime(realTimeDelta: number): void {
animationTime.current += realTimeDelta;
}

function checkAndHandleNewPeak(): void {
const currentHalfWaveCount = Math.floor(animationTime.current / HALF_WAVE_PERIOD_SECONDS);
const lastPeakHalfWaveCount = Math.floor(previousPeakTime.current / HALF_WAVE_PERIOD_SECONDS);

if (currentHalfWaveCount > lastPeakHalfWaveCount) {
currentAmplitude.current = Math.min(currentAmplitude.current + SINUSOIDAL_AMPLITUDE_ACCUMULATOR, MAX_SINUSOIDAL_AMPLITUDE);
previousPeakTime.current = animationTime.current;
}
}

/**
* Emitter shift
*/
useFrame(({ clock }, delta) => {
const currentRealTime = clock.getElapsedTime();
const realTimeDelta = currentRealTime - previousRealTime.current;
previousRealTime.current = currentRealTime;
useFrame(() => {
const currentAnimationTime = getVisualizerTimeDiff();

if (isPlaying) {
updateAnimationTime(realTimeDelta);
checkAndHandleNewPeak();
const { x, y } = getEmitterPosition(currentAnimationTime);

if (emitterRef.current) {
emitterRef.current.position.x = x;
emitterRef.current.position.y = y;
}

if (groupRef.current) {
const { x } = groupRef.current.position;
const newXPos = x + delta * EMITTER_SPEED_MULTIPLIER;
groupRef.current.position.x = newXPos;
groupRef.current.position.x = x;
}

if (emitterRef.current) {
const newYPos = getSinusoidalPosition(animationTime.current, currentAmplitude.current);
emitterRef.current.position.y = newYPos;
if (camera) {
camera.position.x = x;
}
}
});
Expand All @@ -108,7 +77,7 @@ const Emitter: React.FC<EmitterProps> = ({ setRunListeners, emitterRef }: Emitte
{/* TangleWrapper Mesh */}
<mesh name={CanvasElement.TangleWrapperMesh} position={[-(xTangleDistance / 2), 0, 0]}>
<boxGeometry args={[xTangleDistance, yTangleDistance, 0]} attach="geometry" />
<meshPhongMaterial transparent opacity={0} wireframe={true} attach="material" />
<meshPhongMaterial transparent opacity={0} wireframe attach="material" />
</mesh>

{/* Emitter Mesh */}
Expand Down
47 changes: 24 additions & 23 deletions client/src/features/visualizer-threejs/VisualizerInstance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,16 @@ import { Perf } from "r3f-perf";
import React, { useEffect, useRef } from "react";
import { RouteComponentProps } from "react-router-dom";
import * as THREE from "three";
import { Box3 } from "three";
import {
FAR_PLANE,
NEAR_PLANE,
DIRECTIONAL_LIGHT_INTENSITY,
PENDING_BLOCK_COLOR,
VISUALIZER_BACKGROUND,
EMITTER_X_POSITION_MULTIPLIER,
BLOCK_STATE_TO_COLOR,
} from "./constants";
import Emitter from "./Emitter";
import { useTangleStore, useConfigStore } from "./store";
import { getGenerateDynamicYZPosition, randomIntFromInterval } from "./utils";
import { BPSCounter } from "./BPSCounter";
import { VisualizerRouteProps } from "../../app/routes/VisualizerRouteProps";
import { ServiceFactory } from "../../factories/serviceFactory";
Expand All @@ -31,6 +28,8 @@ import { BasicBlockBody, IBlockMetadata } from "@iota/sdk-wasm-nova/web";
import { IFeedBlockData } from "~/models/api/nova/feed/IFeedBlockData";
import CameraControls from "./CameraControls";
import "./Visualizer.scss";
import useVisualizerTimer from "~/helpers/nova/hooks/useVisualizerTimer";
import { getBlockInitPosition, getBlockTargetPosition } from "./blockPositions";

const features = {
statsEnabled: true,
Expand All @@ -43,7 +42,6 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
},
}) => {
const [networkConfig] = useNetworkConfig(network);
const generateYZPositions = getGenerateDynamicYZPosition();
const themeMode = useGetThemeMode();

const [runListeners, setRunListeners] = React.useState<boolean>(false);
Expand All @@ -61,6 +59,8 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
const setDimensions = useConfigStore((s) => s.setDimensions);
const isPlaying = useConfigStore((s) => s.isPlaying);
const setIsPlaying = useConfigStore((s) => s.setIsPlaying);
const inView = useConfigStore((s) => s.inView);
const setInView = useConfigStore((s) => s.setInView);
const addBlock = useTangleStore((s) => s.addToBlockQueue);
const addToEdgeQueue = useTangleStore((s) => s.addToEdgeQueue);
const addToColorQueue = useTangleStore((s) => s.addToColorQueue);
Expand All @@ -74,18 +74,24 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
const emitterRef = useRef<THREE.Mesh>(null);
const [feedService, setFeedService] = React.useState<NovaFeedClient | null>(ServiceFactory.get<NovaFeedClient>(`feed-${network}`));

const getCurrentAnimationTime = useVisualizerTimer();

/**
* Pause on tab or window change
*/
useEffect(() => {
const handleVisibilityChange = async () => {
if (document.hidden) {
setIsPlaying(false);
setInView(false);
} else {
setInView(true);
}
};

const handleBlur = async () => {
setIsPlaying(false);
setInView(false);
};

document.addEventListener("visibilitychange", handleVisibilityChange);
Expand Down Expand Up @@ -160,7 +166,9 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
if (!runListeners) {
return;
}

setIsPlaying(true);
setInView(true);

return () => {
bpsCounter.stop();
Expand Down Expand Up @@ -189,21 +197,14 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
* @param blockData The new block data
*/
const onNewBlock = (blockData: IFeedBlockData) => {
const emitterObj = emitterRef.current;
if (emitterObj && blockData && isPlaying) {
const emitterBox = new Box3().setFromObject(emitterObj);

const emitterCenter = new THREE.Vector3();
emitterBox.getCenter(emitterCenter);

const { y, z } = generateYZPositions(bpsCounter.getBPS(), emitterCenter);
const minX = emitterBox.min.x - (emitterBox.max.x - emitterBox.min.x) * EMITTER_X_POSITION_MULTIPLIER;
const maxX = emitterBox.max.x + (emitterBox.max.x - emitterBox.min.x) * EMITTER_X_POSITION_MULTIPLIER;

const x = randomIntFromInterval(minX, maxX);
const targetPosition = { x, y, z };
if (blockData && isPlaying) {
const currentAnimationTime = getCurrentAnimationTime();
const bps = bpsCounter.getBPS();
const initPosition = getBlockInitPosition(currentAnimationTime);
const targetPosition = getBlockTargetPosition(initPosition, bps);

bpsCounter.addBlock();

if (!bpsCounter.getBPS()) {
bpsCounter.start();
}
Expand All @@ -224,11 +225,7 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
id: blockData.blockId,
color: PENDING_BLOCK_COLOR,
targetPosition,
initPosition: {
x: emitterCenter.x,
y: emitterCenter.y,
z: emitterCenter.z,
},
initPosition,
});
}
};
Expand All @@ -253,13 +250,17 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
onChangeFilter={() => {}}
selectNode={() => {}}
selectedFeedItem={selectedFeedItem}
setIsPlaying={setIsPlaying}
handlePauseButton={() => {
setIsPlaying(!isPlaying);
setInView(!isPlaying);
}}
isEdgeRenderingEnabled={isEdgeRenderingEnabled}
setEdgeRenderingEnabled={(checked) => setEdgeRenderingEnabled(checked)}
>
<Canvas
ref={canvasRef}
orthographic
frameloop={inView ? "always" : "never"}
camera={{
name: CanvasElement.MainCamera,
near: NEAR_PLANE,
Expand Down
31 changes: 31 additions & 0 deletions client/src/features/visualizer-threejs/blockPositions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { EMITTER_WIDTH, EMITTER_X_POSITION_MULTIPLIER } from "./constants";
import { getEmitterPosition, getGenerateDynamicYZPosition, getTangleDistances, randomIntFromInterval } from "./utils";

const generateYZPositions = getGenerateDynamicYZPosition();

interface IPos {
x: number;
y: number;
z: number;
}
export function getBlockTargetPosition(initPosition: IPos, bps: number): IPos {
const { y, z } = generateYZPositions(bps, initPosition);

const emitterMinX = initPosition.x - EMITTER_WIDTH / 2;
const emitterMaxX = initPosition.x + EMITTER_WIDTH / 2;

const minX = emitterMinX - (emitterMaxX - emitterMinX) * EMITTER_X_POSITION_MULTIPLIER;
const maxX = emitterMaxX + (emitterMaxX - emitterMinX) * EMITTER_X_POSITION_MULTIPLIER;

const x = randomIntFromInterval(minX, maxX);

return { x, y, z };
}

export function getBlockInitPosition(currentAnimationTime: number): IPos {
const { xTangleDistance } = getTangleDistances();
const { x: xEmitterPos, y, z } = getEmitterPosition(currentAnimationTime);
const x = xEmitterPos + xTangleDistance / 2;

return { x, y, z };
}
28 changes: 28 additions & 0 deletions client/src/features/visualizer-threejs/store/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@ interface ConfigState {
isPlaying: boolean;
setIsPlaying: (isPlaying: boolean) => void;

inView: boolean;
setInView: (inView: boolean) => void;

isEdgeRenderingEnabled: boolean;
setEdgeRenderingEnabled: (isEdgeRenderingEnabled: boolean) => void;

initialTime: number | null;
setInitialTime: (initialTime: number) => void;
}

export const useConfigStore = create<ConfigState>((set) => ({
Expand All @@ -34,6 +40,17 @@ export const useConfigStore = create<ConfigState>((set) => ({
}));
},

/**
* Is canvas in view
*/
inView: false,
setInView: (inView) => {
set((state) => ({
...state,
inView,
}));
},

/**
* Is edge rendering enabled
*/
Expand All @@ -44,4 +61,15 @@ export const useConfigStore = create<ConfigState>((set) => ({
isEdgeRenderingEnabled,
}));
},

/**
* Initial time
*/
initialTime: null,
setInitialTime: (initialTime) => {
set((state) => ({
...state,
initialTime,
}));
},
}));
Loading

0 comments on commit c93e586

Please sign in to comment.