Skip to content

Commit

Permalink
feat(visualizer): randomize sinusoid period times (#1168)
Browse files Browse the repository at this point in the history
* feat(visualizer): randomize sinusoidal period times

* chore: remove comments

* refactor: rename HALF_PERIOD constants to PERIOD

---------

Co-authored-by: Mario <[email protected]>
  • Loading branch information
VmMad and msarcev authored Feb 22, 2024
1 parent 20efea8 commit ea34f5d
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 20 deletions.
21 changes: 18 additions & 3 deletions client/src/features/visualizer-threejs/Emitter.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/* eslint-disable react/no-unknown-property */
import { useFrame, useThree } from "@react-three/fiber";
import React, { RefObject, Dispatch, SetStateAction, useEffect, useRef } from "react";
import React, { RefObject, Dispatch, SetStateAction, useEffect, useRef, useLayoutEffect } from "react";
import * as THREE from "three";
import { useConfigStore, useTangleStore } from "./store";
import { useRenderTangle } from "./useRenderTangle";
import { getTangleDistances, getEmitterPositions } from "./utils";
import { getTangleDistances, getEmitterPositions, generateRandomPeriods } from "./utils";
import { CanvasElement } from "./enums";
import useVisualizerTimer from "~/helpers/nova/hooks/useVisualizerTimer";
import { EMITTER_DEPTH, EMITTER_HEIGHT, EMITTER_WIDTH } from "./constants";
Expand All @@ -28,8 +28,19 @@ const Emitter: React.FC<EmitterProps> = ({ setRunListeners, emitterRef }: Emitte
const setIsPlaying = useConfigStore((state) => state.setIsPlaying);
const setInitialTime = useConfigStore((state) => state.setInitialTime);

const sinusoidPeriodsSum = useConfigStore((state) => state.sinusoidPeriodsSum);
const setSinusoidPeriodsSum = useConfigStore((state) => state.setSinusoidPeriodsSum);
const randomizedSinusoidPeriods = useConfigStore((state) => state.sinusoidRandomPeriods);
const setRandomizedSinusoidPeriods = useConfigStore((state) => state.setSinusoidRandomPeriods);

const tangleWrapperRef = useRef<THREE.Mesh | null>(null);

useLayoutEffect(() => {
const { periods, sum: periodsSum } = generateRandomPeriods();
setRandomizedSinusoidPeriods(periods);
setSinusoidPeriodsSum(periodsSum);
}, []);

useEffect(() => {
setZoom(currentZoom);
}, [currentZoom]);
Expand All @@ -52,7 +63,11 @@ const Emitter: React.FC<EmitterProps> = ({ setRunListeners, emitterRef }: Emitte
*/
useFrame(() => {
const currentAnimationTime = getVisualizerTimeDiff();
const { x, y } = getEmitterPositions(currentAnimationTime);
const { x, y } = getEmitterPositions({
currentAnimationTime,
periods: randomizedSinusoidPeriods,
periodsSum: sinusoidPeriodsSum,
});

if (isPlaying) {
if (emitterRef.current) {
Expand Down
12 changes: 9 additions & 3 deletions client/src/features/visualizer-threejs/VisualizerInstance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
}) => {
const [networkConfig] = useNetworkConfig(network);
const themeMode = useGetThemeMode();
const getCurrentAnimationTime = useVisualizerTimer();

const [runListeners, setRunListeners] = React.useState<boolean>(false);

Expand Down Expand Up @@ -72,14 +73,15 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
const addToConfirmedBlocksSlot = useTangleStore((s) => s.addToConfirmedBlocksBySlot);
const removeConfirmedBlocksSlot = useTangleStore((s) => s.removeConfirmedBlocksSlot);

const sinusoidPeriodsSum = useConfigStore((s) => s.sinusoidPeriodsSum);
const sinusoidRandomPeriods = useConfigStore((s) => s.sinusoidRandomPeriods);

const selectedFeedItem: TSelectFeedItemNova = clickedInstanceId ? blockMetadata.get(clickedInstanceId) ?? null : null;
const resetConfigState = useTangleStore((s) => s.resetConfigState);

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 @@ -199,7 +201,11 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
if (blockData) {
const currentAnimationTime = getCurrentAnimationTime();
const bps = bpsCounter.getBPS();
const initPosition = getBlockInitPosition(currentAnimationTime);
const initPosition = getBlockInitPosition({
currentAnimationTime,
periods: sinusoidRandomPeriods,
periodsSum: sinusoidPeriodsSum,
});
const targetPosition = getBlockTargetPosition(initPosition, bps);

bpsCounter.addBlock();
Expand Down
5 changes: 3 additions & 2 deletions client/src/features/visualizer-threejs/blockPositions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EMITTER_WIDTH, EMITTER_X_POSITION_MULTIPLIER } from "./constants";
import { ISinusoidalPositionParams } from "./interfaces";
import { getEmitterPositions, getGenerateDynamicYZPosition, getTangleDistances, randomIntFromInterval } from "./utils";

const generateYZPositions = getGenerateDynamicYZPosition();
Expand All @@ -22,9 +23,9 @@ export function getBlockTargetPosition(initPosition: IPos, bps: number): IPos {
return { x, y, z };
}

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

return { x, y, z };
Expand Down
10 changes: 7 additions & 3 deletions client/src/features/visualizer-threejs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ export const PENDING_BLOCK_COLOR = new Color("#A6C3FC");
export const ACCEPTED_BLOCK_COLOR = new Color("#0101AB");
export const CONFIRMED_BLOCK_COLOR = new Color("#0000DB");
export const FINALIZED_BLOCK_COLOR = new Color("#0101FF");
// TODO Remove accepted state once is added to the SDK (missing)
export const BLOCK_STATE_TO_COLOR = new Map<BlockState | "accepted", Color>([

export const BLOCK_STATE_TO_COLOR = new Map<BlockState, Color>([
["pending", PENDING_BLOCK_COLOR],
["accepted", ACCEPTED_BLOCK_COLOR],
["confirmed", CONFIRMED_BLOCK_COLOR],
Expand Down Expand Up @@ -72,7 +72,6 @@ export const EMITTER_HEIGHT = 250;
export const EMITTER_DEPTH = 250;

// conic emitter

export const MIN_TANGLE_RADIUS = 100;
export const MAX_TANGLE_RADIUS = 300;

Expand All @@ -90,3 +89,8 @@ export const MAX_SINUSOIDAL_AMPLITUDE = 200;
export const SINUSOIDAL_AMPLITUDE_ACCUMULATOR = 30;
export const INITIAL_SINUSOIDAL_AMPLITUDE = 80;
export const HALF_WAVE_PERIOD_SECONDS = 5;

/* Values for randomizing the tangle */
export const NUMBER_OF_RANDOM_PERIODS = 100;
export const MIN_SINUSOID_PERIOD = 5;
export const MAX_SINUSOID_PERIOD = 8;
9 changes: 9 additions & 0 deletions client/src/features/visualizer-threejs/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,12 @@ export interface IThreeDimensionalPosition {
y: number;
z: number;
}

export interface ITimeBasedPositionParams {
currentAnimationTime: number;
}

export interface ISinusoidalPositionParams extends ITimeBasedPositionParams {
periods: number[];
periodsSum: number;
}
23 changes: 23 additions & 0 deletions client/src/features/visualizer-threejs/store/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ interface ConfigState {

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

sinusoidPeriodsSum: number;
setSinusoidPeriodsSum: (totalPeriodsSum: number) => void;
sinusoidRandomPeriods: number[];
setSinusoidRandomPeriods: (randomizedPeriods: number[]) => void;
}

export const useConfigStore = create<ConfigState>((set) => ({
Expand Down Expand Up @@ -73,4 +78,22 @@ export const useConfigStore = create<ConfigState>((set) => ({
initialTime,
}));
},

/**
* Randomized periods for the tangle.
*/
sinusoidPeriodsSum: 0,
setSinusoidPeriodsSum: (totalPeriodsSum) => {
set((state) => ({
...state,
sinusoidPeriodsSum: totalPeriodsSum,
}));
},
sinusoidRandomPeriods: [],
setSinusoidRandomPeriods: (randomizedPeriods) => {
set((state) => ({
...state,
sinusoidRandomPeriods: randomizedPeriods,
}));
},
}));
55 changes: 46 additions & 9 deletions client/src/features/visualizer-threejs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
MIN_BLOCK_NEAR_RADIUS,
MAX_PREV_POINTS,
MAX_POINT_RETRIES,
HALF_WAVE_PERIOD_SECONDS,
MAX_BLOCK_INSTANCES,
EMITTER_SPEED_MULTIPLIER,
MAX_SINUSOIDAL_AMPLITUDE,
Expand All @@ -18,8 +17,11 @@ import {
CAMERA_Y_OFFSET,
SINUSOIDAL_AMPLITUDE_ACCUMULATOR,
INITIAL_SINUSOIDAL_AMPLITUDE,
NUMBER_OF_RANDOM_PERIODS,
MIN_SINUSOID_PERIOD,
MAX_SINUSOID_PERIOD,
} from "./constants";
import { ICameraAngles, IThreeDimensionalPosition } from "./interfaces";
import type { ICameraAngles, ISinusoidalPositionParams, IThreeDimensionalPosition } from "./interfaces";

/**
* Generates a random number within a specified range.
Expand Down Expand Up @@ -231,16 +233,22 @@ export function getCameraAngles(): ICameraAngles {
}

/**
* Calculates the sinusoidal position for the emitter based on the current animation time.
* Calculates the sinusoidal position for the emitter based on the current animation time,
* considering random periods.
* @returns the sinusoidal position
*/
export function calculateSinusoidalAmplitude(currentAnimationTime: number): number {
const wavePeriod = HALF_WAVE_PERIOD_SECONDS * 2;
const currentWaveCount = Math.floor(currentAnimationTime / wavePeriod);
export function calculateSinusoidalAmplitude({ currentAnimationTime, periods, periodsSum }: ISinusoidalPositionParams): number {
const elapsedTime = currentAnimationTime % periodsSum;
const { period, accumulatedTime } = getCurrentPeriodValues(currentAnimationTime, periods, periodsSum);

const startTimeOfCurrentPeriod = accumulatedTime - period;
const timeInCurrentPeriod = elapsedTime - startTimeOfCurrentPeriod;

const currentWaveCount = Math.floor(elapsedTime / period);
const accumulatedAmplitude = currentWaveCount * SINUSOIDAL_AMPLITUDE_ACCUMULATOR;
const currentAmplitude = Math.min(INITIAL_SINUSOIDAL_AMPLITUDE + accumulatedAmplitude, MAX_SINUSOIDAL_AMPLITUDE);

const yPosition = currentAmplitude * Math.sin((2 * Math.PI * currentAnimationTime) / wavePeriod);
const yPosition = currentAmplitude * Math.sin((2 * Math.PI * timeInCurrentPeriod) / period);

return yPosition;
}
Expand All @@ -257,9 +265,9 @@ export function calculateEmitterPositionX(currentAnimationTime: number): number
* Calculates the emitter position based on the current animation time.
* @returns the emitter X,Y,Z positions
*/
export function getEmitterPositions(currentAnimationTime: number): IThreeDimensionalPosition {
export function getEmitterPositions({ currentAnimationTime, periods, periodsSum }: ISinusoidalPositionParams): IThreeDimensionalPosition {
const x = calculateEmitterPositionX(currentAnimationTime);
const y = calculateSinusoidalAmplitude(currentAnimationTime);
const y = calculateSinusoidalAmplitude({ currentAnimationTime, periods, periodsSum });
return { x, y, z: 0 };
}

Expand All @@ -271,3 +279,32 @@ export function getEmitterPositions(currentAnimationTime: number): IThreeDimensi
export function positionToVector(position: IThreeDimensionalPosition) {
return new Vector3(position.x, position.y, position.z);
}

export function generateRandomPeriods(): { periods: number[]; sum: number } {
let sum = 0;
const periods = Array.from({ length: NUMBER_OF_RANDOM_PERIODS }, () => {
const period = Number(randomNumberFromInterval(MIN_SINUSOID_PERIOD, MAX_SINUSOID_PERIOD).toFixed(4));
sum += period;
return period;
});
return { periods, sum };
}

type PeriodResult = {
period: number;
accumulatedTime: number;
};

function getCurrentPeriodValues(animationTime: number, periods: number[], totalSum: number): PeriodResult {
const effectiveTime = animationTime % totalSum;

let accumulatedTime = 0;
for (let i = 0; i < periods.length; i++) {
accumulatedTime += periods[i];
if (effectiveTime < accumulatedTime) {
return { period: periods[i], accumulatedTime };
}
}

return { period: periods[0], accumulatedTime: 0 };
}

0 comments on commit ea34f5d

Please sign in to comment.