From 46213025d2124f6ac73cf1f95e311ec0bbbe1f2d Mon Sep 17 00:00:00 2001 From: JCNoguera Date: Wed, 21 Feb 2024 22:53:13 +0100 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 eb79ebf691bdcb3c940b701aa4ff570ebdaad13b Mon Sep 17 00:00:00 2001 From: JCNoguera Date: Fri, 23 Feb 2024 14:54:06 +0100 Subject: [PATCH 7/8] 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 2d7b6a17019cbf1c44b737bc51aabe5b1c277009 Mon Sep 17 00:00:00 2001 From: JCNoguera Date: Fri, 23 Feb 2024 21:11:48 +0100 Subject: [PATCH 8/8] 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; }