From e87ee9f673ac85fc541668ce9901f14017f8b271 Mon Sep 17 00:00:00 2001 From: JCNoguera <88061365+VmMad@users.noreply.github.com> Date: Tue, 9 Jan 2024 16:02:02 +0100 Subject: [PATCH] feat: add sinusoidal wave motion to the emitter (#941) * feat: add sinusoidal wave motion to the emitter * chore: fix formatting issues --- .../features/visualizer-threejs/Emitter.tsx | 56 +++++++++++++++---- .../features/visualizer-threejs/constants.ts | 4 ++ .../src/features/visualizer-threejs/utils.ts | 12 +++- 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/client/src/features/visualizer-threejs/Emitter.tsx b/client/src/features/visualizer-threejs/Emitter.tsx index 5b77d4157..ab857d004 100644 --- a/client/src/features/visualizer-threejs/Emitter.tsx +++ b/client/src/features/visualizer-threejs/Emitter.tsx @@ -1,11 +1,12 @@ /* eslint-disable react/no-unknown-property */ import { useFrame, useThree } from "@react-three/fiber"; -import React, { RefObject, Dispatch, SetStateAction, useEffect } from "react"; +import React, { RefObject, Dispatch, SetStateAction, useEffect, useRef, useState } from "react"; import * as THREE from "three"; import { useBorderPositions } from "./hooks/useBorderPositions"; import { useConfigStore, useTangleStore } from "./store"; import { useRenderTangle } from "./useRenderTangle"; -import { EMITTER_DEPTH, EMITTER_HEIGHT, EMITTER_WIDTH } from './constants'; +import { EMITTER_DEPTH, EMITTER_HEIGHT, EMITTER_WIDTH, MAX_AMPLITUDE, AMPLITUDE_ACCUMULATOR, HALF_WAVE_PERIOD_SECONDS } from './constants'; +import { getNewSinusoidalPosition } from './utils'; interface EmitterProps { readonly setRunListeners: Dispatch>; @@ -16,11 +17,17 @@ const Emitter: React.FC = ({ setRunListeners, emitterRef }: EmitterProps) => { - const isPlaying = useConfigStore(state => state.isPlaying); + const setZoom = useTangleStore(s => s.setZoom); const get = useThree(state => state.get); const currentZoom = useThree(state => state.camera.zoom); - const setZoom = useTangleStore(s => s.setZoom); const { halfScreenWidth } = useBorderPositions(); + const isPlaying = useConfigStore(state => state.isPlaying); + + const [animationTime, setAnimationTime] = useState(0) + const [currentAmplitude, setCurrentAmplitude] = useState(AMPLITUDE_ACCUMULATOR); + + const previousRealTime = useRef(0); + const previousPeakTime = useRef(0); useEffect(() => { setZoom(currentZoom); @@ -41,24 +48,49 @@ const Emitter: React.FC = ({ } }); + function updateAnimationTime(realTimeDelta: number): void { + setAnimationTime(prev => prev + realTimeDelta); + } + + function checkAndHandleNewPeak(): void { + const currentHalfWaveCount = Math.floor(animationTime / HALF_WAVE_PERIOD_SECONDS); + const lastPeakHalfWaveCount = Math.floor(previousPeakTime.current / HALF_WAVE_PERIOD_SECONDS); + + if (currentHalfWaveCount > lastPeakHalfWaveCount) { + setCurrentAmplitude(prev => Math.min(prev + AMPLITUDE_ACCUMULATOR, MAX_AMPLITUDE)); + previousPeakTime.current = animationTime; + } + } + /** * Emitter shift */ - useFrame((_, delta) => { - if (!isPlaying) { - return; - } + useFrame(({ clock }, delta) => { + const DELTA_MULTIPLIER = 80; // depends on this param we can manage speed of emitter - if (emitterRef?.current) { - const DELTA_MULTIPLIER = 80; // depends on this param we can manage speed of emitter - emitterRef.current.position.x += delta * DELTA_MULTIPLIER; + const currentRealTime = clock.getElapsedTime(); + const realTimeDelta = currentRealTime - previousRealTime.current; + previousRealTime.current = currentRealTime; + + if (isPlaying) { + updateAnimationTime(realTimeDelta); + checkAndHandleNewPeak(); + + if (emitterRef.current) { + const { x } = emitterRef.current.position; + + const newXPos = x + (delta * DELTA_MULTIPLIER); + const newYPos = getNewSinusoidalPosition(animationTime, currentAmplitude); + + emitterRef.current.position.y = newYPos; + emitterRef.current.position.x = newXPos; + } } }); // The Tangle rendering hook useRenderTangle(); - return ( = { export const EMITTER_WIDTH = 30; export const EMITTER_HEIGHT = 250; export const EMITTER_DEPTH = 250; + +export const MAX_AMPLITUDE = 200; +export const AMPLITUDE_ACCUMULATOR = 10; +export const HALF_WAVE_PERIOD_SECONDS = 4; diff --git a/client/src/features/visualizer-threejs/utils.ts b/client/src/features/visualizer-threejs/utils.ts index 07f883828..3c38cda55 100644 --- a/client/src/features/visualizer-threejs/utils.ts +++ b/client/src/features/visualizer-threejs/utils.ts @@ -1,4 +1,4 @@ -import { STEP_Y_PX, TIME_DIFF_COUNTER, SECOND } from "./constants"; +import { STEP_Y_PX, TIME_DIFF_COUNTER, SECOND, HALF_WAVE_PERIOD_SECONDS } from "./constants"; /** * Generates a random number within a specified range. @@ -147,3 +147,13 @@ export const getGenerateY = ({ withRandom }: {withRandom?: boolean} = {}): (shif return Y * STEP_Y_PX; }; }; + +export function getNewSinusoidalPosition(time: number, amplitude: number): number { + const period = HALF_WAVE_PERIOD_SECONDS * 2; + const frequency = 1 / period; + const phase = (time % period) * frequency + + const newY = amplitude * Math.sin(phase * 2 * Math.PI); + + return newY; +}