From 46213025d2124f6ac73cf1f95e311ec0bbbe1f2d Mon Sep 17 00:00:00 2001 From: JCNoguera Date: Wed, 21 Feb 2024 22:53:13 +0100 Subject: [PATCH 01/22] feat(visualizer): randomize sinusoidal period times --- .../features/visualizer-threejs/Emitter.tsx | 21 ++++++- .../visualizer-threejs/VisualizerInstance.tsx | 12 +++- .../visualizer-threejs/blockPositions.ts | 5 +- .../features/visualizer-threejs/constants.ts | 7 +++ .../features/visualizer-threejs/interfaces.ts | 9 +++ .../visualizer-threejs/store/config.ts | 23 ++++++++ .../src/features/visualizer-threejs/utils.ts | 56 ++++++++++++++++--- 7 files changed, 116 insertions(+), 17 deletions(-) diff --git a/client/src/features/visualizer-threejs/Emitter.tsx b/client/src/features/visualizer-threejs/Emitter.tsx index 767dfee56..9c251e245 100644 --- a/client/src/features/visualizer-threejs/Emitter.tsx +++ b/client/src/features/visualizer-threejs/Emitter.tsx @@ -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"; @@ -28,8 +28,19 @@ const Emitter: React.FC = ({ 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(null); + useLayoutEffect(() => { + const { periods, sum: periodsSum } = generateRandomPeriods(); + setRandomizedSinusoidPeriods(periods); + setSinusoidPeriodsSum(periodsSum); + }, []); + useEffect(() => { setZoom(currentZoom); }, [currentZoom]); @@ -52,7 +63,11 @@ const Emitter: React.FC = ({ 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) { diff --git a/client/src/features/visualizer-threejs/VisualizerInstance.tsx b/client/src/features/visualizer-threejs/VisualizerInstance.tsx index 751d8e187..fcf5fc4c0 100644 --- a/client/src/features/visualizer-threejs/VisualizerInstance.tsx +++ b/client/src/features/visualizer-threejs/VisualizerInstance.tsx @@ -44,6 +44,7 @@ const VisualizerInstance: React.FC> = }) => { const [networkConfig] = useNetworkConfig(network); const themeMode = useGetThemeMode(); + const getCurrentAnimationTime = useVisualizerTimer(); const [runListeners, setRunListeners] = React.useState(false); @@ -72,14 +73,15 @@ const VisualizerInstance: React.FC> = 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(null); const [feedService, setFeedService] = React.useState(ServiceFactory.get(`feed-${network}`)); - const getCurrentAnimationTime = useVisualizerTimer(); - /** * Pause on tab or window change */ @@ -199,7 +201,11 @@ const VisualizerInstance: React.FC> = 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(); diff --git a/client/src/features/visualizer-threejs/blockPositions.ts b/client/src/features/visualizer-threejs/blockPositions.ts index c38e0cab2..82caa101c 100644 --- a/client/src/features/visualizer-threejs/blockPositions.ts +++ b/client/src/features/visualizer-threejs/blockPositions.ts @@ -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(); @@ -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 }; diff --git a/client/src/features/visualizer-threejs/constants.ts b/client/src/features/visualizer-threejs/constants.ts index 37b399ec5..738b2eb97 100644 --- a/client/src/features/visualizer-threejs/constants.ts +++ b/client/src/features/visualizer-threejs/constants.ts @@ -90,3 +90,10 @@ 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 */ + +// PERIOD +export const NUMBER_OF_RANDOM_PERIODS = 100; +export const MIN_SINUSOID_HALF_PERIOD = 1; +export const MAX_SINUSOID_HALF_PERIOD = 4; diff --git a/client/src/features/visualizer-threejs/interfaces.ts b/client/src/features/visualizer-threejs/interfaces.ts index 0efef94cb..55df2965f 100644 --- a/client/src/features/visualizer-threejs/interfaces.ts +++ b/client/src/features/visualizer-threejs/interfaces.ts @@ -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; +} diff --git a/client/src/features/visualizer-threejs/store/config.ts b/client/src/features/visualizer-threejs/store/config.ts index 02956518b..b3152e8cd 100644 --- a/client/src/features/visualizer-threejs/store/config.ts +++ b/client/src/features/visualizer-threejs/store/config.ts @@ -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((set) => ({ @@ -73,4 +78,22 @@ export const useConfigStore = create((set) => ({ initialTime, })); }, + + /** + * Randomized periods for the tangle. + */ + sinusoidPeriodsSum: 0, + setSinusoidPeriodsSum: (totalPeriodsSum) => { + set((state) => ({ + ...state, + sinusoidPeriodsSum: totalPeriodsSum, + })); + }, + sinusoidRandomPeriods: [], + setSinusoidRandomPeriods: (randomizedPeriods) => { + set((state) => ({ + ...state, + sinusoidRandomPeriods: randomizedPeriods, + })); + }, })); diff --git a/client/src/features/visualizer-threejs/utils.ts b/client/src/features/visualizer-threejs/utils.ts index ddf39952b..50e4e9b33 100644 --- a/client/src/features/visualizer-threejs/utils.ts +++ b/client/src/features/visualizer-threejs/utils.ts @@ -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, @@ -18,8 +17,11 @@ import { CAMERA_Y_OFFSET, SINUSOIDAL_AMPLITUDE_ACCUMULATOR, INITIAL_SINUSOIDAL_AMPLITUDE, + NUMBER_OF_RANDOM_PERIODS, + MIN_SINUSOID_HALF_PERIOD, + MAX_SINUSOID_HALF_PERIOD, } from "./constants"; -import { ICameraAngles, IThreeDimensionalPosition } from "./interfaces"; +import type { ICameraAngles, ISinusoidalPositionParams, IThreeDimensionalPosition } from "./interfaces"; /** * Generates a random number within a specified range. @@ -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; } @@ -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 }; } @@ -271,3 +279,33 @@ 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_HALF_PERIOD, MAX_SINUSOID_HALF_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 }; +} From d474eab78c690fb097260cc350847e007cb50fd1 Mon Sep 17 00:00:00 2001 From: JCNoguera Date: Wed, 21 Feb 2024 22:58:33 +0100 Subject: [PATCH 02/22] chore: remove comments --- client/src/features/visualizer-threejs/constants.ts | 7 ++----- client/src/features/visualizer-threejs/utils.ts | 1 - 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/client/src/features/visualizer-threejs/constants.ts b/client/src/features/visualizer-threejs/constants.ts index 738b2eb97..66f6cd27f 100644 --- a/client/src/features/visualizer-threejs/constants.ts +++ b/client/src/features/visualizer-threejs/constants.ts @@ -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([ + +export const BLOCK_STATE_TO_COLOR = new Map([ ["pending", PENDING_BLOCK_COLOR], ["accepted", ACCEPTED_BLOCK_COLOR], ["confirmed", CONFIRMED_BLOCK_COLOR], @@ -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; @@ -92,8 +91,6 @@ export const INITIAL_SINUSOIDAL_AMPLITUDE = 80; export const HALF_WAVE_PERIOD_SECONDS = 5; /* Values for randomizing the tangle */ - -// PERIOD export const NUMBER_OF_RANDOM_PERIODS = 100; export const MIN_SINUSOID_HALF_PERIOD = 1; export const MAX_SINUSOID_HALF_PERIOD = 4; diff --git a/client/src/features/visualizer-threejs/utils.ts b/client/src/features/visualizer-threejs/utils.ts index 50e4e9b33..53d3417c0 100644 --- a/client/src/features/visualizer-threejs/utils.ts +++ b/client/src/features/visualizer-threejs/utils.ts @@ -306,6 +306,5 @@ function getCurrentPeriodValues(animationTime: number, periods: number[], totalS } } - // return { period: periods[0], accumulatedTime: 0 }; } From 02bb2c35cb28c081576dcfc234bcd2bbf33f2776 Mon Sep 17 00:00:00 2001 From: JCNoguera Date: Thu, 22 Feb 2024 00:34:03 +0100 Subject: [PATCH 03/22] feat(visualizer): randomize sinusoid amplitudes --- .../features/visualizer-threejs/Emitter.tsx | 18 +++-- .../visualizer-threejs/VisualizerInstance.tsx | 2 + .../visualizer-threejs/blockPositions.ts | 4 +- .../features/visualizer-threejs/constants.ts | 13 ++-- .../features/visualizer-threejs/interfaces.ts | 1 + .../visualizer-threejs/store/config.ts | 14 ++++ .../src/features/visualizer-threejs/utils.ts | 67 ++++++++++++++----- 7 files changed, 89 insertions(+), 30 deletions(-) diff --git a/client/src/features/visualizer-threejs/Emitter.tsx b/client/src/features/visualizer-threejs/Emitter.tsx index 9c251e245..fba26e0c7 100644 --- a/client/src/features/visualizer-threejs/Emitter.tsx +++ b/client/src/features/visualizer-threejs/Emitter.tsx @@ -4,7 +4,7 @@ import React, { RefObject, Dispatch, SetStateAction, useEffect, useRef, useLayou import * as THREE from "three"; import { useConfigStore, useTangleStore } from "./store"; import { useRenderTangle } from "./useRenderTangle"; -import { getTangleDistances, getEmitterPositions, generateRandomPeriods } from "./utils"; +import { getTangleDistances, getEmitterPositions, generateRandomPeriods, generateRandomAmplitudes } from "./utils"; import { CanvasElement } from "./enums"; import useVisualizerTimer from "~/helpers/nova/hooks/useVisualizerTimer"; import { EMITTER_DEPTH, EMITTER_HEIGHT, EMITTER_WIDTH } from "./constants"; @@ -30,15 +30,20 @@ const Emitter: React.FC = ({ setRunListeners, emitterRef }: Emitte 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 sinusoidRandomPeriods = useConfigStore((state) => state.sinusoidRandomPeriods); + const setSinusoidRandomPeriods = useConfigStore((state) => state.setSinusoidRandomPeriods); + + const randomSinusoidAmplitudes = useConfigStore((state) => state.randomSinusoidAmplitudes); + const setRandomSinusoidAmplitudes = useConfigStore((state) => state.setRandomSinusoidAmplitudes); const tangleWrapperRef = useRef(null); useLayoutEffect(() => { const { periods, sum: periodsSum } = generateRandomPeriods(); - setRandomizedSinusoidPeriods(periods); + const amplitudes = generateRandomAmplitudes(); + setSinusoidRandomPeriods(periods); setSinusoidPeriodsSum(periodsSum); + setRandomSinusoidAmplitudes(amplitudes); }, []); useEffect(() => { @@ -65,8 +70,9 @@ const Emitter: React.FC = ({ setRunListeners, emitterRef }: Emitte const currentAnimationTime = getVisualizerTimeDiff(); const { x, y } = getEmitterPositions({ currentAnimationTime, - periods: randomizedSinusoidPeriods, + periods: sinusoidRandomPeriods, periodsSum: sinusoidPeriodsSum, + sinusoidAmplitudes: randomSinusoidAmplitudes, }); if (isPlaying) { @@ -99,7 +105,7 @@ const Emitter: React.FC = ({ setRunListeners, emitterRef }: Emitte {/* Emitter Mesh */} - + ); diff --git a/client/src/features/visualizer-threejs/VisualizerInstance.tsx b/client/src/features/visualizer-threejs/VisualizerInstance.tsx index fcf5fc4c0..26f62570f 100644 --- a/client/src/features/visualizer-threejs/VisualizerInstance.tsx +++ b/client/src/features/visualizer-threejs/VisualizerInstance.tsx @@ -75,6 +75,7 @@ const VisualizerInstance: React.FC> = const sinusoidPeriodsSum = useConfigStore((s) => s.sinusoidPeriodsSum); const sinusoidRandomPeriods = useConfigStore((s) => s.sinusoidRandomPeriods); + const sinusoidRandomAmplitudes = useConfigStore((s) => s.randomSinusoidAmplitudes); const selectedFeedItem: TSelectFeedItemNova = clickedInstanceId ? blockMetadata.get(clickedInstanceId) ?? null : null; const resetConfigState = useTangleStore((s) => s.resetConfigState); @@ -205,6 +206,7 @@ const VisualizerInstance: React.FC> = currentAnimationTime, periods: sinusoidRandomPeriods, periodsSum: sinusoidPeriodsSum, + sinusoidAmplitudes: sinusoidRandomAmplitudes, }); const targetPosition = getBlockTargetPosition(initPosition, bps); diff --git a/client/src/features/visualizer-threejs/blockPositions.ts b/client/src/features/visualizer-threejs/blockPositions.ts index 82caa101c..a01f521a6 100644 --- a/client/src/features/visualizer-threejs/blockPositions.ts +++ b/client/src/features/visualizer-threejs/blockPositions.ts @@ -23,9 +23,9 @@ export function getBlockTargetPosition(initPosition: IPos, bps: number): IPos { return { x, y, z }; } -export function getBlockInitPosition({ currentAnimationTime, periods, periodsSum }: ISinusoidalPositionParams): IPos { +export function getBlockInitPosition({ currentAnimationTime, periods, periodsSum, sinusoidAmplitudes }: ISinusoidalPositionParams): IPos { const { xTangleDistance } = getTangleDistances(); - const { x: xEmitterPos, y, z } = getEmitterPositions({ currentAnimationTime, periods, periodsSum }); + const { x: xEmitterPos, y, z } = getEmitterPositions({ currentAnimationTime, periods, periodsSum, sinusoidAmplitudes }); const x = xEmitterPos + xTangleDistance / 2; return { x, y, z }; diff --git a/client/src/features/visualizer-threejs/constants.ts b/client/src/features/visualizer-threejs/constants.ts index 66f6cd27f..77056f154 100644 --- a/client/src/features/visualizer-threejs/constants.ts +++ b/client/src/features/visualizer-threejs/constants.ts @@ -85,12 +85,11 @@ export const MAX_PREV_POINTS = 20; export const EMITTER_X_POSITION_MULTIPLIER = 3; -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_HALF_PERIOD = 1; -export const MAX_SINUSOID_HALF_PERIOD = 4; +export const MIN_SINUSOID_HALF_PERIOD = 5; +export const MAX_SINUSOID_HALF_PERIOD = 8; + +export const NUMBER_OF_RANDOM_AMPLITUDES = 100; +export const MIN_SINUSOID_AMPLITUDE = 100; +export const MAX_SINUSOID_AMPLITUDE = 200; diff --git a/client/src/features/visualizer-threejs/interfaces.ts b/client/src/features/visualizer-threejs/interfaces.ts index 55df2965f..81bae2dd5 100644 --- a/client/src/features/visualizer-threejs/interfaces.ts +++ b/client/src/features/visualizer-threejs/interfaces.ts @@ -18,4 +18,5 @@ export interface ITimeBasedPositionParams { export interface ISinusoidalPositionParams extends ITimeBasedPositionParams { periods: number[]; periodsSum: number; + sinusoidAmplitudes: number[]; } diff --git a/client/src/features/visualizer-threejs/store/config.ts b/client/src/features/visualizer-threejs/store/config.ts index b3152e8cd..d505f5177 100644 --- a/client/src/features/visualizer-threejs/store/config.ts +++ b/client/src/features/visualizer-threejs/store/config.ts @@ -20,6 +20,9 @@ interface ConfigState { setSinusoidPeriodsSum: (totalPeriodsSum: number) => void; sinusoidRandomPeriods: number[]; setSinusoidRandomPeriods: (randomizedPeriods: number[]) => void; + + randomSinusoidAmplitudes: number[]; + setRandomSinusoidAmplitudes: (randomizedAmplitudes: number[]) => void; } export const useConfigStore = create((set) => ({ @@ -96,4 +99,15 @@ export const useConfigStore = create((set) => ({ sinusoidRandomPeriods: randomizedPeriods, })); }, + + /** + * Randomized amplitudes for the tangle. + */ + randomSinusoidAmplitudes: [], + setRandomSinusoidAmplitudes: (randomizedAmplitudes) => { + set((state) => ({ + ...state, + randomSinusoidAmplitudes: randomizedAmplitudes, + })); + }, })); diff --git a/client/src/features/visualizer-threejs/utils.ts b/client/src/features/visualizer-threejs/utils.ts index 53d3417c0..2a27b1758 100644 --- a/client/src/features/visualizer-threejs/utils.ts +++ b/client/src/features/visualizer-threejs/utils.ts @@ -10,16 +10,16 @@ import { MAX_POINT_RETRIES, MAX_BLOCK_INSTANCES, EMITTER_SPEED_MULTIPLIER, - MAX_SINUSOIDAL_AMPLITUDE, CAMERA_X_AXIS_MOVEMENT, CAMERA_Y_AXIS_MOVEMENT, CAMERA_X_OFFSET, CAMERA_Y_OFFSET, - SINUSOIDAL_AMPLITUDE_ACCUMULATOR, - INITIAL_SINUSOIDAL_AMPLITUDE, NUMBER_OF_RANDOM_PERIODS, MIN_SINUSOID_HALF_PERIOD, MAX_SINUSOID_HALF_PERIOD, + NUMBER_OF_RANDOM_AMPLITUDES, + MIN_SINUSOID_AMPLITUDE, + MAX_SINUSOID_AMPLITUDE, } from "./constants"; import type { ICameraAngles, ISinusoidalPositionParams, IThreeDimensionalPosition } from "./interfaces"; @@ -193,7 +193,7 @@ export function getTangleDistances(): { const maxXDistance = MAX_BLOCK_DISTANCE; /* Max Y Distance will be multiplied by 2 to position blocks in the negative and positive Y axis */ - const maxYDistance = MAX_TANGLE_RADIUS * 2 + MAX_SINUSOIDAL_AMPLITUDE * 2; + const maxYDistance = MAX_TANGLE_RADIUS * 2 + MAX_SINUSOID_AMPLITUDE * 2; /* TODO: add sinusoidal distances */ @@ -237,16 +237,18 @@ export function getCameraAngles(): ICameraAngles { * considering random periods. * @returns the sinusoidal position */ -export function calculateSinusoidalAmplitude({ currentAnimationTime, periods, periodsSum }: ISinusoidalPositionParams): number { +export function calculateSinusoidalAmplitude({ + currentAnimationTime, + periods, + periodsSum, + sinusoidAmplitudes, +}: ISinusoidalPositionParams): number { const elapsedTime = currentAnimationTime % periodsSum; - const { period, accumulatedTime } = getCurrentPeriodValues(currentAnimationTime, periods, periodsSum); + const { index, 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 currentAmplitude = sinusoidAmplitudes[index]; const yPosition = currentAmplitude * Math.sin((2 * Math.PI * timeInCurrentPeriod) / period); @@ -265,9 +267,14 @@ 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, periods, periodsSum }: ISinusoidalPositionParams): IThreeDimensionalPosition { +export function getEmitterPositions({ + currentAnimationTime, + periods, + periodsSum, + sinusoidAmplitudes, +}: ISinusoidalPositionParams): IThreeDimensionalPosition { const x = calculateEmitterPositionX(currentAnimationTime); - const y = calculateSinusoidalAmplitude({ currentAnimationTime, periods, periodsSum }); + const y = calculateSinusoidalAmplitude({ currentAnimationTime, periods, periodsSum, sinusoidAmplitudes }); return { x, y, z: 0 }; } @@ -293,18 +300,48 @@ export function generateRandomPeriods(): { periods: number[]; sum: number } { type PeriodResult = { period: number; accumulatedTime: number; + index: 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]; + const period = periods[i]; + accumulatedTime += period; if (effectiveTime < accumulatedTime) { - return { period: periods[i], accumulatedTime }; + return { index: i, period, accumulatedTime }; } } - return { period: periods[0], accumulatedTime: 0 }; + return { index: 0, period: periods[0], accumulatedTime: 0 }; +} + +function getNextAmplitudeWithVariation(currentAmplitude: number = 0): number { + const variation = (2 * MIN_SINUSOID_AMPLITUDE) / 3; + const randomAmplitudeVariation = randomNumberFromInterval(-variation, variation); + + let newAmplitude = currentAmplitude + randomAmplitudeVariation; + + if (newAmplitude > MAX_SINUSOID_AMPLITUDE) { + newAmplitude = currentAmplitude - Math.abs(randomAmplitudeVariation); + } else if (newAmplitude < MIN_SINUSOID_AMPLITUDE) { + newAmplitude = currentAmplitude + Math.abs(randomAmplitudeVariation); + } + + newAmplitude = Math.max(MIN_SINUSOID_AMPLITUDE, Math.min(newAmplitude, MAX_SINUSOID_AMPLITUDE)); + + return newAmplitude; +} + +export function generateRandomAmplitudes(): number[] { + const amplitudes: number[] = []; + let currentAmplitude: number = 0; + for (let i = 0; i < NUMBER_OF_RANDOM_AMPLITUDES; i++) { + currentAmplitude = getNextAmplitudeWithVariation(currentAmplitude); + amplitudes.push(currentAmplitude); + } + return amplitudes; } From bf80194c07446639bb1ec364bf7268aad0b702e2 Mon Sep 17 00:00:00 2001 From: JCNoguera Date: Thu, 22 Feb 2024 10:02:36 +0100 Subject: [PATCH 04/22] refactor: rename HALF_PERIOD constants to PERIOD --- client/src/features/visualizer-threejs/constants.ts | 4 ++-- client/src/features/visualizer-threejs/utils.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/features/visualizer-threejs/constants.ts b/client/src/features/visualizer-threejs/constants.ts index 66f6cd27f..94f527f66 100644 --- a/client/src/features/visualizer-threejs/constants.ts +++ b/client/src/features/visualizer-threejs/constants.ts @@ -92,5 +92,5 @@ export const HALF_WAVE_PERIOD_SECONDS = 5; /* Values for randomizing the tangle */ export const NUMBER_OF_RANDOM_PERIODS = 100; -export const MIN_SINUSOID_HALF_PERIOD = 1; -export const MAX_SINUSOID_HALF_PERIOD = 4; +export const MIN_SINUSOID_PERIOD = 5; +export const MAX_SINUSOID_PERIOD = 8; diff --git a/client/src/features/visualizer-threejs/utils.ts b/client/src/features/visualizer-threejs/utils.ts index 53d3417c0..f38a82354 100644 --- a/client/src/features/visualizer-threejs/utils.ts +++ b/client/src/features/visualizer-threejs/utils.ts @@ -18,8 +18,8 @@ import { SINUSOIDAL_AMPLITUDE_ACCUMULATOR, INITIAL_SINUSOIDAL_AMPLITUDE, NUMBER_OF_RANDOM_PERIODS, - MIN_SINUSOID_HALF_PERIOD, - MAX_SINUSOID_HALF_PERIOD, + MIN_SINUSOID_PERIOD, + MAX_SINUSOID_PERIOD, } from "./constants"; import type { ICameraAngles, ISinusoidalPositionParams, IThreeDimensionalPosition } from "./interfaces"; @@ -283,7 +283,7 @@ export function positionToVector(position: IThreeDimensionalPosition) { 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_HALF_PERIOD, MAX_SINUSOID_HALF_PERIOD).toFixed(4)); + const period = Number(randomNumberFromInterval(MIN_SINUSOID_PERIOD, MAX_SINUSOID_PERIOD).toFixed(4)); sum += period; return period; }); From df6d034f174d0e86b9434f191aff05c486ca2ca8 Mon Sep 17 00:00:00 2001 From: JCNoguera Date: Thu, 22 Feb 2024 12:07:26 +0100 Subject: [PATCH 05/22] feat(visualizer): add tangle tilting factor --- .../features/visualizer-threejs/Emitter.tsx | 6 ++- .../visualizer-threejs/VisualizerInstance.tsx | 5 ++- .../visualizer-threejs/blockPositions.ts | 4 +- .../features/visualizer-threejs/constants.ts | 5 +++ .../features/visualizer-threejs/interfaces.ts | 4 ++ .../visualizer-threejs/store/config.ts | 14 +++++++ .../src/features/visualizer-threejs/utils.ts | 41 +++++++++++++++++-- 7 files changed, 71 insertions(+), 8 deletions(-) diff --git a/client/src/features/visualizer-threejs/Emitter.tsx b/client/src/features/visualizer-threejs/Emitter.tsx index fba26e0c7..1a2b7c08c 100644 --- a/client/src/features/visualizer-threejs/Emitter.tsx +++ b/client/src/features/visualizer-threejs/Emitter.tsx @@ -4,7 +4,7 @@ import React, { RefObject, Dispatch, SetStateAction, useEffect, useRef, useLayou import * as THREE from "three"; import { useConfigStore, useTangleStore } from "./store"; import { useRenderTangle } from "./useRenderTangle"; -import { getTangleDistances, getEmitterPositions, generateRandomPeriods, generateRandomAmplitudes } from "./utils"; +import { getTangleDistances, getEmitterPositions, generateRandomPeriods, generateRandomAmplitudes, generateRandomTiltings } from "./utils"; import { CanvasElement } from "./enums"; import useVisualizerTimer from "~/helpers/nova/hooks/useVisualizerTimer"; import { EMITTER_DEPTH, EMITTER_HEIGHT, EMITTER_WIDTH } from "./constants"; @@ -36,14 +36,18 @@ const Emitter: React.FC = ({ setRunListeners, emitterRef }: Emitte const randomSinusoidAmplitudes = useConfigStore((state) => state.randomSinusoidAmplitudes); const setRandomSinusoidAmplitudes = useConfigStore((state) => state.setRandomSinusoidAmplitudes); + const setRandomTilts = useConfigStore((state) => state.setRandomTilts); + const tangleWrapperRef = useRef(null); useLayoutEffect(() => { const { periods, sum: periodsSum } = generateRandomPeriods(); const amplitudes = generateRandomAmplitudes(); + const tiltings = generateRandomTiltings(); setSinusoidRandomPeriods(periods); setSinusoidPeriodsSum(periodsSum); setRandomSinusoidAmplitudes(amplitudes); + setRandomTilts(tiltings); }, []); useEffect(() => { diff --git a/client/src/features/visualizer-threejs/VisualizerInstance.tsx b/client/src/features/visualizer-threejs/VisualizerInstance.tsx index 26f62570f..f7a26d2e3 100644 --- a/client/src/features/visualizer-threejs/VisualizerInstance.tsx +++ b/client/src/features/visualizer-threejs/VisualizerInstance.tsx @@ -31,6 +31,7 @@ import CameraControls from "./CameraControls"; import "./Visualizer.scss"; import useVisualizerTimer from "~/helpers/nova/hooks/useVisualizerTimer"; import { getBlockInitPosition, getBlockTargetPosition } from "./blockPositions"; +import { getCurrentTiltValue } from "./utils"; const features = { statsEnabled: false, @@ -76,6 +77,7 @@ const VisualizerInstance: React.FC> = const sinusoidPeriodsSum = useConfigStore((s) => s.sinusoidPeriodsSum); const sinusoidRandomPeriods = useConfigStore((s) => s.sinusoidRandomPeriods); const sinusoidRandomAmplitudes = useConfigStore((s) => s.randomSinusoidAmplitudes); + const randomTilts = useConfigStore((state) => state.randomTilts); const selectedFeedItem: TSelectFeedItemNova = clickedInstanceId ? blockMetadata.get(clickedInstanceId) ?? null : null; const resetConfigState = useTangleStore((s) => s.resetConfigState); @@ -208,7 +210,8 @@ const VisualizerInstance: React.FC> = periodsSum: sinusoidPeriodsSum, sinusoidAmplitudes: sinusoidRandomAmplitudes, }); - const targetPosition = getBlockTargetPosition(initPosition, bps); + const blockTiltFactor = getCurrentTiltValue(currentAnimationTime, randomTilts); + const targetPosition = getBlockTargetPosition(initPosition, bps, blockTiltFactor); bpsCounter.addBlock(); diff --git a/client/src/features/visualizer-threejs/blockPositions.ts b/client/src/features/visualizer-threejs/blockPositions.ts index a01f521a6..8d556972b 100644 --- a/client/src/features/visualizer-threejs/blockPositions.ts +++ b/client/src/features/visualizer-threejs/blockPositions.ts @@ -9,8 +9,8 @@ interface IPos { y: number; z: number; } -export function getBlockTargetPosition(initPosition: IPos, bps: number): IPos { - const { y, z } = generateYZPositions(bps, initPosition); +export function getBlockTargetPosition(initPosition: IPos, bps: number, tiltDegress: number): IPos { + const { y, z } = generateYZPositions(bps, initPosition, tiltDegress); const emitterMinX = initPosition.x - EMITTER_WIDTH / 2; const emitterMaxX = initPosition.x + EMITTER_WIDTH / 2; diff --git a/client/src/features/visualizer-threejs/constants.ts b/client/src/features/visualizer-threejs/constants.ts index cc588c7f8..12adbda36 100644 --- a/client/src/features/visualizer-threejs/constants.ts +++ b/client/src/features/visualizer-threejs/constants.ts @@ -93,3 +93,8 @@ export const MAX_SINUSOID_PERIOD = 8; export const NUMBER_OF_RANDOM_AMPLITUDES = 100; export const MIN_SINUSOID_AMPLITUDE = 100; export const MAX_SINUSOID_AMPLITUDE = 200; + +export const NUMBER_OF_RANDOM_TILTINGS = 100; +export const TILT_DURATION_SECONDS = 4; +export const MAX_TILT_FACTOR_DEGREES = 16; +export const MIN_TILT_FACTOR_DEGREES = 1; diff --git a/client/src/features/visualizer-threejs/interfaces.ts b/client/src/features/visualizer-threejs/interfaces.ts index 81bae2dd5..afe4af6d0 100644 --- a/client/src/features/visualizer-threejs/interfaces.ts +++ b/client/src/features/visualizer-threejs/interfaces.ts @@ -11,6 +11,10 @@ export interface IThreeDimensionalPosition { z: number; } +export interface IThreeDimensionalPositionWithTilt extends IThreeDimensionalPosition { + tiltFactor: number; +} + export interface ITimeBasedPositionParams { currentAnimationTime: number; } diff --git a/client/src/features/visualizer-threejs/store/config.ts b/client/src/features/visualizer-threejs/store/config.ts index d505f5177..8c5d9ce9d 100644 --- a/client/src/features/visualizer-threejs/store/config.ts +++ b/client/src/features/visualizer-threejs/store/config.ts @@ -23,6 +23,9 @@ interface ConfigState { randomSinusoidAmplitudes: number[]; setRandomSinusoidAmplitudes: (randomizedAmplitudes: number[]) => void; + + randomTilts: number[]; + setRandomTilts: (randomTilts: number[]) => void; } export const useConfigStore = create((set) => ({ @@ -110,4 +113,15 @@ export const useConfigStore = create((set) => ({ randomSinusoidAmplitudes: randomizedAmplitudes, })); }, + + /** + * Randomized tilts for the tangle. + */ + randomTilts: [], + setRandomTilts: (randomTilts) => { + set((state) => ({ + ...state, + randomTilts, + })); + }, })); diff --git a/client/src/features/visualizer-threejs/utils.ts b/client/src/features/visualizer-threejs/utils.ts index bba6c9ec2..d66c97542 100644 --- a/client/src/features/visualizer-threejs/utils.ts +++ b/client/src/features/visualizer-threejs/utils.ts @@ -20,6 +20,10 @@ import { NUMBER_OF_RANDOM_AMPLITUDES, MIN_SINUSOID_AMPLITUDE, MAX_SINUSOID_AMPLITUDE, + NUMBER_OF_RANDOM_TILTINGS, + MIN_TILT_FACTOR_DEGREES, + MAX_TILT_FACTOR_DEGREES, + TILT_DURATION_SECONDS, } from "./constants"; import type { ICameraAngles, ISinusoidalPositionParams, IThreeDimensionalPosition } from "./interfaces"; @@ -105,6 +109,7 @@ function getDynamicRandomYZPoints( y: 0, z: 0, }, + tiltDegrees: number, ): IBlockTanglePosition { const theta = Math.random() * (2 * Math.PI); @@ -112,9 +117,12 @@ function getDynamicRandomYZPoints( const randomFactor = Math.random(); const radius = randomFactor * maxRadius; - const y = radius * Math.cos(theta) + initialPosition.y; + let y = radius * Math.cos(theta) + initialPosition.y; const z = radius * Math.sin(theta) + initialPosition.z; + const tiltRadians = tiltDegrees * (Math.PI / 180); + y += Math.tan(tiltRadians) * radius; + return { y, z }; } @@ -138,13 +146,14 @@ function generateAValidRandomPoint( bps: number, initialPosition: IThreeDimensionalPosition, prevPoints: IBlockTanglePosition[], + tiltDegress: number, ): IBlockTanglePosition { let trialPoint: IBlockTanglePosition; let passAllChecks = false; let retries = 0; do { - trialPoint = getDynamicRandomYZPoints(bps, initialPosition); + trialPoint = getDynamicRandomYZPoints(bps, initialPosition, tiltDegress); passAllChecks = pointPassesAllChecks(trialPoint, prevPoints); retries++; } while (!passAllChecks && retries < MAX_POINT_RETRIES); @@ -164,8 +173,8 @@ function generateAValidRandomPoint( export function getGenerateDynamicYZPosition(): typeof getDynamicRandomYZPoints { const prevPoints: IBlockTanglePosition[] = []; - return (bps: number, initialPosition: IThreeDimensionalPosition = { x: 0, y: 0, z: 0 }): IBlockTanglePosition => { - const validPoint = generateAValidRandomPoint(bps, initialPosition, prevPoints); + return (bps: number, initialPosition: IThreeDimensionalPosition = { x: 0, y: 0, z: 0 }, tiltDegress): IBlockTanglePosition => { + const validPoint = generateAValidRandomPoint(bps, initialPosition, prevPoints, tiltDegress); const randomYNumber = randomNumberFromInterval(0, BLOCK_STEP_PX / 20); const randomZNumber = randomNumberFromInterval(0, BLOCK_STEP_PX / 20); @@ -345,3 +354,27 @@ export function generateRandomAmplitudes(): number[] { } return amplitudes; } + +export function generateRandomTiltings(): number[] { + let previousValue: number; + + const tilts: number[] = Array.from({ length: NUMBER_OF_RANDOM_TILTINGS }, () => { + let randomTilt = randomIntFromInterval(MIN_TILT_FACTOR_DEGREES, MAX_TILT_FACTOR_DEGREES); + + if ((previousValue < 0 && randomTilt < 0) || (previousValue > 0 && randomTilt > 0)) { + randomTilt *= -1; + } + + previousValue = randomTilt; + + return randomTilt; + }); + return tilts; +} + +export function getCurrentTiltValue(animationTime: number, tilts: number[]): number { + const tiltAnimationDuration = TILT_DURATION_SECONDS * 2; + const currentAnimationTime = animationTime % tiltAnimationDuration; + currentAnimationTime; + return 0; +} From ac9bd5d2f466a29dd7fa7d13781811ed78c604ab Mon Sep 17 00:00:00 2001 From: JCNoguera Date: Thu, 22 Feb 2024 16:20:04 +0100 Subject: [PATCH 06/22] feat: improve spray --- .../features/visualizer-threejs/Emitter.tsx | 12 ++++++++- .../visualizer-threejs/blockPositions.ts | 15 +++-------- .../features/visualizer-threejs/constants.ts | 12 ++++----- .../visualizer-threejs/store/tangle.ts | 5 ++-- .../visualizer-threejs/useRenderTangle.tsx | 6 ++--- .../src/features/visualizer-threejs/utils.ts | 25 ++++++++++++++++--- 6 files changed, 47 insertions(+), 28 deletions(-) diff --git a/client/src/features/visualizer-threejs/Emitter.tsx b/client/src/features/visualizer-threejs/Emitter.tsx index 1a2b7c08c..b19a07765 100644 --- a/client/src/features/visualizer-threejs/Emitter.tsx +++ b/client/src/features/visualizer-threejs/Emitter.tsx @@ -4,7 +4,14 @@ import React, { RefObject, Dispatch, SetStateAction, useEffect, useRef, useLayou import * as THREE from "three"; import { useConfigStore, useTangleStore } from "./store"; import { useRenderTangle } from "./useRenderTangle"; -import { getTangleDistances, getEmitterPositions, generateRandomPeriods, generateRandomAmplitudes, generateRandomTiltings } from "./utils"; +import { + getTangleDistances, + getEmitterPositions, + generateRandomPeriods, + generateRandomAmplitudes, + generateRandomTiltings, + getCurrentTiltValue, +} from "./utils"; import { CanvasElement } from "./enums"; import useVisualizerTimer from "~/helpers/nova/hooks/useVisualizerTimer"; import { EMITTER_DEPTH, EMITTER_HEIGHT, EMITTER_WIDTH } from "./constants"; @@ -36,6 +43,7 @@ const Emitter: React.FC = ({ setRunListeners, emitterRef }: Emitte const randomSinusoidAmplitudes = useConfigStore((state) => state.randomSinusoidAmplitudes); const setRandomSinusoidAmplitudes = useConfigStore((state) => state.setRandomSinusoidAmplitudes); + const randomTilts = useConfigStore((state) => state.randomTilts); const setRandomTilts = useConfigStore((state) => state.setRandomTilts); const tangleWrapperRef = useRef(null); @@ -72,6 +80,7 @@ const Emitter: React.FC = ({ setRunListeners, emitterRef }: Emitte */ useFrame(() => { const currentAnimationTime = getVisualizerTimeDiff(); + const currentTilt = getCurrentTiltValue(currentAnimationTime, randomTilts); const { x, y } = getEmitterPositions({ currentAnimationTime, periods: sinusoidRandomPeriods, @@ -83,6 +92,7 @@ const Emitter: React.FC = ({ setRunListeners, emitterRef }: Emitte if (emitterRef.current) { emitterRef.current.position.x = x; emitterRef.current.position.y = y; + emitterRef.current.rotation.z = THREE.MathUtils.degToRad(currentTilt); } if (tangleWrapperRef.current) { diff --git a/client/src/features/visualizer-threejs/blockPositions.ts b/client/src/features/visualizer-threejs/blockPositions.ts index 8d556972b..9a4147a0d 100644 --- a/client/src/features/visualizer-threejs/blockPositions.ts +++ b/client/src/features/visualizer-threejs/blockPositions.ts @@ -1,6 +1,6 @@ -import { EMITTER_WIDTH, EMITTER_X_POSITION_MULTIPLIER } from "./constants"; +import { SPRAY_DISTANCE } from "./constants"; import { ISinusoidalPositionParams } from "./interfaces"; -import { getEmitterPositions, getGenerateDynamicYZPosition, getTangleDistances, randomIntFromInterval } from "./utils"; +import { getEmitterPositions, getGenerateDynamicYZPosition, getTangleDistances } from "./utils"; const generateYZPositions = getGenerateDynamicYZPosition(); @@ -11,16 +11,7 @@ interface IPos { } export function getBlockTargetPosition(initPosition: IPos, bps: number, tiltDegress: number): IPos { const { y, z } = generateYZPositions(bps, initPosition, tiltDegress); - - 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 }; + return { x: initPosition.x - SPRAY_DISTANCE, y, z }; } export function getBlockInitPosition({ currentAnimationTime, periods, periodsSum, sinusoidAmplitudes }: ISinusoidalPositionParams): IPos { diff --git a/client/src/features/visualizer-threejs/constants.ts b/client/src/features/visualizer-threejs/constants.ts index 12adbda36..a0aa310b1 100644 --- a/client/src/features/visualizer-threejs/constants.ts +++ b/client/src/features/visualizer-threejs/constants.ts @@ -19,7 +19,6 @@ export const ZOOM_DEFAULT = 2; export const TIME_DIFF_COUNTER = 250; export const SECOND = 1000; export const DATA_SENDER_TIME_INTERVAL = 500; -export const ANIMATION_TIME_SECONDS = 3; // colors export const PENDING_BLOCK_COLOR = new Color("#A6C3FC"); @@ -35,7 +34,7 @@ export const BLOCK_STATE_TO_COLOR = new Map([ ]); // emitter -export const EMITTER_SPEED_MULTIPLIER = 80; +export const EMITTER_SPEED_MULTIPLIER = 150; export const EMITTER_PADDING_RIGHT = 150; export const VISUALIZER_SAFE_ZONE = 150; @@ -72,10 +71,10 @@ export const EMITTER_HEIGHT = 250; export const EMITTER_DEPTH = 250; // conic emitter -export const MIN_TANGLE_RADIUS = 100; -export const MAX_TANGLE_RADIUS = 300; +export const MIN_TANGLE_RADIUS = 200; +export const MAX_TANGLE_RADIUS = 600; -export const MIN_BLOCKS_PER_SECOND = 100; +export const MIN_BLOCKS_PER_SECOND = 150; export const MAX_BLOCKS_PER_SECOND = 250; export const MIN_BLOCK_NEAR_RADIUS = 20; @@ -83,7 +82,8 @@ export const MIN_BLOCK_NEAR_RADIUS = 20; export const MAX_POINT_RETRIES = 10; export const MAX_PREV_POINTS = 20; -export const EMITTER_X_POSITION_MULTIPLIER = 3; +export const SPRAY_DISTANCE = 500; +export const SPRAY_ANIMATION_DURATION = SPRAY_DISTANCE / EMITTER_SPEED_MULTIPLIER; /* Values for randomizing the tangle */ export const NUMBER_OF_RANDOM_PERIODS = 100; diff --git a/client/src/features/visualizer-threejs/store/tangle.ts b/client/src/features/visualizer-threejs/store/tangle.ts index 4fb7e376d..5044b9914 100644 --- a/client/src/features/visualizer-threejs/store/tangle.ts +++ b/client/src/features/visualizer-threejs/store/tangle.ts @@ -1,7 +1,7 @@ import { Color } from "three"; import { create } from "zustand"; import { devtools } from "zustand/middleware"; -import { ZOOM_DEFAULT, ANIMATION_TIME_SECONDS } from "../constants"; +import { ZOOM_DEFAULT, EMITTER_SPEED_MULTIPLIER, SPRAY_DISTANCE } from "../constants"; import { IFeedBlockData } from "~models/api/nova/feed/IFeedBlockData"; import { IThreeDimensionalPosition } from "../interfaces"; import { BlockId, SlotIndex } from "@iota/sdk-wasm-nova/web"; @@ -104,7 +104,8 @@ export const useTangleStore = create()( }); for (const [key, value] of state.blockIdToAnimationPosition) { - if (value.elapsedTime > ANIMATION_TIME_SECONDS) { + const animationTime = SPRAY_DISTANCE / EMITTER_SPEED_MULTIPLIER; + if (value.elapsedTime > animationTime) { state.blockIdToAnimationPosition.delete(key); } } diff --git a/client/src/features/visualizer-threejs/useRenderTangle.tsx b/client/src/features/visualizer-threejs/useRenderTangle.tsx index 14f8c3b27..2297a1c51 100644 --- a/client/src/features/visualizer-threejs/useRenderTangle.tsx +++ b/client/src/features/visualizer-threejs/useRenderTangle.tsx @@ -1,7 +1,7 @@ import { useFrame, useThree } from "@react-three/fiber"; import { useEffect, useRef } from "react"; import * as THREE from "three"; -import { ANIMATION_TIME_SECONDS, MAX_BLOCK_INSTANCES, NODE_SIZE_DEFAULT } from "./constants"; +import { SPRAY_ANIMATION_DURATION, MAX_BLOCK_INSTANCES, NODE_SIZE_DEFAULT } from "./constants"; import { useMouseMove } from "./hooks/useMouseMove"; import { IBlockState, IBlockAnimationPosition, useConfigStore, useTangleStore } from "./store"; import { useRenderEdges } from "./useRenderEdges"; @@ -161,10 +161,10 @@ export const useRenderTangle = () => { blockIdToAnimationPosition.forEach(({ initPosition, targetPosition, blockAddedTimestamp }, blockId) => { const currentAnimationTime = getVisualizerTimeDiff(); const elapsedTime = currentAnimationTime - blockAddedTimestamp; - const positionBasedOnTime = Math.min(elapsedTime / ANIMATION_TIME_SECONDS, 1); + const animationAlpha = Math.min(elapsedTime / SPRAY_ANIMATION_DURATION, 1); const targetPositionVector = new THREE.Vector3(); - targetPositionVector.lerpVectors(positionToVector(initPosition), positionToVector(targetPosition), positionBasedOnTime); + targetPositionVector.lerpVectors(positionToVector(initPosition), positionToVector(targetPosition), animationAlpha); updatedAnimationPositions.set(blockId, { initPosition, elapsedTime, targetPosition, blockAddedTimestamp }); const index = blockIdToIndex.get(blockId); diff --git a/client/src/features/visualizer-threejs/utils.ts b/client/src/features/visualizer-threejs/utils.ts index d66c97542..4739b288f 100644 --- a/client/src/features/visualizer-threejs/utils.ts +++ b/client/src/features/visualizer-threejs/utils.ts @@ -373,8 +373,25 @@ export function generateRandomTiltings(): number[] { } export function getCurrentTiltValue(animationTime: number, tilts: number[]): number { - const tiltAnimationDuration = TILT_DURATION_SECONDS * 2; - const currentAnimationTime = animationTime % tiltAnimationDuration; - currentAnimationTime; - return 0; + const tiltAnimationDuration = TILT_DURATION_SECONDS * 2; // Multiplied by 2 so it goes back to the initial position + const totalIntervalDuration = tilts.length * tiltAnimationDuration; // The total duration of the random tilts + + const currentTiltAnimationSeconds = animationTime % tiltAnimationDuration; + const currentAnimationSecondsInInterval = animationTime % totalIntervalDuration; + + const currentTiltIndex = Math.floor(currentAnimationSecondsInInterval / tiltAnimationDuration); + const tilt = tilts[currentTiltIndex]; + + // Calculate the proportion of the current animation time within the half-duration + const proportionOfHalfDuration = currentTiltAnimationSeconds / (tiltAnimationDuration / 2); + let currentTilt; + + if (currentTiltAnimationSeconds <= tiltAnimationDuration / 2) { + currentTilt = tilt * proportionOfHalfDuration; + } else { + // We subtract from 2 to reverse the effect after the peak + currentTilt = tilt * (2 - proportionOfHalfDuration); + } + + return currentTilt; } From 9cefde30b06a5f8e0acc23c451e76ceb47a19fb1 Mon Sep 17 00:00:00 2001 From: Eugene Panteleymonchuk Date: Thu, 22 Feb 2024 23:21:24 +0200 Subject: [PATCH 07/22] feat: controls for visualizer (PoC) --- .../features/visualizer-threejs/Controls.tsx | 113 ++++++++++++++++++ .../features/visualizer-threejs/Emitter.tsx | 1 + .../visualizer-threejs/VisualizerInstance.tsx | 8 ++ .../src/features/visualizer-threejs/utils.ts | 4 + .../visualizer-threejs/wrapper/Wrapper.tsx | 2 + 5 files changed, 128 insertions(+) create mode 100644 client/src/features/visualizer-threejs/Controls.tsx diff --git a/client/src/features/visualizer-threejs/Controls.tsx b/client/src/features/visualizer-threejs/Controls.tsx new file mode 100644 index 000000000..31e15f606 --- /dev/null +++ b/client/src/features/visualizer-threejs/Controls.tsx @@ -0,0 +1,113 @@ +import React, { useState } from 'react'; + +const defaultControlsVisualiser: IControlsVisualiser = { MIN_SINUSOID_PERIOD: 5, MAX_SINUSOID_PERIOD: 8 } + +interface IControlsVisualiser { + MIN_SINUSOID_PERIOD: number; + MAX_SINUSOID_PERIOD: number; +} +/** + * Retrieves a value from localStorage and parses it as JSON. + */ +const LOCAL_STORAGE_KEY = 'controlsVisualiser'; +export function getFromLocalStorage(): IControlsVisualiser { + const item = localStorage.getItem(LOCAL_STORAGE_KEY); + return item ? JSON.parse(item) : defaultControlsVisualiser; +} + +/** + * Saves a value to localStorage as a JSON string. + */ +function setToLocalStorage(value: IControlsVisualiser) { + localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(value)); +} + +const Input = ({label, min, max, val, set}: {label: string; min: number; max: number; val: number; set: (val: number) => void}) => { + const [err, setErr] = useState(); + + const handleChange = (event: React.ChangeEvent) => { + + // Check if the input value is a valid number + if (event.target.value === '-' || event.target.value === '') { + // @ts-ignore + set(event.target.value); + return; + } + + const newValue = Math.max(min, Math.min(max, Number(event.target.value))); + if (newValue >= min && newValue <= max) { + setErr(undefined); + set(newValue); + } else { + set(newValue); + } + }; + + return ( +
+ + + {!!err &&
{err}
} +
+ ); +}; + +export const Controls = () => { + const [state, setState] = useState(() => { + // Use getFromLocalStorage to retrieve the state + return getFromLocalStorage() || defaultControlsVisualiser; + }); + + const handleApply = () => { + setToLocalStorage(state); + location.reload(); + } + + return ( +
+ { + setState({ + ...state, + MIN_SINUSOID_PERIOD: val, + }); + }} + /> + { + setState({ + ...state, + MAX_SINUSOID_PERIOD: val, + }); + }} + /> + +
+ ); +}; + + diff --git a/client/src/features/visualizer-threejs/Emitter.tsx b/client/src/features/visualizer-threejs/Emitter.tsx index 1a2b7c08c..4b0b4bb9d 100644 --- a/client/src/features/visualizer-threejs/Emitter.tsx +++ b/client/src/features/visualizer-threejs/Emitter.tsx @@ -41,6 +41,7 @@ const Emitter: React.FC = ({ setRunListeners, emitterRef }: Emitte const tangleWrapperRef = useRef(null); useLayoutEffect(() => { + console.log('--- test'); const { periods, sum: periodsSum } = generateRandomPeriods(); const amplitudes = generateRandomAmplitudes(); const tiltings = generateRandomTiltings(); diff --git a/client/src/features/visualizer-threejs/VisualizerInstance.tsx b/client/src/features/visualizer-threejs/VisualizerInstance.tsx index f7a26d2e3..cb645884d 100644 --- a/client/src/features/visualizer-threejs/VisualizerInstance.tsx +++ b/client/src/features/visualizer-threejs/VisualizerInstance.tsx @@ -272,6 +272,14 @@ const VisualizerInstance: React.FC> = removeConfirmedBlocksSlot(slot); } + const [localKey, setLocalKey] = React.useState(0); + useEffect(() => { + setTimeout(() => { + // location.reload(); + // setLocalKey(2); + }, 3000); + }, []); + return ( { + + const { MIN_SINUSOID_PERIOD } = getFromLocalStorage(); + console.log('--- MIN_SINUSOID_PERIOD', MIN_SINUSOID_PERIOD); const period = Number(randomNumberFromInterval(MIN_SINUSOID_PERIOD, MAX_SINUSOID_PERIOD).toFixed(4)); sum += period; return period; diff --git a/client/src/features/visualizer-threejs/wrapper/Wrapper.tsx b/client/src/features/visualizer-threejs/wrapper/Wrapper.tsx index e2796f4d7..65e60336c 100644 --- a/client/src/features/visualizer-threejs/wrapper/Wrapper.tsx +++ b/client/src/features/visualizer-threejs/wrapper/Wrapper.tsx @@ -5,6 +5,7 @@ import { INetwork } from "~/models/config/INetwork"; import KeyPanel from "./KeyPanel"; import mainHeader from "~assets/modals/visualizer/main-header.json"; import { SelectedFeedInfo } from "./SelectedFeedInfo"; +import { Controls } from "../Controls"; export const Wrapper = ({ blocksCount, @@ -46,6 +47,7 @@ export const Wrapper = ({ +
{children}
From 9778b0bbc22972a5dc10fc48d30389e783df5952 Mon Sep 17 00:00:00 2001 From: Eugene Panteleymonchuk Date: Fri, 23 Feb 2024 13:55:40 +0200 Subject: [PATCH 08/22] feat: add all fields, cover by feature flag. --- .../features/visualizer-threejs/Controls.scss | 17 ++ .../features/visualizer-threejs/Controls.tsx | 179 ++++++++++++++---- .../visualizer-threejs/VisualizerInstance.tsx | 3 +- .../src/features/visualizer-threejs/utils.ts | 5 +- 4 files changed, 165 insertions(+), 39 deletions(-) create mode 100644 client/src/features/visualizer-threejs/Controls.scss diff --git a/client/src/features/visualizer-threejs/Controls.scss b/client/src/features/visualizer-threejs/Controls.scss new file mode 100644 index 000000000..0eb12d638 --- /dev/null +++ b/client/src/features/visualizer-threejs/Controls.scss @@ -0,0 +1,17 @@ +.controls-container { + background: var(--body-background); + border: 1px solid var(--border-color); + padding: 8px 16px; + border-radius: 8px; + .controls__list { + display: flex; + gap: 8px; + } + .controls__item { + flex: 1; + + input { + padding: 8px 16px; + } + } +} diff --git a/client/src/features/visualizer-threejs/Controls.tsx b/client/src/features/visualizer-threejs/Controls.tsx index 31e15f606..f6be1fcf5 100644 --- a/client/src/features/visualizer-threejs/Controls.tsx +++ b/client/src/features/visualizer-threejs/Controls.tsx @@ -1,18 +1,47 @@ import React, { useState } from 'react'; +import { features } from './VisualizerInstance'; +import { + MIN_SINUSOID_PERIOD, + MAX_SINUSOID_PERIOD, + MIN_SINUSOID_AMPLITUDE, + MAX_SINUSOID_AMPLITUDE, + MIN_TILT_FACTOR_DEGREES, + MAX_TILT_FACTOR_DEGREES, +} from "./constants"; +import "./Controls.scss"; -const defaultControlsVisualiser: IControlsVisualiser = { MIN_SINUSOID_PERIOD: 5, MAX_SINUSOID_PERIOD: 8 } +const defaultControlsVisualiser: IControlsVisualiser = { + MIN_SINUSOID_PERIOD: MIN_SINUSOID_PERIOD, + MAX_SINUSOID_PERIOD: MAX_SINUSOID_PERIOD, + MIN_SINUSOID_AMPLITUDE: MIN_SINUSOID_AMPLITUDE, + MAX_SINUSOID_AMPLITUDE: MAX_SINUSOID_AMPLITUDE, + MIN_TILT_FACTOR_DEGREES: MIN_TILT_FACTOR_DEGREES, + MAX_TILT_FACTOR_DEGREES: MAX_TILT_FACTOR_DEGREES, +} interface IControlsVisualiser { MIN_SINUSOID_PERIOD: number; MAX_SINUSOID_PERIOD: number; + MIN_SINUSOID_AMPLITUDE: number; + MAX_SINUSOID_AMPLITUDE: number; + MIN_TILT_FACTOR_DEGREES: number; + MAX_TILT_FACTOR_DEGREES: number; } /** * Retrieves a value from localStorage and parses it as JSON. */ const LOCAL_STORAGE_KEY = 'controlsVisualiser'; + export function getFromLocalStorage(): IControlsVisualiser { - const item = localStorage.getItem(LOCAL_STORAGE_KEY); - return item ? JSON.parse(item) : defaultControlsVisualiser; + if (features.controlsVisualiserEnabled) { + const item = localStorage.getItem(LOCAL_STORAGE_KEY); + return item ? JSON.parse(item) : defaultControlsVisualiser; + } else { + localStorage.removeItem(LOCAL_STORAGE_KEY); + return defaultControlsVisualiser; + } + + } /** @@ -53,8 +82,8 @@ const Input = ({label, min, max, val, set}: {label: string; min: number; max: nu @@ -69,41 +98,123 @@ export const Controls = () => { return getFromLocalStorage() || defaultControlsVisualiser; }); + const [errors, setErrors] = useState({}); + const handleApply = () => { + if (Object.keys(errors).some(key => errors[key])) { + // Handle the error case, e.g., display a message + console.error("There are errors in the form."); + return; + } + setToLocalStorage(state); location.reload(); } + const setError = (key: string, error: string) => { + setErrors(prevErrors => ({ + ...prevErrors, + [key]: error + })); + }; + + if (!features.controlsVisualiserEnabled) { + return null; + } + return ( -
- { - setState({ - ...state, - MIN_SINUSOID_PERIOD: val, - }); - }} - /> - { - setState({ - ...state, - MAX_SINUSOID_PERIOD: val, - }); - }} - /> -
diff --git a/client/src/features/visualizer-threejs/VisualizerInstance.tsx b/client/src/features/visualizer-threejs/VisualizerInstance.tsx index cb645884d..35a672dd5 100644 --- a/client/src/features/visualizer-threejs/VisualizerInstance.tsx +++ b/client/src/features/visualizer-threejs/VisualizerInstance.tsx @@ -33,9 +33,10 @@ import useVisualizerTimer from "~/helpers/nova/hooks/useVisualizerTimer"; import { getBlockInitPosition, getBlockTargetPosition } from "./blockPositions"; import { getCurrentTiltValue } from "./utils"; -const features = { +export const features = { statsEnabled: false, cameraControls: true, + controlsVisualiserEnabled: true, }; const VisualizerInstance: React.FC> = ({ diff --git a/client/src/features/visualizer-threejs/utils.ts b/client/src/features/visualizer-threejs/utils.ts index 3b0926cc1..13e2def4e 100644 --- a/client/src/features/visualizer-threejs/utils.ts +++ b/client/src/features/visualizer-threejs/utils.ts @@ -15,8 +15,6 @@ import { CAMERA_X_OFFSET, CAMERA_Y_OFFSET, NUMBER_OF_RANDOM_PERIODS, - MIN_SINUSOID_PERIOD, - MAX_SINUSOID_PERIOD, NUMBER_OF_RANDOM_AMPLITUDES, MIN_SINUSOID_AMPLITUDE, MAX_SINUSOID_AMPLITUDE, @@ -301,8 +299,7 @@ export function generateRandomPeriods(): { periods: number[]; sum: number } { let sum = 0; const periods = Array.from({ length: NUMBER_OF_RANDOM_PERIODS }, () => { - const { MIN_SINUSOID_PERIOD } = getFromLocalStorage(); - console.log('--- MIN_SINUSOID_PERIOD', MIN_SINUSOID_PERIOD); + const { MIN_SINUSOID_PERIOD, MAX_SINUSOID_PERIOD } = getFromLocalStorage(); const period = Number(randomNumberFromInterval(MIN_SINUSOID_PERIOD, MAX_SINUSOID_PERIOD).toFixed(4)); sum += period; return period; From 2bd70731bacfa61d624cf968c1e2368ba9063cc8 Mon Sep 17 00:00:00 2001 From: Eugene Panteleymonchuk Date: Fri, 23 Feb 2024 14:12:10 +0200 Subject: [PATCH 09/22] feat: control values by list. --- .../features/visualizer-threejs/Controls.tsx | 228 +++++++----------- 1 file changed, 84 insertions(+), 144 deletions(-) diff --git a/client/src/features/visualizer-threejs/Controls.tsx b/client/src/features/visualizer-threejs/Controls.tsx index f6be1fcf5..b46580a46 100644 --- a/client/src/features/visualizer-threejs/Controls.tsx +++ b/client/src/features/visualizer-threejs/Controls.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import { features } from './VisualizerInstance'; +import React, { useState } from "react"; +import { features } from "./VisualizerInstance"; import { MIN_SINUSOID_PERIOD, MAX_SINUSOID_PERIOD, @@ -17,7 +17,7 @@ const defaultControlsVisualiser: IControlsVisualiser = { MAX_SINUSOID_AMPLITUDE: MAX_SINUSOID_AMPLITUDE, MIN_TILT_FACTOR_DEGREES: MIN_TILT_FACTOR_DEGREES, MAX_TILT_FACTOR_DEGREES: MAX_TILT_FACTOR_DEGREES, -} +}; interface IControlsVisualiser { MIN_SINUSOID_PERIOD: number; @@ -27,10 +27,13 @@ interface IControlsVisualiser { MIN_TILT_FACTOR_DEGREES: number; MAX_TILT_FACTOR_DEGREES: number; } + +type TKey = keyof IControlsVisualiser; + /** * Retrieves a value from localStorage and parses it as JSON. */ -const LOCAL_STORAGE_KEY = 'controlsVisualiser'; +const LOCAL_STORAGE_KEY = "controlsVisualiser"; export function getFromLocalStorage(): IControlsVisualiser { if (features.controlsVisualiserEnabled) { @@ -40,8 +43,6 @@ export function getFromLocalStorage(): IControlsVisualiser { localStorage.removeItem(LOCAL_STORAGE_KEY); return defaultControlsVisualiser; } - - } /** @@ -51,57 +52,62 @@ function setToLocalStorage(value: IControlsVisualiser) { localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(value)); } -const Input = ({label, min, max, val, set}: {label: string; min: number; max: number; val: number; set: (val: number) => void}) => { - const [err, setErr] = useState(); - - const handleChange = (event: React.ChangeEvent) => { - - // Check if the input value is a valid number - if (event.target.value === '-' || event.target.value === '') { - // @ts-ignore - set(event.target.value); - return; - } - - const newValue = Math.max(min, Math.min(max, Number(event.target.value))); - if (newValue >= min && newValue <= max) { - setErr(undefined); - set(newValue); - } else { - set(newValue); - } - }; - - return ( -
- - - {!!err &&
{err}
} -
- ); -}; - export const Controls = () => { const [state, setState] = useState(() => { // Use getFromLocalStorage to retrieve the state return getFromLocalStorage() || defaultControlsVisualiser; }); - const [errors, setErrors] = useState({}); + const [errors, setErrors] = useState<{ + [k: string]: string; + }>({}); + + const inputs: { + key: TKey; + label: string; + min: number; + max: number; + }[] = [ + { + key: "MIN_SINUSOID_PERIOD", + label: "Min sinusoid period", + min: 1, + max: 7, + }, + { + key: "MAX_SINUSOID_PERIOD", + label: "Max sinusoid period", + min: 8, + max: 15, + }, + { + key: "MIN_SINUSOID_AMPLITUDE", + label: "Min sinusoid amplitude", + min: 100, + max: 199, + }, + { + key: "MAX_SINUSOID_AMPLITUDE", + label: "Max sinusoid amplitude", + min: 200, + max: 500, + }, + { + key: "MIN_TILT_FACTOR_DEGREES", + label: "Min tilt factor degrees", + min: 1, + max: 15, + }, + { + key: "MAX_TILT_FACTOR_DEGREES", + label: "Max tilt factor degrees", + min: 16, + max: 100, + }, + ]; const handleApply = () => { - if (Object.keys(errors).some(key => errors[key])) { + if (Object.keys(errors).some((key) => errors[key])) { // Handle the error case, e.g., display a message console.error("There are errors in the form."); return; @@ -109,13 +115,20 @@ export const Controls = () => { setToLocalStorage(state); location.reload(); - } + }; + + const handleChange = (key: TKey, val: string) => { + const input = inputs.find((input) => input.key === key); + if (!input) return; - const setError = (key: string, error: string) => { - setErrors(prevErrors => ({ - ...prevErrors, - [key]: error - })); + const numericValue = Number(val); + if (numericValue < input.min || numericValue > input.max) { + setErrors((prevErrors) => ({ ...prevErrors, [key]: `Value must be between ${input.min} and ${input.max}` })); + } else { + setErrors((prevErrors) => ({ ...prevErrors, [key]: "" })); + } + + setState((prevState) => ({ ...prevState, [key]: numericValue })); }; if (!features.controlsVisualiserEnabled) { @@ -125,93 +138,22 @@ export const Controls = () => { return (
-
- { - setState({ - ...state, - MIN_SINUSOID_PERIOD: val, - }); - }} - setError={(error) => setError('MIN_SINUSOID_PERIOD', error)} - /> -
-
- { - setState({ - ...state, - MAX_SINUSOID_PERIOD: val, - }); - }} - error={errors['MAX_SINUSOID_PERIOD']} - setError={(error) => setError('MAX_SINUSOID_PERIOD', error)} - /> -
-
- { - setState({ - ...state, - MIN_SINUSOID_AMPLITUDE: val, - }); - }} - /> -
-
- { - setState({ - ...state, - MAX_SINUSOID_AMPLITUDE: val, - }); - }} - /> -
-
- { - setState({ - ...state, - MIN_TILT_FACTOR_DEGREES: val, - }); - }} - /> -
-
- { - setState({ - ...state, - MAX_TILT_FACTOR_DEGREES: val, - }); - }} - /> -
+ {inputs.map((i) => { + return ( +
+
+ + handleChange(i.key, e.target.value)} /> + {!!errors[i.key] &&
{errors[i.key]}
} +
+
+ ); + })}
); }; - - From 1d9fb1008e7271a6f926dfa65ccd1596b519f2cc Mon Sep 17 00:00:00 2001 From: Eugene Panteleymonchuk Date: Fri, 23 Feb 2024 14:31:19 +0200 Subject: [PATCH 10/22] feat: add "reset" btn. Signed-off-by: Eugene Panteleymonchuk --- .../features/visualizer-threejs/Controls.scss | 31 +++++++++++-------- .../features/visualizer-threejs/Controls.tsx | 28 +++++++++++++++-- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/client/src/features/visualizer-threejs/Controls.scss b/client/src/features/visualizer-threejs/Controls.scss index 0eb12d638..156810171 100644 --- a/client/src/features/visualizer-threejs/Controls.scss +++ b/client/src/features/visualizer-threejs/Controls.scss @@ -1,17 +1,22 @@ .controls-container { - background: var(--body-background); - border: 1px solid var(--border-color); - padding: 8px 16px; - border-radius: 8px; - .controls__list { - display: flex; - gap: 8px; - } - .controls__item { - flex: 1; + background: var(--body-background); + border: 1px solid var(--border-color); + padding: 8px 16px; + border-radius: 8px; + .controls__list { + display: flex; + gap: 8px; + } + .controls__item { + flex: 1; - input { - padding: 8px 16px; + input { + padding: 8px 16px; + } + } + .controls__actions { + margin-top: 16px; + display: flex; + gap: 8px; } - } } diff --git a/client/src/features/visualizer-threejs/Controls.tsx b/client/src/features/visualizer-threejs/Controls.tsx index b46580a46..d25727d60 100644 --- a/client/src/features/visualizer-threejs/Controls.tsx +++ b/client/src/features/visualizer-threejs/Controls.tsx @@ -52,11 +52,20 @@ function setToLocalStorage(value: IControlsVisualiser) { localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(value)); } +/** + */ +function isExistsInLocalStorage(): boolean { + return !!localStorage.getItem(LOCAL_STORAGE_KEY); +} + export const Controls = () => { const [state, setState] = useState(() => { // Use getFromLocalStorage to retrieve the state return getFromLocalStorage() || defaultControlsVisualiser; }); + const [isShowReset, setIsShowReset] = useState(() => { + return isExistsInLocalStorage(); + }); const [errors, setErrors] = useState<{ [k: string]: string; @@ -156,9 +165,22 @@ export const Controls = () => { })}
- +
+ + {isShowReset && ( + + )} +
); }; From eb79ebf691bdcb3c940b701aa4ff570ebdaad13b Mon Sep 17 00:00:00 2001 From: JCNoguera Date: Fri, 23 Feb 2024 14:54:06 +0100 Subject: [PATCH 11/22] fix: camera zoom --- .../visualizer-threejs/CameraControls.tsx | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/client/src/features/visualizer-threejs/CameraControls.tsx b/client/src/features/visualizer-threejs/CameraControls.tsx index 18c36e95e..3ed23fb3e 100644 --- a/client/src/features/visualizer-threejs/CameraControls.tsx +++ b/client/src/features/visualizer-threejs/CameraControls.tsx @@ -1,44 +1,29 @@ import { CameraControls as DreiCameraControls } from "@react-three/drei"; import { getCameraAngles } from "./utils"; -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import { useThree } from "@react-three/fiber"; import { CanvasElement } from "./enums"; import { VISUALIZER_PADDINGS } from "./constants"; +import { useTangleStore } from "./store"; const CAMERA_ANGLES = getCameraAngles(); const CameraControls = () => { const controls = React.useRef(null); + const [shouldLockZoom, setShouldLockZoom] = useState(false); const scene = useThree((state) => state.scene); + const zoom = useTangleStore((state) => state.zoom); const mesh = scene.getObjectByName(CanvasElement.TangleWrapperMesh) as THREE.Mesh | undefined; - /** - * Locks the camera zoom to the current zoom value. - */ - function lockCameraZoom(controls: DreiCameraControls) { - const zoom = controls.camera.zoom; - controls.maxZoom = zoom; - controls.minZoom = zoom; - } - - /** - * Unlocks the camera zoom for free movement. - */ - function unlockCameraZoom(controls: DreiCameraControls) { - controls.maxZoom = Infinity; - controls.minZoom = 0.01; - } - /** * Fits the camera to the TangleMesh. */ function fitCameraToTangle(controls: DreiCameraControls | null, mesh?: THREE.Mesh) { if (controls && mesh) { - unlockCameraZoom(controls); + setShouldLockZoom(false); controls.fitToBox(mesh, false, { ...VISUALIZER_PADDINGS }); - controls.setOrbitPoint(0, 0, 0); - lockCameraZoom(controls); + setShouldLockZoom(true); } } @@ -55,6 +40,16 @@ const CameraControls = () => { }; }, [controls, mesh]); + /** + * Locks the camera zoom to the current zoom value. + */ + useEffect(() => { + if (controls.current) { + controls.current.maxZoom = shouldLockZoom ? zoom : Infinity; + controls.current.minZoom = shouldLockZoom ? zoom : 0.01; + } + }, [controls.current, shouldLockZoom, zoom]); + return ; }; From 0dd05cc79dcffed9c0122910cb06c252547bed71 Mon Sep 17 00:00:00 2001 From: Eugene Panteleymonchuk Date: Fri, 23 Feb 2024 16:13:18 +0200 Subject: [PATCH 12/22] fix: import `features` bug Signed-off-by: Eugene Panteleymonchuk --- .../visualizer-threejs/CameraControls.tsx | 4 +- .../features/visualizer-threejs/Controls.scss | 1 + .../features/visualizer-threejs/Controls.tsx | 39 +++++++++++++------ .../features/visualizer-threejs/Emitter.tsx | 1 - .../visualizer-threejs/VisualizerInstance.tsx | 17 +------- .../features/visualizer-threejs/constants.ts | 6 +++ .../src/features/visualizer-threejs/utils.ts | 9 ++--- 7 files changed, 43 insertions(+), 34 deletions(-) diff --git a/client/src/features/visualizer-threejs/CameraControls.tsx b/client/src/features/visualizer-threejs/CameraControls.tsx index 18c36e95e..c73227fb2 100644 --- a/client/src/features/visualizer-threejs/CameraControls.tsx +++ b/client/src/features/visualizer-threejs/CameraControls.tsx @@ -17,7 +17,9 @@ const CameraControls = () => { * Locks the camera zoom to the current zoom value. */ function lockCameraZoom(controls: DreiCameraControls) { - const zoom = controls.camera.zoom; + // const zoom = controls.camera.zoom; + // console.log('--- zoom', zoom); + const zoom = 0.5; controls.maxZoom = zoom; controls.minZoom = zoom; } diff --git a/client/src/features/visualizer-threejs/Controls.scss b/client/src/features/visualizer-threejs/Controls.scss index 156810171..043c45b49 100644 --- a/client/src/features/visualizer-threejs/Controls.scss +++ b/client/src/features/visualizer-threejs/Controls.scss @@ -12,6 +12,7 @@ input { padding: 8px 16px; + width: 100%; } } .controls__actions { diff --git a/client/src/features/visualizer-threejs/Controls.tsx b/client/src/features/visualizer-threejs/Controls.tsx index d25727d60..c00ca8876 100644 --- a/client/src/features/visualizer-threejs/Controls.tsx +++ b/client/src/features/visualizer-threejs/Controls.tsx @@ -1,5 +1,4 @@ import React, { useState } from "react"; -import { features } from "./VisualizerInstance"; import { MIN_SINUSOID_PERIOD, MAX_SINUSOID_PERIOD, @@ -7,18 +6,11 @@ import { MAX_SINUSOID_AMPLITUDE, MIN_TILT_FACTOR_DEGREES, MAX_TILT_FACTOR_DEGREES, + TILT_DURATION_SECONDS, + features, } from "./constants"; import "./Controls.scss"; -const defaultControlsVisualiser: IControlsVisualiser = { - MIN_SINUSOID_PERIOD: MIN_SINUSOID_PERIOD, - MAX_SINUSOID_PERIOD: MAX_SINUSOID_PERIOD, - MIN_SINUSOID_AMPLITUDE: MIN_SINUSOID_AMPLITUDE, - MAX_SINUSOID_AMPLITUDE: MAX_SINUSOID_AMPLITUDE, - MIN_TILT_FACTOR_DEGREES: MIN_TILT_FACTOR_DEGREES, - MAX_TILT_FACTOR_DEGREES: MAX_TILT_FACTOR_DEGREES, -}; - interface IControlsVisualiser { MIN_SINUSOID_PERIOD: number; MAX_SINUSOID_PERIOD: number; @@ -26,8 +18,19 @@ interface IControlsVisualiser { MAX_SINUSOID_AMPLITUDE: number; MIN_TILT_FACTOR_DEGREES: number; MAX_TILT_FACTOR_DEGREES: number; + TILT_DURATION_SECONDS: number; } +const defaultControlsVisualiser: IControlsVisualiser = { + MIN_SINUSOID_PERIOD: MIN_SINUSOID_PERIOD, + MAX_SINUSOID_PERIOD: MAX_SINUSOID_PERIOD, + MIN_SINUSOID_AMPLITUDE: MIN_SINUSOID_AMPLITUDE, + MAX_SINUSOID_AMPLITUDE: MAX_SINUSOID_AMPLITUDE, + MIN_TILT_FACTOR_DEGREES: MIN_TILT_FACTOR_DEGREES, + MAX_TILT_FACTOR_DEGREES: MAX_TILT_FACTOR_DEGREES, + TILT_DURATION_SECONDS: TILT_DURATION_SECONDS, +}; + type TKey = keyof IControlsVisualiser; /** @@ -35,7 +38,7 @@ type TKey = keyof IControlsVisualiser; */ const LOCAL_STORAGE_KEY = "controlsVisualiser"; -export function getFromLocalStorage(): IControlsVisualiser { +export const getFromLocalStorage = (): IControlsVisualiser => { if (features.controlsVisualiserEnabled) { const item = localStorage.getItem(LOCAL_STORAGE_KEY); return item ? JSON.parse(item) : defaultControlsVisualiser; @@ -43,7 +46,7 @@ export function getFromLocalStorage(): IControlsVisualiser { localStorage.removeItem(LOCAL_STORAGE_KEY); return defaultControlsVisualiser; } -} +}; /** * Saves a value to localStorage as a JSON string. @@ -113,6 +116,12 @@ export const Controls = () => { min: 16, max: 100, }, + { + key: "TILT_DURATION_SECONDS", + label: "Tilt_duration_seconds", + min: 1, + max: 10, + }, ]; const handleApply = () => { @@ -130,6 +139,12 @@ export const Controls = () => { const input = inputs.find((input) => input.key === key); if (!input) return; + if (!val) { + setErrors((prevErrors) => ({ ...prevErrors, [key]: "Value is required" })); + setState((prevState) => ({ ...prevState, [key]: "" })); + return; + } + const numericValue = Number(val); if (numericValue < input.min || numericValue > input.max) { setErrors((prevErrors) => ({ ...prevErrors, [key]: `Value must be between ${input.min} and ${input.max}` })); diff --git a/client/src/features/visualizer-threejs/Emitter.tsx b/client/src/features/visualizer-threejs/Emitter.tsx index 4b0b4bb9d..1a2b7c08c 100644 --- a/client/src/features/visualizer-threejs/Emitter.tsx +++ b/client/src/features/visualizer-threejs/Emitter.tsx @@ -41,7 +41,6 @@ const Emitter: React.FC = ({ setRunListeners, emitterRef }: Emitte const tangleWrapperRef = useRef(null); useLayoutEffect(() => { - console.log('--- test'); const { periods, sum: periodsSum } = generateRandomPeriods(); const amplitudes = generateRandomAmplitudes(); const tiltings = generateRandomTiltings(); diff --git a/client/src/features/visualizer-threejs/VisualizerInstance.tsx b/client/src/features/visualizer-threejs/VisualizerInstance.tsx index 35a672dd5..3d8ed04c7 100644 --- a/client/src/features/visualizer-threejs/VisualizerInstance.tsx +++ b/client/src/features/visualizer-threejs/VisualizerInstance.tsx @@ -7,6 +7,7 @@ import { RouteComponentProps } from "react-router-dom"; import * as THREE from "three"; import { FAR_PLANE, + features, NEAR_PLANE, DIRECTIONAL_LIGHT_INTENSITY, PENDING_BLOCK_COLOR, @@ -33,12 +34,6 @@ import useVisualizerTimer from "~/helpers/nova/hooks/useVisualizerTimer"; import { getBlockInitPosition, getBlockTargetPosition } from "./blockPositions"; import { getCurrentTiltValue } from "./utils"; -export const features = { - statsEnabled: false, - cameraControls: true, - controlsVisualiserEnabled: true, -}; - const VisualizerInstance: React.FC> = ({ match: { params: { network }, @@ -272,15 +267,7 @@ const VisualizerInstance: React.FC> = removeConfirmedBlocksSlot(slot); } - - const [localKey, setLocalKey] = React.useState(0); - useEffect(() => { - setTimeout(() => { - // location.reload(); - // setLocalKey(2); - }, 3000); - }, []); - + // const [z, setZ] = useState(1); return ( { - const { MIN_SINUSOID_PERIOD, MAX_SINUSOID_PERIOD } = getFromLocalStorage(); const period = Number(randomNumberFromInterval(MIN_SINUSOID_PERIOD, MAX_SINUSOID_PERIOD).toFixed(4)); sum += period; @@ -330,6 +327,7 @@ function getCurrentPeriodValues(animationTime: number, periods: number[], totalS } function getNextAmplitudeWithVariation(currentAmplitude: number = 0): number { + const { MIN_SINUSOID_AMPLITUDE, MAX_SINUSOID_AMPLITUDE } = getFromLocalStorage(); const variation = (2 * MIN_SINUSOID_AMPLITUDE) / 3; const randomAmplitudeVariation = randomNumberFromInterval(-variation, variation); @@ -358,6 +356,7 @@ export function generateRandomAmplitudes(): number[] { export function generateRandomTiltings(): number[] { let previousValue: number; + const { MIN_TILT_FACTOR_DEGREES, MAX_TILT_FACTOR_DEGREES } = getFromLocalStorage(); const tilts: number[] = Array.from({ length: NUMBER_OF_RANDOM_TILTINGS }, () => { let randomTilt = randomIntFromInterval(MIN_TILT_FACTOR_DEGREES, MAX_TILT_FACTOR_DEGREES); From e5371fe3606a15ec97c32e8fc1c37e1e00ae7ac0 Mon Sep 17 00:00:00 2001 From: Eugene Panteleymonchuk Date: Fri, 23 Feb 2024 17:44:12 +0200 Subject: [PATCH 13/22] feat: dynamic zoom. Signed-off-by: Eugene Panteleymonchuk --- .../visualizer-threejs/CameraControls.tsx | 14 +++++++ .../{Controls.tsx => ConfigControls.tsx} | 37 ++++++++++++++++++- .../visualizer-threejs/store/tangle.ts | 10 +++++ .../src/features/visualizer-threejs/utils.ts | 2 +- .../visualizer-threejs/wrapper/Wrapper.tsx | 4 +- 5 files changed, 63 insertions(+), 4 deletions(-) rename client/src/features/visualizer-threejs/{Controls.tsx => ConfigControls.tsx} (81%) diff --git a/client/src/features/visualizer-threejs/CameraControls.tsx b/client/src/features/visualizer-threejs/CameraControls.tsx index a8353cac9..7a65a5382 100644 --- a/client/src/features/visualizer-threejs/CameraControls.tsx +++ b/client/src/features/visualizer-threejs/CameraControls.tsx @@ -9,14 +9,28 @@ import { VISUALIZER_PADDINGS } from "./constants"; const CAMERA_ANGLES = getCameraAngles(); const CameraControls = () => { + const { camera } = useThree(); const controls = React.useRef(null); const [shouldLockZoom, setShouldLockZoom] = useState(false); const scene = useThree((state) => state.scene); const zoom = useTangleStore((state) => state.zoom); + const forcedZoom = useTangleStore((state) => state.forcedZoom); const mesh = scene.getObjectByName(CanvasElement.TangleWrapperMesh) as THREE.Mesh | undefined; const canvasDimensions = useConfigStore((state) => state.dimensions); + useEffect(() => { + if (!forcedZoom) return; + + (async () => { + if (camera && controls.current) { + controls.current.minZoom = forcedZoom; + controls.current.minZoom = forcedZoom; + await controls.current.zoomTo(forcedZoom, true); + } + })(); + }, [forcedZoom]); + /** * Fits the camera to the TangleMesh. */ diff --git a/client/src/features/visualizer-threejs/Controls.tsx b/client/src/features/visualizer-threejs/ConfigControls.tsx similarity index 81% rename from client/src/features/visualizer-threejs/Controls.tsx rename to client/src/features/visualizer-threejs/ConfigControls.tsx index c00ca8876..3a983647c 100644 --- a/client/src/features/visualizer-threejs/Controls.tsx +++ b/client/src/features/visualizer-threejs/ConfigControls.tsx @@ -10,6 +10,7 @@ import { features, } from "./constants"; import "./Controls.scss"; +import { useTangleStore } from "~features/visualizer-threejs/store"; interface IControlsVisualiser { MIN_SINUSOID_PERIOD: number; @@ -61,7 +62,11 @@ function isExistsInLocalStorage(): boolean { return !!localStorage.getItem(LOCAL_STORAGE_KEY); } -export const Controls = () => { +export const ConfigControls = () => { + const forcedZoom = useTangleStore((state) => state.forcedZoom); + const setForcedZoom = useTangleStore((state) => state.setForcedZoom); + const [localZoom, setLocalZoom] = useState(forcedZoom); + const [state, setState] = useState(() => { // Use getFromLocalStorage to retrieve the state return getFromLocalStorage() || defaultControlsVisualiser; @@ -190,12 +195,42 @@ export const Controls = () => { onClick={() => { localStorage.removeItem(LOCAL_STORAGE_KEY); setIsShowReset(false); + location.reload(); }} > Reset )} + +
+ + { + if (!e.target.value) { + setLocalZoom(undefined); + return; + } + + const value = Number(e.target.value); + + if (value > 2) { + setLocalZoom(2); + return; + } + setLocalZoom(Number(e.target.value)); + }} + /> +
+ +
+
); }; + +export default React.memo(ConfigControls); diff --git a/client/src/features/visualizer-threejs/store/tangle.ts b/client/src/features/visualizer-threejs/store/tangle.ts index 5044b9914..1f47b446b 100644 --- a/client/src/features/visualizer-threejs/store/tangle.ts +++ b/client/src/features/visualizer-threejs/store/tangle.ts @@ -60,6 +60,9 @@ interface TangleState { zoom: number; setZoom: (zoom: number) => void; + forcedZoom: number | undefined; + setForcedZoom: (zoom: number | undefined) => void; + bps: number; setBps: (bps: number) => void; @@ -88,6 +91,7 @@ const INITIAL_STATE = { blockIdToAnimationPosition: new Map(), indexToBlockId: [], zoom: ZOOM_DEFAULT, + forcedZoom: undefined, bps: 0, clickedInstanceId: null, confirmedBlocksBySlot: new Map(), @@ -207,6 +211,12 @@ export const useTangleStore = create()( zoom, })); }, + setForcedZoom: (forcedZoom) => { + set((state) => ({ + ...state, + forcedZoom, + })); + }, setBps: (bps) => { set((state) => ({ ...state, diff --git a/client/src/features/visualizer-threejs/utils.ts b/client/src/features/visualizer-threejs/utils.ts index bb4dce083..5a7e66a7c 100644 --- a/client/src/features/visualizer-threejs/utils.ts +++ b/client/src/features/visualizer-threejs/utils.ts @@ -20,7 +20,7 @@ import { TILT_DURATION_SECONDS, } from "./constants"; import type { ICameraAngles, ISinusoidalPositionParams, IThreeDimensionalPosition } from "./interfaces"; -import { getFromLocalStorage } from "~features/visualizer-threejs/Controls"; +import { getFromLocalStorage } from "~features/visualizer-threejs/ConfigControls"; /** * Generates a random number within a specified range. diff --git a/client/src/features/visualizer-threejs/wrapper/Wrapper.tsx b/client/src/features/visualizer-threejs/wrapper/Wrapper.tsx index 65e60336c..b27284d97 100644 --- a/client/src/features/visualizer-threejs/wrapper/Wrapper.tsx +++ b/client/src/features/visualizer-threejs/wrapper/Wrapper.tsx @@ -5,7 +5,7 @@ import { INetwork } from "~/models/config/INetwork"; import KeyPanel from "./KeyPanel"; import mainHeader from "~assets/modals/visualizer/main-header.json"; import { SelectedFeedInfo } from "./SelectedFeedInfo"; -import { Controls } from "../Controls"; +import ConfigControls from "../ConfigControls"; export const Wrapper = ({ blocksCount, @@ -47,7 +47,7 @@ export const Wrapper = ({ - +
{children}
From d58ec0822dc642513a0cb74c7355eacf46c3031c Mon Sep 17 00:00:00 2001 From: Eugene Panteleymonchuk Date: Fri, 23 Feb 2024 17:50:11 +0200 Subject: [PATCH 14/22] fix: clean code. Signed-off-by: Eugene Panteleymonchuk --- client/src/features/visualizer-threejs/VisualizerInstance.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/features/visualizer-threejs/VisualizerInstance.tsx b/client/src/features/visualizer-threejs/VisualizerInstance.tsx index 3d8ed04c7..e79fcbc04 100644 --- a/client/src/features/visualizer-threejs/VisualizerInstance.tsx +++ b/client/src/features/visualizer-threejs/VisualizerInstance.tsx @@ -267,7 +267,7 @@ const VisualizerInstance: React.FC> = removeConfirmedBlocksSlot(slot); } - // const [z, setZ] = useState(1); + return ( Date: Fri, 23 Feb 2024 21:11:48 +0100 Subject: [PATCH 15/22] fix: spray shape --- .../visualizer-threejs/VisualizerInstance.tsx | 8 +- .../visualizer-threejs/blockPositions.ts | 28 ++--- .../features/visualizer-threejs/constants.ts | 2 +- .../features/visualizer-threejs/interfaces.ts | 5 + .../visualizer-threejs/useRenderTangle.tsx | 79 ++++++++----- .../src/features/visualizer-threejs/utils.ts | 107 ++++++++---------- 6 files changed, 121 insertions(+), 108 deletions(-) diff --git a/client/src/features/visualizer-threejs/VisualizerInstance.tsx b/client/src/features/visualizer-threejs/VisualizerInstance.tsx index f7a26d2e3..ff59a791d 100644 --- a/client/src/features/visualizer-threejs/VisualizerInstance.tsx +++ b/client/src/features/visualizer-threejs/VisualizerInstance.tsx @@ -24,14 +24,13 @@ import { Wrapper } from "./wrapper/Wrapper"; import { CanvasElement } from "./enums"; import { useGetThemeMode } from "~/helpers/hooks/useGetThemeMode"; import { TSelectFeedItemNova } from "~/app/types/visualizer.types"; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import { BasicBlockBody, Utils, type IBlockMetadata, type BlockState, type SlotIndex, type BlockId } from "@iota/sdk-wasm-nova/web"; +import { BasicBlockBody, Utils, type IBlockMetadata, type BlockState, type SlotIndex } 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"; import { getCurrentTiltValue } from "./utils"; +import "./Visualizer.scss"; const features = { statsEnabled: false, @@ -230,11 +229,10 @@ const VisualizerInstance: React.FC> = if (blockWeakParents.length > 0) { addToEdgeQueue(blockData.blockId, blockWeakParents); } - addBlock({ id: blockData.blockId, color: PENDING_BLOCK_COLOR, - blockAddedTimestamp: getCurrentAnimationTime(), + blockAddedTimestamp: currentAnimationTime, targetPosition, initPosition, }); diff --git a/client/src/features/visualizer-threejs/blockPositions.ts b/client/src/features/visualizer-threejs/blockPositions.ts index 9a4147a0d..7f398e038 100644 --- a/client/src/features/visualizer-threejs/blockPositions.ts +++ b/client/src/features/visualizer-threejs/blockPositions.ts @@ -1,20 +1,22 @@ -import { SPRAY_DISTANCE } from "./constants"; -import { ISinusoidalPositionParams } from "./interfaces"; -import { getEmitterPositions, getGenerateDynamicYZPosition, getTangleDistances } from "./utils"; +import { ISinusoidalPositionParams, IThreeDimensionalPosition } from "./interfaces"; +import { getEmitterPositions, getTangleDistances, getBlockPositionGenerator } from "./utils"; -const generateYZPositions = getGenerateDynamicYZPosition(); +const generateBlockTargetPosition = getBlockPositionGenerator(); -interface IPos { - x: number; - y: number; - z: number; -} -export function getBlockTargetPosition(initPosition: IPos, bps: number, tiltDegress: number): IPos { - const { y, z } = generateYZPositions(bps, initPosition, tiltDegress); - return { x: initPosition.x - SPRAY_DISTANCE, y, z }; +export function getBlockTargetPosition( + initPosition: IThreeDimensionalPosition, + bps: number, + tiltDegress: number, +): IThreeDimensionalPosition { + return generateBlockTargetPosition(bps, initPosition, tiltDegress); } -export function getBlockInitPosition({ currentAnimationTime, periods, periodsSum, sinusoidAmplitudes }: ISinusoidalPositionParams): IPos { +export function getBlockInitPosition({ + currentAnimationTime, + periods, + periodsSum, + sinusoidAmplitudes, +}: ISinusoidalPositionParams): IThreeDimensionalPosition { const { xTangleDistance } = getTangleDistances(); const { x: xEmitterPos, y, z } = getEmitterPositions({ currentAnimationTime, periods, periodsSum, sinusoidAmplitudes }); const x = xEmitterPos + xTangleDistance / 2; diff --git a/client/src/features/visualizer-threejs/constants.ts b/client/src/features/visualizer-threejs/constants.ts index a0aa310b1..6e78c4962 100644 --- a/client/src/features/visualizer-threejs/constants.ts +++ b/client/src/features/visualizer-threejs/constants.ts @@ -82,7 +82,7 @@ export const MIN_BLOCK_NEAR_RADIUS = 20; export const MAX_POINT_RETRIES = 10; export const MAX_PREV_POINTS = 20; -export const SPRAY_DISTANCE = 500; +export const SPRAY_DISTANCE = 400; export const SPRAY_ANIMATION_DURATION = SPRAY_DISTANCE / EMITTER_SPEED_MULTIPLIER; /* Values for randomizing the tangle */ diff --git a/client/src/features/visualizer-threejs/interfaces.ts b/client/src/features/visualizer-threejs/interfaces.ts index afe4af6d0..732fce9b1 100644 --- a/client/src/features/visualizer-threejs/interfaces.ts +++ b/client/src/features/visualizer-threejs/interfaces.ts @@ -5,6 +5,11 @@ export interface ICameraAngles { maxAzimuthAngle: number; } +export interface ITwoDimensionalPosition { + x: number; + y: number; +} + export interface IThreeDimensionalPosition { x: number; y: number; diff --git a/client/src/features/visualizer-threejs/useRenderTangle.tsx b/client/src/features/visualizer-threejs/useRenderTangle.tsx index 2297a1c51..4da8ff476 100644 --- a/client/src/features/visualizer-threejs/useRenderTangle.tsx +++ b/client/src/features/visualizer-threejs/useRenderTangle.tsx @@ -1,5 +1,5 @@ -import { useFrame, useThree } from "@react-three/fiber"; -import { useEffect, useRef } from "react"; +import { useThree } from "@react-three/fiber"; +import { useEffect, useRef, useState } from "react"; import * as THREE from "three"; import { SPRAY_ANIMATION_DURATION, MAX_BLOCK_INSTANCES, NODE_SIZE_DEFAULT } from "./constants"; import { useMouseMove } from "./hooks/useMouseMove"; @@ -15,7 +15,8 @@ const INITIAL_SPHERE_SCALE = 0.7; export const useRenderTangle = () => { const tangleMeshRef = useRef(new THREE.InstancedMesh(SPHERE_GEOMETRY, SPHERE_MATERIAL, MAX_BLOCK_INSTANCES)); - const objectIndexRef = useRef(0); + const [updateAnimationPositionQueue, setUpdateAnimationPositionQueue] = useState>(new Map()); + const objectIndexRef = useRef(1); const { scene } = useThree(); const isPlaying = useConfigStore((s) => s.isPlaying); @@ -28,6 +29,8 @@ export const useRenderTangle = () => { const blockIdToIndex = useTangleStore((s) => s.blockIdToIndex); const updateBlockIdToIndex = useTangleStore((s) => s.updateBlockIdToIndex); const blockIdToAnimationPosition = useTangleStore((s) => s.blockIdToAnimationPosition); + const updateBlockIdToAnimationPosition = useTangleStore((s) => s.updateBlockIdToAnimationPosition); + const getVisualizerTimeDiff = useVisualizerTimer(); const assignBlockToMesh = (block: IBlockState) => { @@ -42,10 +45,10 @@ export const useRenderTangle = () => { // Reuses old indexes when MAX_INSTANCES is reached // This also makes it so that old nodes are removed - if (objectIndexRef.current < MAX_BLOCK_INSTANCES - 1) { + if (objectIndexRef.current < MAX_BLOCK_INSTANCES) { objectIndexRef.current += 1; } else { - objectIndexRef.current = 0; + objectIndexRef.current = 1; } return block.id; @@ -146,33 +149,49 @@ export const useRenderTangle = () => { /** * Spray animation */ - useFrame(() => { - const isPlaying = useConfigStore.getState().isPlaying; - - if (!isPlaying) { - return; - } - - const blockIdToAnimationPosition = useTangleStore.getState().blockIdToAnimationPosition; - const updateBlockIdToAnimationPosition = useTangleStore.getState().updateBlockIdToAnimationPosition; - + useEffect(() => { const updatedAnimationPositions: Map = new Map(); + const updateAnimationPositionQueue: Map = new Map(); + const SPRAY_FRAMES_PER_SECOND = 24; + + const interval = setInterval(() => { + blockIdToAnimationPosition.forEach((properties, blockId) => { + const { initPosition, targetPosition, blockAddedTimestamp } = properties; + const currentAnimationTime = getVisualizerTimeDiff(); + const elapsedTime = currentAnimationTime - blockAddedTimestamp; + const animationAlpha = Math.min(elapsedTime / SPRAY_ANIMATION_DURATION, 1); + const targetPositionVector = new THREE.Vector3(); + + targetPositionVector.lerpVectors(positionToVector(initPosition), positionToVector(targetPosition), animationAlpha); + updatedAnimationPositions.set(blockId, { initPosition, elapsedTime, targetPosition, blockAddedTimestamp }); + + const index = blockIdToIndex.get(blockId); + if (index) { + if (isPlaying) { + updateInstancedMeshPosition(tangleMeshRef.current, index, targetPositionVector); + } else { + updateAnimationPositionQueue.set(index, targetPositionVector); + } + } + }); + }, 1000 / SPRAY_FRAMES_PER_SECOND); - blockIdToAnimationPosition.forEach(({ initPosition, targetPosition, blockAddedTimestamp }, blockId) => { - const currentAnimationTime = getVisualizerTimeDiff(); - const elapsedTime = currentAnimationTime - blockAddedTimestamp; - const animationAlpha = Math.min(elapsedTime / SPRAY_ANIMATION_DURATION, 1); - const targetPositionVector = new THREE.Vector3(); - - targetPositionVector.lerpVectors(positionToVector(initPosition), positionToVector(targetPosition), animationAlpha); - updatedAnimationPositions.set(blockId, { initPosition, elapsedTime, targetPosition, blockAddedTimestamp }); + updateBlockIdToAnimationPosition(updatedAnimationPositions); + setUpdateAnimationPositionQueue(updateAnimationPositionQueue); - const index = blockIdToIndex.get(blockId); - if (index) { - updateInstancedMeshPosition(tangleMeshRef.current, index, targetPositionVector); - } - }); + return () => clearInterval(interval); + }, [blockIdToAnimationPosition, isPlaying]); - updateBlockIdToAnimationPosition(updatedAnimationPositions); - }); + /** + * Update animation positions after unpausing + */ + useEffect(() => { + if (isPlaying) { + updateAnimationPositionQueue.forEach((position, index) => { + updateInstancedMeshPosition(tangleMeshRef.current, index, position); + }); + updateAnimationPositionQueue.clear(); + setUpdateAnimationPositionQueue(updateAnimationPositionQueue); + } + }, [isPlaying, updateAnimationPositionQueue]); }; diff --git a/client/src/features/visualizer-threejs/utils.ts b/client/src/features/visualizer-threejs/utils.ts index 4739b288f..a77a28edd 100644 --- a/client/src/features/visualizer-threejs/utils.ts +++ b/client/src/features/visualizer-threejs/utils.ts @@ -1,13 +1,9 @@ -import { Vector3 } from "three"; +import { Vector3, MathUtils } from "three"; import { - BLOCK_STEP_PX, MIN_BLOCKS_PER_SECOND, MAX_BLOCKS_PER_SECOND, MIN_TANGLE_RADIUS, MAX_TANGLE_RADIUS, - MIN_BLOCK_NEAR_RADIUS, - MAX_PREV_POINTS, - MAX_POINT_RETRIES, MAX_BLOCK_INSTANCES, EMITTER_SPEED_MULTIPLIER, CAMERA_X_AXIS_MOVEMENT, @@ -21,11 +17,15 @@ import { MIN_SINUSOID_AMPLITUDE, MAX_SINUSOID_AMPLITUDE, NUMBER_OF_RANDOM_TILTINGS, + TILT_DURATION_SECONDS, + SPRAY_DISTANCE, + MAX_PREV_POINTS, + MAX_POINT_RETRIES, + MIN_BLOCK_NEAR_RADIUS, MIN_TILT_FACTOR_DEGREES, MAX_TILT_FACTOR_DEGREES, - TILT_DURATION_SECONDS, } from "./constants"; -import type { ICameraAngles, ISinusoidalPositionParams, IThreeDimensionalPosition } from "./interfaces"; +import type { ICameraAngles, ISinusoidalPositionParams, IThreeDimensionalPosition, ITwoDimensionalPosition } from "./interfaces"; /** * Generates a random number within a specified range. @@ -73,16 +73,6 @@ interface IBlockTanglePosition { z: number; } -/** - * Calculates the distance between two points. - * @returns the distance between two points. - */ -function distanceBetweenPoints(point1: IBlockTanglePosition, point2: IBlockTanglePosition): number { - const { z: z1, y: y1 } = point1; - const { z: z2, y: y2 } = point2; - return Math.sqrt((y2 - y1) ** 2 + (z2 - z1) ** 2); -} - /** * Calculates the radius of the circle based on the blocks per second. * @returns the radius of the circle. @@ -98,32 +88,8 @@ function getLinearRadius(bps: number): number { return radius; } -/** - * Generates a random point on a circle. - * @returns the random point on a circle. - */ -function getDynamicRandomYZPoints( - bps: number, - initialPosition: IThreeDimensionalPosition = { - x: 0, - y: 0, - z: 0, - }, - tiltDegrees: number, -): IBlockTanglePosition { - const theta = Math.random() * (2 * Math.PI); - - const maxRadius = getLinearRadius(bps); - const randomFactor = Math.random(); - const radius = randomFactor * maxRadius; - - let y = radius * Math.cos(theta) + initialPosition.y; - const z = radius * Math.sin(theta) + initialPosition.z; - - const tiltRadians = tiltDegrees * (Math.PI / 180); - y += Math.tan(tiltRadians) * radius; - - return { y, z }; +function distanceBetweenPoints(point1: IBlockTanglePosition, point2: IBlockTanglePosition): number { + return Math.sqrt(Math.pow(point1.y - point2.y, 2) + Math.pow(point1.z - point2.z, 2)); } /** @@ -138,6 +104,20 @@ function pointPassesAllChecks(point: IBlockTanglePosition, prevPoints: IBlockTan return prevPoints.some((prevPoint) => distanceBetweenPoints(point, prevPoint) > MIN_BLOCK_NEAR_RADIUS); } +export function getBlockPositionGenerator(): ( + bps: number, + initialPosition: IThreeDimensionalPosition, + tiltDegress: number, +) => IThreeDimensionalPosition { + const prevPoints: IBlockTanglePosition[] = []; + + return (bps: number, initialPosition: IThreeDimensionalPosition, tiltDegress: number) => { + const point = generateAValidRandomPoint(bps, initialPosition, prevPoints, tiltDegress); + prevPoints.push({ y: point.y, z: point.z }); + return point; + }; +} + /** * Retries to generate a point until it passes all the checks. * @returns the point that passes all the checks. @@ -147,18 +127,19 @@ function generateAValidRandomPoint( initialPosition: IThreeDimensionalPosition, prevPoints: IBlockTanglePosition[], tiltDegress: number, -): IBlockTanglePosition { - let trialPoint: IBlockTanglePosition; +): IThreeDimensionalPosition { + let trialPoint: IThreeDimensionalPosition; let passAllChecks = false; let retries = 0; do { - trialPoint = getDynamicRandomYZPoints(bps, initialPosition, tiltDegress); + trialPoint = generateRandomXYZPoints(bps, initialPosition, tiltDegress); passAllChecks = pointPassesAllChecks(trialPoint, prevPoints); retries++; } while (!passAllChecks && retries < MAX_POINT_RETRIES); prevPoints.push(trialPoint); + if (prevPoints.length > MAX_PREV_POINTS) { prevPoints.shift(); } @@ -167,23 +148,30 @@ function generateAValidRandomPoint( } /** - * Gets a function to generate a random point on a circle. - * @returns the function to generate the random point on a circle. + * Generates a random point on a circle. + * @returns the random point on a circle. */ -export function getGenerateDynamicYZPosition(): typeof getDynamicRandomYZPoints { - const prevPoints: IBlockTanglePosition[] = []; - - return (bps: number, initialPosition: IThreeDimensionalPosition = { x: 0, y: 0, z: 0 }, tiltDegress): IBlockTanglePosition => { - const validPoint = generateAValidRandomPoint(bps, initialPosition, prevPoints, tiltDegress); +export function generateRandomXYZPoints( + bps: number, + initialPosition: IThreeDimensionalPosition, + tiltDegrees: number, +): IThreeDimensionalPosition { + const tiltRad = MathUtils.degToRad(-tiltDegrees); + const opposite = SPRAY_DISTANCE * Math.sin(tiltRad); + const adjacent = SPRAY_DISTANCE * Math.cos(tiltRad); + const circumferenceCenter: ITwoDimensionalPosition = { + x: initialPosition.x - adjacent, + y: initialPosition.y + opposite, + }; - const randomYNumber = randomNumberFromInterval(0, BLOCK_STEP_PX / 20); - const randomZNumber = randomNumberFromInterval(0, BLOCK_STEP_PX / 20); + const _radius = getLinearRadius(bps); + const randomFactor = Math.random(); + const radius = _radius * randomFactor; - validPoint.y += randomYNumber; - validPoint.z += randomZNumber; + const y = circumferenceCenter.y + radius * Math.cos(radius); + const z = initialPosition.z + radius * Math.sin(radius); - return validPoint; - }; + return { x: circumferenceCenter.x, y, z }; } /** @@ -352,6 +340,7 @@ export function generateRandomAmplitudes(): number[] { currentAmplitude = getNextAmplitudeWithVariation(currentAmplitude); amplitudes.push(currentAmplitude); } + return amplitudes; } From 79214b7ac6bbba7313a11a96a92a7f884961eaf7 Mon Sep 17 00:00:00 2001 From: Eugene P Date: Mon, 26 Feb 2024 14:20:22 +0200 Subject: [PATCH 16/22] feat: commit suggestion for client/src/features/visualizer-threejs/ConfigControls.tsx Co-authored-by: JCNoguera <88061365+VmMad@users.noreply.github.com> --- .../visualizer-threejs/ConfigControls.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/client/src/features/visualizer-threejs/ConfigControls.tsx b/client/src/features/visualizer-threejs/ConfigControls.tsx index 3a983647c..eea0739fe 100644 --- a/client/src/features/visualizer-threejs/ConfigControls.tsx +++ b/client/src/features/visualizer-threejs/ConfigControls.tsx @@ -12,14 +12,14 @@ import { import "./Controls.scss"; import { useTangleStore } from "~features/visualizer-threejs/store"; -interface IControlsVisualiser { - MIN_SINUSOID_PERIOD: number; - MAX_SINUSOID_PERIOD: number; - MIN_SINUSOID_AMPLITUDE: number; - MAX_SINUSOID_AMPLITUDE: number; - MIN_TILT_FACTOR_DEGREES: number; - MAX_TILT_FACTOR_DEGREES: number; - TILT_DURATION_SECONDS: number; +enum VisualizerInput { + MinSinusoidPeriod = 'minSinusoidPeriod', + MaxSinusoidPeriod = 'maxSinusoidPeriod', + MinSinusiudAmplitude = 'minSinusiudAmplitude', + MaxSinusoidAmplitude = 'maxSinusoidAmplitude', + MinTiltDegrees = 'minTiltDegrees', + MaxTiltDegrees = 'maxTiltDegrees', + TiltDurationSeconds = 'tiltDurationSeconds' } const defaultControlsVisualiser: IControlsVisualiser = { From d5947d47e7f4e0b59ad9321cd5c433e0504852f6 Mon Sep 17 00:00:00 2001 From: Eugene P Date: Mon, 26 Feb 2024 14:21:10 +0200 Subject: [PATCH 17/22] feat: commit suggestion for client/src/features/visualizer-threejs/ConfigControls.tsx Co-authored-by: JCNoguera <88061365+VmMad@users.noreply.github.com> --- client/src/features/visualizer-threejs/ConfigControls.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/features/visualizer-threejs/ConfigControls.tsx b/client/src/features/visualizer-threejs/ConfigControls.tsx index eea0739fe..aa731dba9 100644 --- a/client/src/features/visualizer-threejs/ConfigControls.tsx +++ b/client/src/features/visualizer-threejs/ConfigControls.tsx @@ -22,7 +22,7 @@ enum VisualizerInput { TiltDurationSeconds = 'tiltDurationSeconds' } -const defaultControlsVisualiser: IControlsVisualiser = { +const DEFAULT_VISUALIZER_CONTROLS: Record = { MIN_SINUSOID_PERIOD: MIN_SINUSOID_PERIOD, MAX_SINUSOID_PERIOD: MAX_SINUSOID_PERIOD, MIN_SINUSOID_AMPLITUDE: MIN_SINUSOID_AMPLITUDE, From e04a744728c3fe258c6b6bfbba81f402c87d492d Mon Sep 17 00:00:00 2001 From: Eugene Panteleymonchuk Date: Mon, 26 Feb 2024 15:17:11 +0200 Subject: [PATCH 18/22] fix: review comments Signed-off-by: Eugene Panteleymonchuk --- .../{Controls.scss => ConfigControls.scss} | 9 +- .../visualizer-threejs/ConfigControls.tsx | 167 +++++++++--------- .../src/features/visualizer-threejs/utils.ts | 24 +-- 3 files changed, 108 insertions(+), 92 deletions(-) rename client/src/features/visualizer-threejs/{Controls.scss => ConfigControls.scss} (69%) diff --git a/client/src/features/visualizer-threejs/Controls.scss b/client/src/features/visualizer-threejs/ConfigControls.scss similarity index 69% rename from client/src/features/visualizer-threejs/Controls.scss rename to client/src/features/visualizer-threejs/ConfigControls.scss index 043c45b49..553f41d25 100644 --- a/client/src/features/visualizer-threejs/Controls.scss +++ b/client/src/features/visualizer-threejs/ConfigControls.scss @@ -1,4 +1,5 @@ .controls-container { + //background-color: transparent; background: var(--body-background); border: 1px solid var(--border-color); padding: 8px 16px; @@ -9,9 +10,10 @@ } .controls__item { flex: 1; + display: flex; + flex-direction: column; input { - padding: 8px 16px; width: 100%; } } @@ -20,4 +22,9 @@ display: flex; gap: 8px; } + + input { + background: var(--body-background); + padding: 8px 16px; + } } diff --git a/client/src/features/visualizer-threejs/ConfigControls.tsx b/client/src/features/visualizer-threejs/ConfigControls.tsx index aa731dba9..ad6fa43a9 100644 --- a/client/src/features/visualizer-threejs/ConfigControls.tsx +++ b/client/src/features/visualizer-threejs/ConfigControls.tsx @@ -9,57 +9,56 @@ import { TILT_DURATION_SECONDS, features, } from "./constants"; -import "./Controls.scss"; import { useTangleStore } from "~features/visualizer-threejs/store"; - -enum VisualizerInput { - MinSinusoidPeriod = 'minSinusoidPeriod', - MaxSinusoidPeriod = 'maxSinusoidPeriod', - MinSinusiudAmplitude = 'minSinusiudAmplitude', - MaxSinusoidAmplitude = 'maxSinusoidAmplitude', - MinTiltDegrees = 'minTiltDegrees', - MaxTiltDegrees = 'maxTiltDegrees', - TiltDurationSeconds = 'tiltDurationSeconds' +import "./ConfigControls.scss"; + +enum VisualizerConfig { + MinSinusoidPeriod = "minSinusoidPeriod", + MaxSinusoidPeriod = "maxSinusoidPeriod", + MinSinusoidAmplitude = "minSinusoidAmplitude", + MaxSinusoidAmplitude = "maxSinusoidAmplitude", + MinTiltDegrees = "minTiltDegrees", + MaxTiltDegrees = "maxTiltDegrees", + TiltDurationSeconds = "tiltDurationSeconds", } -const DEFAULT_VISUALIZER_CONTROLS: Record = { - MIN_SINUSOID_PERIOD: MIN_SINUSOID_PERIOD, - MAX_SINUSOID_PERIOD: MAX_SINUSOID_PERIOD, - MIN_SINUSOID_AMPLITUDE: MIN_SINUSOID_AMPLITUDE, - MAX_SINUSOID_AMPLITUDE: MAX_SINUSOID_AMPLITUDE, - MIN_TILT_FACTOR_DEGREES: MIN_TILT_FACTOR_DEGREES, - MAX_TILT_FACTOR_DEGREES: MAX_TILT_FACTOR_DEGREES, - TILT_DURATION_SECONDS: TILT_DURATION_SECONDS, -}; +const VISUALIZER_CONFIG_LOCAL_STORAGE_KEY = "visualizerConfigs"; -type TKey = keyof IControlsVisualiser; +const DEFAULT_VISUALIZER_CONFIG_VALUES: Record = { + [VisualizerConfig.MinSinusoidPeriod]: MIN_SINUSOID_PERIOD, + [VisualizerConfig.MaxSinusoidPeriod]: MAX_SINUSOID_PERIOD, + [VisualizerConfig.MinSinusoidAmplitude]: MIN_SINUSOID_AMPLITUDE, + [VisualizerConfig.MaxSinusoidAmplitude]: MAX_SINUSOID_AMPLITUDE, + [VisualizerConfig.MinTiltDegrees]: MIN_TILT_FACTOR_DEGREES, + [VisualizerConfig.MaxTiltDegrees]: MAX_TILT_FACTOR_DEGREES, + [VisualizerConfig.TiltDurationSeconds]: TILT_DURATION_SECONDS, +}; /** * Retrieves a value from localStorage and parses it as JSON. */ -const LOCAL_STORAGE_KEY = "controlsVisualiser"; - -export const getFromLocalStorage = (): IControlsVisualiser => { +export const getVisualizerConfigValues = (): Record => { if (features.controlsVisualiserEnabled) { - const item = localStorage.getItem(LOCAL_STORAGE_KEY); - return item ? JSON.parse(item) : defaultControlsVisualiser; + const item = localStorage.getItem(VISUALIZER_CONFIG_LOCAL_STORAGE_KEY); + return item ? JSON.parse(item) : DEFAULT_VISUALIZER_CONFIG_VALUES; } else { - localStorage.removeItem(LOCAL_STORAGE_KEY); - return defaultControlsVisualiser; + localStorage.removeItem(VISUALIZER_CONFIG_LOCAL_STORAGE_KEY); + return DEFAULT_VISUALIZER_CONFIG_VALUES; } }; /** * Saves a value to localStorage as a JSON string. */ -function setToLocalStorage(value: IControlsVisualiser) { - localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(value)); +function setToLocalStorage(value: Record) { + localStorage.setItem(VISUALIZER_CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(value)); } /** + * Checks if config for visualizer inputs exists in localStorage. */ -function isExistsInLocalStorage(): boolean { - return !!localStorage.getItem(LOCAL_STORAGE_KEY); +function controlsExistInLocalStorage(): boolean { + return !!localStorage.getItem(VISUALIZER_CONFIG_LOCAL_STORAGE_KEY); } export const ConfigControls = () => { @@ -67,12 +66,11 @@ export const ConfigControls = () => { const setForcedZoom = useTangleStore((state) => state.setForcedZoom); const [localZoom, setLocalZoom] = useState(forcedZoom); - const [state, setState] = useState(() => { - // Use getFromLocalStorage to retrieve the state - return getFromLocalStorage() || defaultControlsVisualiser; + const [visualizerConfigValues, setVisualizerConfigValues] = useState>(() => { + return getVisualizerConfigValues() || DEFAULT_VISUALIZER_CONFIG_VALUES; // Use getFromLocalStorage to retrieve the state }); - const [isShowReset, setIsShowReset] = useState(() => { - return isExistsInLocalStorage(); + const [showResetButton, setShowResetButton] = useState(() => { + return controlsExistInLocalStorage(); }); const [errors, setErrors] = useState<{ @@ -80,52 +78,52 @@ export const ConfigControls = () => { }>({}); const inputs: { - key: TKey; + key: VisualizerConfig; label: string; min: number; max: number; }[] = [ { - key: "MIN_SINUSOID_PERIOD", + key: VisualizerConfig.MinSinusoidPeriod, label: "Min sinusoid period", min: 1, max: 7, }, { - key: "MAX_SINUSOID_PERIOD", + key: VisualizerConfig.MaxSinusoidPeriod, label: "Max sinusoid period", min: 8, max: 15, }, { - key: "MIN_SINUSOID_AMPLITUDE", + key: VisualizerConfig.MinSinusoidAmplitude, label: "Min sinusoid amplitude", - min: 100, + min: 50, max: 199, }, { - key: "MAX_SINUSOID_AMPLITUDE", + key: VisualizerConfig.MaxSinusoidAmplitude, label: "Max sinusoid amplitude", min: 200, max: 500, }, { - key: "MIN_TILT_FACTOR_DEGREES", + key: VisualizerConfig.MinTiltDegrees, label: "Min tilt factor degrees", - min: 1, - max: 15, + min: 0, + max: 90, }, { - key: "MAX_TILT_FACTOR_DEGREES", + key: VisualizerConfig.MaxTiltDegrees, label: "Max tilt factor degrees", - min: 16, - max: 100, + min: 0, + max: 90, }, { - key: "TILT_DURATION_SECONDS", - label: "Tilt_duration_seconds", + key: VisualizerConfig.TiltDurationSeconds, + label: "Tilt duration (seconds)", min: 1, - max: 10, + max: 100, }, ]; @@ -136,17 +134,17 @@ export const ConfigControls = () => { return; } - setToLocalStorage(state); + setToLocalStorage(visualizerConfigValues); location.reload(); }; - const handleChange = (key: TKey, val: string) => { + const handleChange = (key: VisualizerConfig, val: string) => { const input = inputs.find((input) => input.key === key); if (!input) return; if (!val) { setErrors((prevErrors) => ({ ...prevErrors, [key]: "Value is required" })); - setState((prevState) => ({ ...prevState, [key]: "" })); + setVisualizerConfigValues((prevState) => ({ ...prevState, [key]: "" })); return; } @@ -157,7 +155,7 @@ export const ConfigControls = () => { setErrors((prevErrors) => ({ ...prevErrors, [key]: "" })); } - setState((prevState) => ({ ...prevState, [key]: numericValue })); + setVisualizerConfigValues((prevState) => ({ ...prevState, [key]: numericValue })); }; if (!features.controlsVisualiserEnabled) { @@ -170,16 +168,13 @@ export const ConfigControls = () => { {inputs.map((i) => { return (
-
- - handleChange(i.key, e.target.value)} /> - {!!errors[i.key] &&
{errors[i.key]}
} -
+ + handleChange(i.key, e.target.value)} + /> + {!!errors[i.key] &&
{errors[i.key]}
}
); })} @@ -189,12 +184,11 @@ export const ConfigControls = () => { - {isShowReset && ( + {showResetButton && (
diff --git a/client/src/features/visualizer-threejs/utils.ts b/client/src/features/visualizer-threejs/utils.ts index 5a7e66a7c..b8faaba7e 100644 --- a/client/src/features/visualizer-threejs/utils.ts +++ b/client/src/features/visualizer-threejs/utils.ts @@ -20,7 +20,7 @@ import { TILT_DURATION_SECONDS, } from "./constants"; import type { ICameraAngles, ISinusoidalPositionParams, IThreeDimensionalPosition } from "./interfaces"; -import { getFromLocalStorage } from "~features/visualizer-threejs/ConfigControls"; +import { getVisualizerConfigValues } from "~features/visualizer-threejs/ConfigControls"; /** * Generates a random number within a specified range. @@ -189,7 +189,7 @@ export function getTangleDistances(): { xTangleDistance: number; yTangleDistance: number; } { - const { MAX_SINUSOID_AMPLITUDE } = getFromLocalStorage(); + const { maxSinusoidAmplitude } = getVisualizerConfigValues(); /* We assume MAX BPS to get the max possible Y */ const MAX_TANGLE_DISTANCE_SECONDS = MAX_BLOCK_INSTANCES / MIN_BLOCKS_PER_SECOND; @@ -199,7 +199,7 @@ export function getTangleDistances(): { const maxXDistance = MAX_BLOCK_DISTANCE; /* Max Y Distance will be multiplied by 2 to position blocks in the negative and positive Y axis */ - const maxYDistance = MAX_TANGLE_RADIUS * 2 + MAX_SINUSOID_AMPLITUDE * 2; + const maxYDistance = MAX_TANGLE_RADIUS * 2 + maxSinusoidAmplitude * 2; /* TODO: add sinusoidal distances */ @@ -296,8 +296,8 @@ export function positionToVector(position: IThreeDimensionalPosition) { export function generateRandomPeriods(): { periods: number[]; sum: number } { let sum = 0; const periods = Array.from({ length: NUMBER_OF_RANDOM_PERIODS }, () => { - const { MIN_SINUSOID_PERIOD, MAX_SINUSOID_PERIOD } = getFromLocalStorage(); - const period = Number(randomNumberFromInterval(MIN_SINUSOID_PERIOD, MAX_SINUSOID_PERIOD).toFixed(4)); + const { minSinusoidPeriod, maxSinusoidPeriod } = getVisualizerConfigValues(); + const period = Number(randomNumberFromInterval(minSinusoidPeriod, maxSinusoidPeriod).toFixed(4)); sum += period; return period; }); @@ -327,19 +327,19 @@ function getCurrentPeriodValues(animationTime: number, periods: number[], totalS } function getNextAmplitudeWithVariation(currentAmplitude: number = 0): number { - const { MIN_SINUSOID_AMPLITUDE, MAX_SINUSOID_AMPLITUDE } = getFromLocalStorage(); - const variation = (2 * MIN_SINUSOID_AMPLITUDE) / 3; + const { minSinusoidAmplitude, maxSinusoidAmplitude } = getVisualizerConfigValues(); + const variation = (2 * minSinusoidAmplitude) / 3; const randomAmplitudeVariation = randomNumberFromInterval(-variation, variation); let newAmplitude = currentAmplitude + randomAmplitudeVariation; - if (newAmplitude > MAX_SINUSOID_AMPLITUDE) { + if (newAmplitude > maxSinusoidAmplitude) { newAmplitude = currentAmplitude - Math.abs(randomAmplitudeVariation); - } else if (newAmplitude < MIN_SINUSOID_AMPLITUDE) { + } else if (newAmplitude < minSinusoidAmplitude) { newAmplitude = currentAmplitude + Math.abs(randomAmplitudeVariation); } - newAmplitude = Math.max(MIN_SINUSOID_AMPLITUDE, Math.min(newAmplitude, MAX_SINUSOID_AMPLITUDE)); + newAmplitude = Math.max(minSinusoidAmplitude, Math.min(newAmplitude, maxSinusoidAmplitude)); return newAmplitude; } @@ -356,10 +356,10 @@ export function generateRandomAmplitudes(): number[] { export function generateRandomTiltings(): number[] { let previousValue: number; - const { MIN_TILT_FACTOR_DEGREES, MAX_TILT_FACTOR_DEGREES } = getFromLocalStorage(); + const { minTiltDegrees, maxTiltDegrees } = getVisualizerConfigValues(); const tilts: number[] = Array.from({ length: NUMBER_OF_RANDOM_TILTINGS }, () => { - let randomTilt = randomIntFromInterval(MIN_TILT_FACTOR_DEGREES, MAX_TILT_FACTOR_DEGREES); + let randomTilt = randomIntFromInterval(minTiltDegrees, maxTiltDegrees); if ((previousValue < 0 && randomTilt < 0) || (previousValue > 0 && randomTilt > 0)) { randomTilt *= -1; From 561e6328471b4ac7c0d4e24c19cb9b6de16d1884 Mon Sep 17 00:00:00 2001 From: Eugene Panteleymonchuk Date: Mon, 26 Feb 2024 18:42:46 +0200 Subject: [PATCH 19/22] fix: add label color for dark mode. Signed-off-by: Eugene Panteleymonchuk --- .../visualizer-threejs/ConfigControls.scss | 18 +++++++++++++++++- .../visualizer-threejs/ConfigControls.tsx | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/client/src/features/visualizer-threejs/ConfigControls.scss b/client/src/features/visualizer-threejs/ConfigControls.scss index 553f41d25..44b1b5cf7 100644 --- a/client/src/features/visualizer-threejs/ConfigControls.scss +++ b/client/src/features/visualizer-threejs/ConfigControls.scss @@ -1,10 +1,22 @@ .controls-container { - //background-color: transparent; + font-family: + "Metropolis Regular", + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + Helvetica, + Arial, + sans-serif, + "Apple Color Emoji", + "Segoe UI Emoji", + "Segoe UI Symbol"; background: var(--body-background); border: 1px solid var(--border-color); padding: 8px 16px; border-radius: 8px; .controls__list { + color: var(--type-color); display: flex; gap: 8px; } @@ -17,6 +29,10 @@ width: 100%; } } + .controls__error { + font-size: 12px; + margin-top: 4px; + } .controls__actions { margin-top: 16px; display: flex; diff --git a/client/src/features/visualizer-threejs/ConfigControls.tsx b/client/src/features/visualizer-threejs/ConfigControls.tsx index ad6fa43a9..fa9b6b29b 100644 --- a/client/src/features/visualizer-threejs/ConfigControls.tsx +++ b/client/src/features/visualizer-threejs/ConfigControls.tsx @@ -174,7 +174,7 @@ export const ConfigControls = () => { value={visualizerConfigValues[i.key]} onChange={(e) => handleChange(i.key, e.target.value)} /> - {!!errors[i.key] &&
{errors[i.key]}
} + {!!errors[i.key] &&
{errors[i.key]}
}
); })} From 7b132b2f2e4edb13ab2345d3df138a2aec7d0805 Mon Sep 17 00:00:00 2001 From: Eugene Panteleymonchuk Date: Thu, 22 Feb 2024 23:21:24 +0200 Subject: [PATCH 20/22] feat: add emitterSpeedMultiplier Signed-off-by: Eugene Panteleymonchuk --- .../visualizer-threejs/ConfigControls.scss | 3 +- .../visualizer-threejs/ConfigControls.tsx | 87 +++++++++++-------- .../visualizer-threejs/store/tangle.ts | 8 +- .../visualizer-threejs/useRenderTangle.tsx | 5 +- .../src/features/visualizer-threejs/utils.ts | 8 +- 5 files changed, 67 insertions(+), 44 deletions(-) diff --git a/client/src/features/visualizer-threejs/ConfigControls.scss b/client/src/features/visualizer-threejs/ConfigControls.scss index 44b1b5cf7..07da9d94d 100644 --- a/client/src/features/visualizer-threejs/ConfigControls.scss +++ b/client/src/features/visualizer-threejs/ConfigControls.scss @@ -19,9 +19,10 @@ color: var(--type-color); display: flex; gap: 8px; + flex-wrap: wrap; } .controls__item { - flex: 1; + width: 20%; display: flex; flex-direction: column; diff --git a/client/src/features/visualizer-threejs/ConfigControls.tsx b/client/src/features/visualizer-threejs/ConfigControls.tsx index fa9b6b29b..9a8da0a83 100644 --- a/client/src/features/visualizer-threejs/ConfigControls.tsx +++ b/client/src/features/visualizer-threejs/ConfigControls.tsx @@ -7,6 +7,7 @@ import { MIN_TILT_FACTOR_DEGREES, MAX_TILT_FACTOR_DEGREES, TILT_DURATION_SECONDS, + EMITTER_SPEED_MULTIPLIER, features, } from "./constants"; import { useTangleStore } from "~features/visualizer-threejs/store"; @@ -20,6 +21,7 @@ enum VisualizerConfig { MinTiltDegrees = "minTiltDegrees", MaxTiltDegrees = "maxTiltDegrees", TiltDurationSeconds = "tiltDurationSeconds", + EmitterSpeedMultiplier = "emitterSpeedMultiplier", } const VISUALIZER_CONFIG_LOCAL_STORAGE_KEY = "visualizerConfigs"; @@ -32,6 +34,7 @@ const DEFAULT_VISUALIZER_CONFIG_VALUES: Record = { [VisualizerConfig.MinTiltDegrees]: MIN_TILT_FACTOR_DEGREES, [VisualizerConfig.MaxTiltDegrees]: MAX_TILT_FACTOR_DEGREES, [VisualizerConfig.TiltDurationSeconds]: TILT_DURATION_SECONDS, + [VisualizerConfig.EmitterSpeedMultiplier]: EMITTER_SPEED_MULTIPLIER, }; /** @@ -125,6 +128,12 @@ export const ConfigControls = () => { min: 1, max: 100, }, + { + key: VisualizerConfig.EmitterSpeedMultiplier, + label: "Emitter Speed Multiplier", + min: 0, + max: 1000, + }, ]; const handleApply = () => { @@ -197,45 +206,51 @@ export const ConfigControls = () => { )} -
- - { - const input = e.target.value; - - if (!input) { - setLocalZoom(undefined); - return; - } - - const numberRegExp = /^-?\d+(\.|\.\d*|\d*)?$/; - if (numberRegExp.test(input)) { - if (input.endsWith(".")) { - setLocalZoom(input as any); - } else { - const value = parseFloat(input); - if (value > 2) { - setLocalZoom(2); - return; - } - setLocalZoom(value); - } - } - }} - /> -
- + /> + {!!errors["zoom"] &&
{errors["zoom"]}
} +
+ +
diff --git a/client/src/features/visualizer-threejs/store/tangle.ts b/client/src/features/visualizer-threejs/store/tangle.ts index 1f47b446b..0438608f5 100644 --- a/client/src/features/visualizer-threejs/store/tangle.ts +++ b/client/src/features/visualizer-threejs/store/tangle.ts @@ -1,10 +1,11 @@ import { Color } from "three"; import { create } from "zustand"; import { devtools } from "zustand/middleware"; -import { ZOOM_DEFAULT, EMITTER_SPEED_MULTIPLIER, SPRAY_DISTANCE } from "../constants"; +import { ZOOM_DEFAULT, SPRAY_DISTANCE } from "../constants"; import { IFeedBlockData } from "~models/api/nova/feed/IFeedBlockData"; import { IThreeDimensionalPosition } from "../interfaces"; import { BlockId, SlotIndex } from "@iota/sdk-wasm-nova/web"; +import { getVisualizerConfigValues } from "~features/visualizer-threejs/ConfigControls"; export interface IBlockAnimationPosition { initPosition: IThreeDimensionalPosition; @@ -107,8 +108,11 @@ export const useTangleStore = create()( state.blockIdToAnimationPosition.set(key, value); }); + const { emitterSpeedMultiplier } = getVisualizerConfigValues(); + for (const [key, value] of state.blockIdToAnimationPosition) { - const animationTime = SPRAY_DISTANCE / EMITTER_SPEED_MULTIPLIER; + // const animationTime = SPRAY_DISTANCE / emitterSpeedMultiplier; + const animationTime = SPRAY_DISTANCE / emitterSpeedMultiplier; if (value.elapsedTime > animationTime) { state.blockIdToAnimationPosition.delete(key); } diff --git a/client/src/features/visualizer-threejs/useRenderTangle.tsx b/client/src/features/visualizer-threejs/useRenderTangle.tsx index 4da8ff476..a9d5c107e 100644 --- a/client/src/features/visualizer-threejs/useRenderTangle.tsx +++ b/client/src/features/visualizer-threejs/useRenderTangle.tsx @@ -1,12 +1,13 @@ import { useThree } from "@react-three/fiber"; import { useEffect, useRef, useState } from "react"; import * as THREE from "three"; -import { SPRAY_ANIMATION_DURATION, MAX_BLOCK_INSTANCES, NODE_SIZE_DEFAULT } from "./constants"; +import { MAX_BLOCK_INSTANCES, NODE_SIZE_DEFAULT, SPRAY_DISTANCE } from "./constants"; import { useMouseMove } from "./hooks/useMouseMove"; import { IBlockState, IBlockAnimationPosition, useConfigStore, useTangleStore } from "./store"; import { useRenderEdges } from "./useRenderEdges"; import useVisualizerTimer from "~/helpers/nova/hooks/useVisualizerTimer"; import { positionToVector } from "./utils"; +import { getVisualizerConfigValues } from "~features/visualizer-threejs/ConfigControls"; const SPHERE_GEOMETRY = new THREE.SphereGeometry(NODE_SIZE_DEFAULT, 32, 16); const SPHERE_MATERIAL = new THREE.MeshPhongMaterial(); @@ -155,10 +156,12 @@ export const useRenderTangle = () => { const SPRAY_FRAMES_PER_SECOND = 24; const interval = setInterval(() => { + const { emitterSpeedMultiplier } = getVisualizerConfigValues(); blockIdToAnimationPosition.forEach((properties, blockId) => { const { initPosition, targetPosition, blockAddedTimestamp } = properties; const currentAnimationTime = getVisualizerTimeDiff(); const elapsedTime = currentAnimationTime - blockAddedTimestamp; + const SPRAY_ANIMATION_DURATION = SPRAY_DISTANCE / emitterSpeedMultiplier; const animationAlpha = Math.min(elapsedTime / SPRAY_ANIMATION_DURATION, 1); const targetPositionVector = new THREE.Vector3(); diff --git a/client/src/features/visualizer-threejs/utils.ts b/client/src/features/visualizer-threejs/utils.ts index 146ee5116..e687a51e5 100644 --- a/client/src/features/visualizer-threejs/utils.ts +++ b/client/src/features/visualizer-threejs/utils.ts @@ -5,7 +5,6 @@ import { MIN_TANGLE_RADIUS, MAX_TANGLE_RADIUS, MAX_BLOCK_INSTANCES, - EMITTER_SPEED_MULTIPLIER, CAMERA_X_AXIS_MOVEMENT, CAMERA_Y_AXIS_MOVEMENT, CAMERA_X_OFFSET, @@ -177,12 +176,12 @@ export function getTangleDistances(): { xTangleDistance: number; yTangleDistance: number; } { - const { maxSinusoidAmplitude } = getVisualizerConfigValues(); + const { maxSinusoidAmplitude, emitterSpeedMultiplier } = getVisualizerConfigValues(); /* We assume MAX BPS to get the max possible Y */ const MAX_TANGLE_DISTANCE_SECONDS = MAX_BLOCK_INSTANCES / MIN_BLOCKS_PER_SECOND; - const MAX_BLOCK_DISTANCE = EMITTER_SPEED_MULTIPLIER * MAX_TANGLE_DISTANCE_SECONDS; + const MAX_BLOCK_DISTANCE = emitterSpeedMultiplier * MAX_TANGLE_DISTANCE_SECONDS; const maxXDistance = MAX_BLOCK_DISTANCE; @@ -254,7 +253,8 @@ export function calculateSinusoidalAmplitude({ * @returns the emitter position */ export function calculateEmitterPositionX(currentAnimationTime: number): number { - return currentAnimationTime * EMITTER_SPEED_MULTIPLIER; + const { emitterSpeedMultiplier } = getVisualizerConfigValues(); + return currentAnimationTime * emitterSpeedMultiplier; } /** From 7c322328176b897400f7a840d9a0745edf17cdf2 Mon Sep 17 00:00:00 2001 From: Eugene Panteleymonchuk Date: Tue, 27 Feb 2024 12:06:28 +0200 Subject: [PATCH 21/22] fix: change types. Signed-off-by: Eugene Panteleymonchuk --- .../features/visualizer-threejs/ConfigControls.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/client/src/features/visualizer-threejs/ConfigControls.tsx b/client/src/features/visualizer-threejs/ConfigControls.tsx index 9a8da0a83..59784fa44 100644 --- a/client/src/features/visualizer-threejs/ConfigControls.tsx +++ b/client/src/features/visualizer-threejs/ConfigControls.tsx @@ -67,7 +67,8 @@ function controlsExistInLocalStorage(): boolean { export const ConfigControls = () => { const forcedZoom = useTangleStore((state) => state.forcedZoom); const setForcedZoom = useTangleStore((state) => state.setForcedZoom); - const [localZoom, setLocalZoom] = useState(forcedZoom); + const forcedZoomInit = forcedZoom !== undefined ? String(forcedZoom) : forcedZoom; + const [localZoom, setLocalZoom] = useState(forcedZoomInit); const [visualizerConfigValues, setVisualizerConfigValues] = useState>(() => { return getVisualizerConfigValues() || DEFAULT_VISUALIZER_CONFIG_VALUES; // Use getFromLocalStorage to retrieve the state @@ -223,16 +224,16 @@ export const ConfigControls = () => { const numberRegExp = /^-?\d+(\.|\.\d*|\d*)?$/; if (numberRegExp.test(input)) { if (input.endsWith(".")) { - setLocalZoom(input as any); + setLocalZoom(input); } else { const value = parseFloat(input); if (value > 2) { setErrors((prevErrors) => ({ ...prevErrors, zoom: "Value must be between 0 and 2" })); - setLocalZoom(2); + setLocalZoom(String(2)); return; } - setLocalZoom(value); + setLocalZoom(input); } } }} @@ -242,10 +243,10 @@ export const ConfigControls = () => { - - {isEdgeRenderingEnabled !== undefined && setEdgeRenderingEnabled !== undefined && ( -
-

Show edges:

+
+
+
Search
setEdgeRenderingEnabled(checked)} + className="input form-input-long" + type="text" + value={searchQuery} + onChange={(e) => { + setSearchQuery(e.target.value); + }} + maxLength={2000} />
- )} +
+
+
+ {children} +
+
+ +
+ {isEdgeRenderingEnabled !== undefined && setEdgeRenderingEnabled !== undefined && ( +
+

Show edges:

+ setEdgeRenderingEnabled(checked)} + /> +
+ )} +
+ {selectedFeedItem && ( + + )} + - - {selectedFeedItem && } - - +
+ +
+ ); };