From 72a6a6b9376ba4dd37324e8d0ed997461e3dfe39 Mon Sep 17 00:00:00 2001 From: Eugene P Date: Wed, 20 Dec 2023 19:01:09 +0200 Subject: [PATCH] enhancement: Update shape of the tangle: star trail (#924) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * change first element Signed-off-by: Eugene Panteleymonchuk * feat: update shape position smooth. Signed-off-by: Eugene Panteleymonchuk * feature: update shape of tangle. Init position using ref. Signed-off-by: Eugene Panteleymonchuk * feature: update shape of tangle. Signed-off-by: Eugene Panteleymonchuk * chore: broken changing of position. Signed-off-by: Eugene Panteleymonchuk * chore: Spray animation in another way. Signed-off-by: Eugene Panteleymonchuk * chore: Clear changing of formatting. Signed-off-by: Eugene Panteleymonchuk --------- Signed-off-by: Eugene Panteleymonchuk Co-authored-by: Begoña Álvarez de la Cruz --- .../visualizer-threejs/VisualizerInstance.tsx | 36 +++--- .../features/visualizer-threejs/constants.ts | 1 + .../visualizer-threejs/store/tangle.ts | 61 ++++++++-- .../visualizer-threejs/useRenderTangle.tsx | 110 +++++++++++++----- 4 files changed, 157 insertions(+), 51 deletions(-) diff --git a/client/src/features/visualizer-threejs/VisualizerInstance.tsx b/client/src/features/visualizer-threejs/VisualizerInstance.tsx index 7c8b6dec1..398360846 100644 --- a/client/src/features/visualizer-threejs/VisualizerInstance.tsx +++ b/client/src/features/visualizer-threejs/VisualizerInstance.tsx @@ -55,7 +55,6 @@ const VisualizerInstance: React.FC> = const addToEdgeQueue = useTangleStore(s => s.addToEdgeQueue); const addToColorQueue = useTangleStore(s => s.addToColorQueue); const addYPosition = useTangleStore(s => s.addYPosition); - const blockIdToPosition = useTangleStore(s => s.blockIdToPosition); const blockMetadata = useTangleStore(s => s.blockMetadata); const indexToBlockId = useTangleStore(s => s.indexToBlockId); @@ -137,28 +136,39 @@ const VisualizerInstance: React.FC> = const Y = generateY(secondsFromStart, bpsCounter.getBPS()); - const position: [number, number, number] = [ - randomIntFromInterval(emitterBox.min.x, emitterBox.max.x), - Y, - randomIntFromInterval(emitterBox.min.z, emitterBox.max.z), - ]; + const targetPosition = { + x: randomIntFromInterval(emitterBox.min.x, emitterBox.max.x), + y: Y, + z: randomIntFromInterval(emitterBox.min.z, emitterBox.max.z), + }; bpsCounter.addBlock(); if (!bpsCounter.getBPS()) { bpsCounter.start(); } - addBlock({ - id: blockData.blockId, - position, - color: PENDING_BLOCK_COLOR - }); - - blockIdToPosition.set(blockData.blockId, position); blockMetadata.set(blockData.blockId, blockData); addToEdgeQueue(blockData.blockId, blockData.parents ?? []); addYPosition(Y); + + const emitterCenter = new THREE.Vector3(); + emitterBox.getCenter(emitterCenter); + + addBlock({ + id: blockData.blockId, + color: PENDING_BLOCK_COLOR, + targetPosition: { + x: targetPosition.x, + y: targetPosition.y, + z: targetPosition.z + }, + initPosition: { + x: emitterCenter.x, + y: emitterCenter.y, + z: emitterCenter.z + } + }); } }; diff --git a/client/src/features/visualizer-threejs/constants.ts b/client/src/features/visualizer-threejs/constants.ts index 6796cf18e..1eb418b6c 100644 --- a/client/src/features/visualizer-threejs/constants.ts +++ b/client/src/features/visualizer-threejs/constants.ts @@ -18,6 +18,7 @@ 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') diff --git a/client/src/features/visualizer-threejs/store/tangle.ts b/client/src/features/visualizer-threejs/store/tangle.ts index 884c2ea99..905dde677 100644 --- a/client/src/features/visualizer-threejs/store/tangle.ts +++ b/client/src/features/visualizer-threejs/store/tangle.ts @@ -1,12 +1,21 @@ import { Color } from "three"; import { create } from "zustand"; import { devtools } from "zustand/middleware"; -import { ZOOM_DEFAULT } from "../constants"; -import {IFeedBlockData} from "../../../models/api/stardust/feed/IFeedBlockData"; +import { ZOOM_DEFAULT, ANIMATION_TIME_SECONDS } from "../constants"; +import { IFeedBlockData } from "~models/api/stardust/feed/IFeedBlockData"; -interface BlockState { +interface IPosition { + x: number; + y: number; + z: number; +} + +export interface IBlockInitPosition extends IPosition { + duration: number; +} + +export interface BlockState { id: string; - position: [x: number, y: number, z: number]; color: Color; } @@ -23,7 +32,7 @@ interface EdgeEntry { interface TangleState { // Queue for "add block" operation to the canvas blockQueue: BlockState[]; - addToBlockQueue: (newBlock: BlockState) => void; + addToBlockQueue: (newBlock: BlockState & { initPosition: IPosition; targetPosition: IPosition; }) => void; removeFromBlockQueue: (blockIds: string[]) => void; edgeQueue: Edge[]; @@ -39,6 +48,7 @@ interface TangleState { blockIdToEdges: Map; blockIdToPosition: Map; blockMetadata: Map; + blockIdToAnimationPosition: Map; indexToBlockId: string[]; updateBlockIdToIndex: (blockId: string, index: number) => void; @@ -55,6 +65,8 @@ interface TangleState { clickedInstanceId: string | null; setClickedInstanceId: (instanceId: string | null) => void; + + updateBlockIdToAnimationPosition: (updatedPositions: Map) => void; } export const useTangleStore = create()(devtools(set => ({ @@ -65,19 +77,46 @@ export const useTangleStore = create()(devtools(set => ({ blockIdToIndex: new Map(), blockIdToPosition: new Map(), blockMetadata: new Map(), + blockIdToAnimationPosition: new Map(), indexToBlockId: [], yPositions: {}, zoom: ZOOM_DEFAULT, bps: 0, clickedInstanceId: null, - addToBlockQueue: newBlockData => { - set(state => ({ - blockQueue: [...state.blockQueue, newBlockData] - })); + updateBlockIdToAnimationPosition: (updatedPositions) => { + set(state => { + updatedPositions.forEach((value, key) => { + state.blockIdToAnimationPosition.set(key, value); + }); + + for (const [key, value] of state.blockIdToAnimationPosition) { + if (value.duration > ANIMATION_TIME_SECONDS) { + state.blockIdToAnimationPosition.delete(key); + } + } + return { + blockIdToAnimationPosition: state.blockIdToAnimationPosition + }; + }); + }, + addToBlockQueue: block => { + set(state => { + const { initPosition, targetPosition, ...blockRest } = block; + + state.blockIdToPosition.set(block.id, [targetPosition.x, targetPosition.y, targetPosition.z]); + state.blockIdToAnimationPosition.set(block.id, { + ...initPosition, + duration: 0, + }); + return { + ...state, + blockQueue: [...state.blockQueue, blockRest] + }; + }); }, removeFromBlockQueue: (blockIds: string[]) => { - set(state => ({ - ...state, + if (!blockIds.length) return; + set((state) => ({ blockQueue: state.blockQueue.filter(b => !blockIds.includes(b.id)) })); }, diff --git a/client/src/features/visualizer-threejs/useRenderTangle.tsx b/client/src/features/visualizer-threejs/useRenderTangle.tsx index 944c2ac7f..f0923e7ea 100644 --- a/client/src/features/visualizer-threejs/useRenderTangle.tsx +++ b/client/src/features/visualizer-threejs/useRenderTangle.tsx @@ -1,9 +1,9 @@ import { useThree } from "@react-three/fiber"; import { useEffect, useRef } from "react"; import * as THREE from "three"; -import { MAX_BLOCK_INSTANCES, NODE_SIZE_DEFAULT } from "./constants"; +import { MAX_BLOCK_INSTANCES, NODE_SIZE_DEFAULT, ANIMATION_TIME_SECONDS } from "./constants"; import { useMouseMove } from "./hooks/useMouseMove"; -import { useTangleStore } from "./store"; +import { BlockState, IBlockInitPosition, useConfigStore, useTangleStore } from "./store"; import { useRenderEdges } from "./useRenderEdges"; const SPHERE_GEOMETRY = new THREE.SphereGeometry(NODE_SIZE_DEFAULT, 32, 16); @@ -25,6 +25,8 @@ export const useRenderTangle = () => { const blockIdToIndex = useTangleStore(s => s.blockIdToIndex); const updateBlockIdToIndex = useTangleStore(s => s.updateBlockIdToIndex); + const blockIdToPosition = useTangleStore(s => s.blockIdToPosition); + const blockIdToAnimationPosition = useTangleStore(s => s.blockIdToAnimationPosition); const updateBlockColor = (blockId: string, color: THREE.Color): void => { const indexToUpdate = blockIdToIndex.get(blockId); @@ -38,15 +40,83 @@ export const useRenderTangle = () => { } }; + function updateInstancedMeshPosition(instancedMesh: THREE.InstancedMesh, index: number, nextPosition: THREE.Vector3) { + const matrix = new THREE.Matrix4(); + const position = new THREE.Vector3(); + const quaternion = new THREE.Quaternion(); + const scale = new THREE.Vector3(); + instancedMesh.getMatrixAt(index, matrix); + matrix.decompose(position, quaternion, scale); + matrix.compose(nextPosition, quaternion, scale); + instancedMesh.setMatrixAt(index, matrix); + instancedMesh.instanceMatrix.needsUpdate = true; + } + + const assignBlockToMesh = (block: BlockState) => { + const initPosition = blockIdToAnimationPosition.get(block.id); + + if (!initPosition) return; + + SPHERE_TEMP_OBJECT.position.set( + initPosition.x, + initPosition.y, + initPosition.z, + ); + SPHERE_TEMP_OBJECT.scale.setScalar(INITIAL_SPHERE_SCALE); + SPHERE_TEMP_OBJECT.updateMatrix(); + + updateBlockIdToIndex(block.id, objectIndexRef.current); + + tangleMeshRef.current.setMatrixAt(objectIndexRef.current, SPHERE_TEMP_OBJECT.matrix); + tangleMeshRef.current.setColorAt(objectIndexRef.current, block.color); + + // 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) { + objectIndexRef.current += 1; + } else { + objectIndexRef.current = 0; + } + + return block.id; + } + useRenderEdges(); useMouseMove({ tangleMeshRef }); - const st = useThree(state => state); - + /** Spray animation */ useEffect(() => { - // @ts-expect-error: It's fine - window.st = st; - }, [st]); + const PERIOD = 24; // ms + + const int = setInterval(() => { + const isPlaying = useConfigStore.getState().isPlaying; + if (!isPlaying) { + return; + } + const blockIdToAnimationPosition = useTangleStore.getState().blockIdToAnimationPosition; + const updateBlockIdToAnimationPosition = useTangleStore.getState().updateBlockIdToAnimationPosition; + const delta = PERIOD / 1000; + + const updatedAnimationPositions: Map = new Map(); + blockIdToAnimationPosition.forEach(({ x, y, z, duration: currentTime }, blockId) => { + const nextTime = currentTime + delta; + const startPositionVector = new THREE.Vector3(x, y, z); + const endPositionVector = new THREE.Vector3(...blockIdToPosition.get(blockId) as [number, number, number]); + const interpolationFactor = Math.min(nextTime / ANIMATION_TIME_SECONDS, 1); // set 1 as max value + + const targetPositionVector = new THREE.Vector3(); + targetPositionVector.lerpVectors(startPositionVector, endPositionVector, interpolationFactor) + updatedAnimationPositions.set(blockId, { x, y, z, duration: nextTime }); + const index = blockIdToIndex.get(blockId); + if (index) { + updateInstancedMeshPosition(tangleMeshRef.current, index, targetPositionVector); + } + }); + updateBlockIdToAnimationPosition(updatedAnimationPositions); + }, PERIOD); + + return () => clearInterval(int); + }, []); useEffect(() => { const intervalCallback = () => { @@ -75,6 +145,8 @@ export const useRenderTangle = () => { } }, [tangleMeshRef]); + + useEffect(() => { if (blockQueue.length === 0) { return; @@ -83,27 +155,11 @@ export const useRenderTangle = () => { const addedIds = []; for (const block of blockQueue) { - const [x, y, z] = block.position; - const color = block.color; - - SPHERE_TEMP_OBJECT.position.set(x, y, z); - SPHERE_TEMP_OBJECT.scale.setScalar(INITIAL_SPHERE_SCALE); - SPHERE_TEMP_OBJECT.updateMatrix(); + const assignedBlockId = assignBlockToMesh(block); - updateBlockIdToIndex(block.id, objectIndexRef.current); - - tangleMeshRef.current.setMatrixAt(objectIndexRef.current, SPHERE_TEMP_OBJECT.matrix); - tangleMeshRef.current.setColorAt(objectIndexRef.current, color); - - // 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) { - objectIndexRef.current += 1; - } else { - objectIndexRef.current = 0; + if (assignedBlockId) { + addedIds.push(assignedBlockId); } - - addedIds.push(block.id); } if (tangleMeshRef.current.instanceColor) { @@ -114,7 +170,7 @@ export const useRenderTangle = () => { tangleMeshRef.current.computeBoundingSphere(); removeFromBlockQueue(addedIds); - }, [blockQueue]); + }, [blockQueue, blockIdToAnimationPosition]); useEffect(() => { if (colorQueue.length === 0) {