From aa56a6ff4bee93875b0dd72a8826733d85e7cb91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=B0=D1=81=D1=83=D0=BB?= Date: Wed, 11 Dec 2024 23:38:15 +0300 Subject: [PATCH 1/3] feat: added animation for new appearing nodes --- .../Universe/Graph/Cubes/Text/index.tsx | 201 +++++++++++------- src/components/Universe/Graph/Cubes/index.tsx | 22 +- src/components/Universe/Graph/index.tsx | 51 ++--- 3 files changed, 152 insertions(+), 122 deletions(-) diff --git a/src/components/Universe/Graph/Cubes/Text/index.tsx b/src/components/Universe/Graph/Cubes/Text/index.tsx index 56ebf1079..f0c06a83a 100644 --- a/src/components/Universe/Graph/Cubes/Text/index.tsx +++ b/src/components/Universe/Graph/Cubes/Text/index.tsx @@ -1,7 +1,7 @@ import { Billboard, Plane, Svg, Text } from '@react-three/drei' -import { useFrame } from '@react-three/fiber' +import { useFrame, useThree } from '@react-three/fiber' import gsap from 'gsap' -import { memo, useEffect, useRef } from 'react' +import { memo, useRef } from 'react' import { Mesh, MeshBasicMaterial, Vector3 } from 'three' import { Icons } from '~/components/Icons' import { useTraceUpdate } from '~/hooks/useTraceUpdate' @@ -18,6 +18,8 @@ type Props = { color: string hide?: boolean ignoreDistance: boolean + scale: number + index: number } function splitStringIntoThreeParts(text: string): string { @@ -38,13 +40,22 @@ function splitStringIntoThreeParts(text: string): string { return `${firstPart}\n${secondPart}\n${thirdPart}` } +const offset = { x: 20, y: 20 } + export const TextNode = memo( (props: Props) => { - const { node, hide, ignoreDistance, color } = props + const { node, hide, ignoreDistance, color, index } = props + const simulation = useGraphStore((s) => s.simulation) + const positionAnimationRef = useRef(null) + const scaleAnimationRef = useRef(null) + + const finishedSimulationCircle = useRef(false) const svgRef = useRef(null) const ringRef = useRef(null) const circleRef = useRef(null) + const wrapperRef = useRef(null) + const { camera: perspectiveCamera, size } = useThree() useTraceUpdate(props) @@ -89,30 +100,69 @@ export const TextNode = memo( } if (circleRef.current) { - circleRef.current.visible = false + circleRef.current.visible = true } - checkDistance() - }) - - useEffect(() => { - if (!ringRef.current) { - return + if (finishedSimulationCircle.current) { + checkDistance() } - gsap.fromTo( - ringRef.current.scale, // Target - { x: 1, y: 1, z: 1 }, // From values - { - x: 2, - y: 2, - z: 2, // To values - duration: 1.5, // Animation duration - yoyo: true, - repeat: 1, - }, - ) - }, [ringRef]) + if (wrapperRef.current && simulation) { + const simulationNode = simulation.nodes()[index] + + if (!simulationNode) { + return + } + + if (!finishedSimulationCircle.current) { + // Define the NDC coordinates for the fixed position + const ndc = new Vector3( + -1 + (offset.x * 2) / size.width, // Adjust for left offset + 1 - (offset.y * 2) / size.height, // Adjust for top offset + 0, // Near clipping plane + ) + + // Convert NDC to world space + const worldPosition = ndc.unproject(perspectiveCamera) + + // Maintain a fixed distance from the camera + const distanceFromCamera = 5 + const direction = worldPosition.sub(perspectiveCamera.position).normalize() + const fixedPosition = perspectiveCamera.position.clone().add(direction.multiplyScalar(distanceFromCamera)) + + wrapperRef.current.position.copy(fixedPosition) + + // Store the largest dimension as the "size" of the mesh + + wrapperRef.current.scale.set(0.1, 0.1, 0.1) + + wrapperRef.current.visible = false + } + + if (simulationNode.fx && !finishedSimulationCircle.current) { + wrapperRef.current.visible = true + finishedSimulationCircle.current = true + positionAnimationRef?.current?.kill() + scaleAnimationRef?.current?.kill() + + positionAnimationRef.current = gsap.to(wrapperRef.current.position, { + x: simulationNode.fx, // Destination X coordinate + y: simulationNode.fy, // Destination Y coordinate + z: simulationNode.fz, // Destination Z coordinate + duration: 4, // Animation duration in seconds + ease: 'power2.in', // Easing function + }) + + scaleAnimationRef.current = gsap.to(wrapperRef.current.scale, { + x: 1, // Destination X coordinate + y: 1, // Destination Y coordinate + z: 1, // Destination Z coordinate + duration: 4.5, // Animation duration in seconds + ease: 'power2.in', // Easing function + }) + } + } + }) const primaryColor = normalizedSchemasByType[node.node_type]?.primary_color const primaryIcon = normalizedSchemasByType[node.node_type]?.icon @@ -132,17 +182,20 @@ export const TextNode = memo( } return ( - - - - - - - - {node.properties?.image_url && texture ? ( - - + + + + + + + + + + {node.properties?.image_url && texture ? ( + + + + ) : ( + { + svg.traverse((child) => { + if (child instanceof Mesh) { + // Apply dynamic color to meshes + // eslint-disable-next-line no-param-reassign + child.material = new MeshBasicMaterial({ color }) + } + }) + }} + position={[-15, 15, 0]} + scale={2} + src={`svg-icons/${iconName}.svg`} + strokeMaterial={{ color: 'yellow' }} + userData={node} /> - - ) : ( - { - svg.traverse((child) => { - if (child instanceof Mesh) { - // Apply dynamic color to meshes - // eslint-disable-next-line no-param-reassign - child.material = new MeshBasicMaterial({ color }) - } - }) - }} - position={[-15, 15, 0]} - scale={2} - src={`svg-icons/${iconName}.svg`} - strokeMaterial={{ color: 'yellow' }} - userData={node} - /> - )} - - {sanitizedNodeName && ( - - {splitStringIntoThreeParts(sanitizedNodeName)} - - )} - - + )} + + {sanitizedNodeName && ( + + {splitStringIntoThreeParts(sanitizedNodeName)} + + )} + + + ) }, (prevProps, nextProps) => diff --git a/src/components/Universe/Graph/Cubes/index.tsx b/src/components/Universe/Graph/Cubes/index.tsx index 3c677261e..4da4b98be 100644 --- a/src/components/Universe/Graph/Cubes/index.tsx +++ b/src/components/Universe/Graph/Cubes/index.tsx @@ -142,22 +142,20 @@ export const Cubes = memo(() => { > - {data?.nodes.map((node: NodeExtended) => { + {data?.nodes.map((node: NodeExtended, index) => { const hide = !!selectedNode && (relativeIds.includes(node.ref_id) || selectedNode.ref_id === node.ref_id) const color = COLORS_MAP[nodeTypes.indexOf(node.node_type)] || colors.white return ( - - - - - + ) })} diff --git a/src/components/Universe/Graph/index.tsx b/src/components/Universe/Graph/index.tsx index c1c78840c..fcf6e3eb2 100644 --- a/src/components/Universe/Graph/index.tsx +++ b/src/components/Universe/Graph/index.tsx @@ -88,22 +88,24 @@ export const Graph = () => { cameraSettled.current = true } } + }) + + simulation.on('end', () => { + const nodesVector = simulation.nodes().map((i: NodeExtended) => { + // eslint-disable-next-line no-param-reassign + i.fx = i.x + // eslint-disable-next-line no-param-reassign + i.fy = i.y + // eslint-disable-next-line no-param-reassign + i.fz = i.z + + return new Vector3(i.x, i.y, i.z) + }) if (groupRef.current) { - const gr = groupRef.current.getObjectByName('simulation-3d-group__nodes') as Group const grPoints = groupRef.current.getObjectByName('simulation-3d-group__node-points') as Group const grConnections = groupRef.current.getObjectByName('simulation-3d-group__connections') as Group - if (gr) { - gr.children.forEach((mesh, index) => { - const simulationNode = simulation.nodes()[index] - - if (simulationNode) { - mesh.position.set(simulationNode.x, simulationNode.y, simulationNode.z) - } - }) - } - if (grPoints) { grPoints.children[0].children.forEach((mesh, index) => { const simulationNode = simulation.nodes()[index] @@ -114,10 +116,6 @@ export const Graph = () => { }) } - if (simulation.alpha() > 1) { - return - } - if (grConnections) { linksPositionRef.current.clear() @@ -174,31 +172,10 @@ export const Graph = () => { }) } } - }) - - simulation.on('end', () => { - const nodesVector = simulation.nodes().map((i: NodeExtended) => { - // eslint-disable-next-line no-param-reassign - i.fx = i.x - // eslint-disable-next-line no-param-reassign - i.fy = i.y - // eslint-disable-next-line no-param-reassign - i.fz = i.z - - return new Vector3(i.x, i.y, i.z) - }) const boundingBox = new Box3().setFromPoints(nodesVector) - const boundingSphere = new Sphere() - - boundingBox.getBoundingSphere(boundingSphere) - - const sphereRadius = boundingSphere.radius - - setGraphRadius(sphereRadius * 1.5) - - cameraSettled.current = false + console.log(boundingBox) }) }, [dataInitial, simulation, setGraphRadius, normalizedSchemasByType]) From 2f46eae9e4f78d8054babd82ca543268671e35a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=B0=D1=81=D1=83=D0=BB?= Date: Wed, 11 Dec 2024 23:39:12 +0300 Subject: [PATCH 2/3] feat: remove particles --- src/components/Universe/Graph/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/Universe/Graph/index.tsx b/src/components/Universe/Graph/index.tsx index fcf6e3eb2..7a37e9354 100644 --- a/src/components/Universe/Graph/index.tsx +++ b/src/components/Universe/Graph/index.tsx @@ -10,7 +10,6 @@ import { Connections } from './Connections' import { Cubes } from './Cubes' import { Earth } from './Earth' import { LoadingNodes } from './LoadingNodes' -import { Particles } from './Particles/index' import { NodeDetailsPanel } from './UI' export type LinkPosition = { @@ -187,7 +186,6 @@ export const Graph = () => { {graphStyle === 'earth' && } - {(isLoadingNew || isFetching) && } From f34919f018861f8c10d84cab4b88802c92df7477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=B0=D1=81=D1=83=D0=BB?= Date: Wed, 11 Dec 2024 23:46:07 +0300 Subject: [PATCH 3/3] feat: fix build --- .../Graph/Cubes/SelectionDataNodes/index.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/Universe/Graph/Cubes/SelectionDataNodes/index.tsx b/src/components/Universe/Graph/Cubes/SelectionDataNodes/index.tsx index bac65beb7..99fe4c07b 100644 --- a/src/components/Universe/Graph/Cubes/SelectionDataNodes/index.tsx +++ b/src/components/Universe/Graph/Cubes/SelectionDataNodes/index.tsx @@ -116,9 +116,17 @@ export const SelectionDataNodes = memo(() => { return ( <> - {selectionGraphData?.nodes.map((node) => ( + {selectionGraphData?.nodes.map((node, index) => ( - + ))}