Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: resumed visualizer not plotting correctly data while it was paused #1140

Merged
merged 8 commits into from
Feb 21, 2024
82 changes: 26 additions & 56 deletions client/src/features/visualizer-threejs/Emitter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,31 @@ 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, getEmitterPositions } 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>>;
readonly emitterRef: RefObject<THREE.Mesh>;
}

const { xTangleDistance, yTangleDistance } = getTangleDistances();

const Emitter: React.FC<EmitterProps> = ({ setRunListeners, emitterRef }: EmitterProps) => {
const getVisualizerTimeDiff = useVisualizerTimer();

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 animationTime = useRef<number>(0);
const currentAmplitude = useRef<number>(INITIAL_SINUSOIDAL_AMPLITUDE);

const previousRealTime = useRef<number>(0);
const previousPeakTime = useRef<number>(0);
const tangleWrapperRef = useRef<THREE.Mesh | null>(null);

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

return () => {
Expand All @@ -55,68 +47,46 @@ 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();
const { x, y } = getEmitterPositions(currentAnimationTime);

if (isPlaying) {
updateAnimationTime(realTimeDelta);
checkAndHandleNewPeak();

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

if (emitterRef.current) {
const newYPos = getSinusoidalPosition(animationTime.current, currentAmplitude.current);
emitterRef.current.position.y = newYPos;
if (tangleWrapperRef.current) {
tangleWrapperRef.current.position.x = x - xTangleDistance / 2;
}
}

if (tangleWrapperRef.current && camera) {
camera.position.x = tangleWrapperRef.current.position.x + xTangleDistance / 2;
}
});

// The Tangle rendering hook
useRenderTangle();

return (
<group ref={groupRef}>
<>
{/* TangleWrapper Mesh */}
<mesh name={CanvasElement.TangleWrapperMesh} position={[-(xTangleDistance / 2), 0, 0]}>
<mesh ref={tangleWrapperRef} 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} attach="material" />
</mesh>

{/* Emitter Mesh */}
<mesh ref={emitterRef} name={CanvasElement.EmitterMesh} position={[0, 0, 0]}>
<boxGeometry args={[EMITTER_WIDTH, EMITTER_HEIGHT, EMITTER_DEPTH]} />
<meshPhongMaterial transparent opacity={0} />
</mesh>
</group>
</>
);
};
export default Emitter;
35 changes: 13 additions & 22 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 @@ -32,6 +29,8 @@ import { BasicBlockBody, Utils, type IBlockMetadata, type BlockState, type SlotI
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: false,
Expand All @@ -44,7 +43,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 Down Expand Up @@ -80,6 +78,8 @@ 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
*/
Expand Down Expand Up @@ -166,6 +166,7 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
if (!runListeners) {
return;
}

setIsPlaying(true);

return () => {
Expand Down Expand Up @@ -195,21 +196,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) {
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 @@ -229,12 +223,9 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
addBlock({
id: blockData.blockId,
color: PENDING_BLOCK_COLOR,
blockAddedTimestamp: getCurrentAnimationTime(),
targetPosition,
initPosition: {
x: emitterCenter.x,
y: emitterCenter.y,
z: emitterCenter.z,
},
initPosition,
});
}
};
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 { getEmitterPositions, 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 } = getEmitterPositions(currentAnimationTime);
const x = xEmitterPos + xTangleDistance / 2;

return { x, y, z };
}
6 changes: 6 additions & 0 deletions client/src/features/visualizer-threejs/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,9 @@ export interface ICameraAngles {
maxPolarAngle: number;
maxAzimuthAngle: number;
}

export interface IThreeDimensionalPosition {
x: number;
y: number;
z: number;
}
29 changes: 29 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,16 @@ export const useConfigStore = create<ConfigState>((set) => ({
isEdgeRenderingEnabled,
}));
},

/**
* The initial time when the emitter was mounted.
* Used for all animations based on time.
*/
initialTime: null,
setInitialTime: (initialTime) => {
set((state) => ({
...state,
initialTime,
}));
},
}));
Loading
Loading