Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feat/nova-visualizer/add-emitter…
Browse files Browse the repository at this point in the history
…-tilting' into feat/issues-1171-add-controls-for-visualizer

# Conflicts:
#	client/src/features/visualizer-threejs/utils.ts
  • Loading branch information
panteleymonchuk committed Feb 26, 2024
2 parents e04a744 + 2d7b6a1 commit cba1854
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 107 deletions.
8 changes: 3 additions & 5 deletions client/src/features/visualizer-threejs/VisualizerInstance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,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 VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> = ({
match: {
Expand Down Expand Up @@ -226,11 +225,10 @@ const VisualizerInstance: React.FC<RouteComponentProps<VisualizerRouteProps>> =
if (blockWeakParents.length > 0) {
addToEdgeQueue(blockData.blockId, blockWeakParents);
}

addBlock({
id: blockData.blockId,
color: PENDING_BLOCK_COLOR,
blockAddedTimestamp: getCurrentAnimationTime(),
blockAddedTimestamp: currentAnimationTime,
targetPosition,
initPosition,
});
Expand Down
28 changes: 15 additions & 13 deletions client/src/features/visualizer-threejs/blockPositions.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
2 changes: 1 addition & 1 deletion client/src/features/visualizer-threejs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
5 changes: 5 additions & 0 deletions client/src/features/visualizer-threejs/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ export interface ICameraAngles {
maxAzimuthAngle: number;
}

export interface ITwoDimensionalPosition {
x: number;
y: number;
}

export interface IThreeDimensionalPosition {
x: number;
y: number;
Expand Down
79 changes: 49 additions & 30 deletions client/src/features/visualizer-threejs/useRenderTangle.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<Map<number, THREE.Vector3>>(new Map());
const objectIndexRef = useRef(1);
const { scene } = useThree();
const isPlaying = useConfigStore((s) => s.isPlaying);

Expand All @@ -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) => {
Expand All @@ -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;
Expand Down Expand Up @@ -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<string, IBlockAnimationPosition> = new Map();
const updateAnimationPositionQueue: Map<number, THREE.Vector3> = 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]);
};
105 changes: 47 additions & 58 deletions client/src/features/visualizer-threejs/utils.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -18,8 +14,12 @@ import {
NUMBER_OF_RANDOM_AMPLITUDES,
NUMBER_OF_RANDOM_TILTINGS,
TILT_DURATION_SECONDS,
SPRAY_DISTANCE,
MAX_PREV_POINTS,
MAX_POINT_RETRIES,
MIN_BLOCK_NEAR_RADIUS,
} from "./constants";
import type { ICameraAngles, ISinusoidalPositionParams, IThreeDimensionalPosition } from "./interfaces";
import type { ICameraAngles, ISinusoidalPositionParams, IThreeDimensionalPosition, ITwoDimensionalPosition } from "./interfaces";
import { getVisualizerConfigValues } from "~features/visualizer-threejs/ConfigControls";

/**
Expand Down Expand Up @@ -68,16 +68,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.
Expand All @@ -93,32 +83,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));
}

/**
Expand All @@ -133,6 +99,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.
Expand All @@ -142,18 +122,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();
}
Expand All @@ -162,23 +143,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 };
}

/**
Expand Down Expand Up @@ -351,6 +339,7 @@ export function generateRandomAmplitudes(): number[] {
currentAmplitude = getNextAmplitudeWithVariation(currentAmplitude);
amplitudes.push(currentAmplitude);
}

return amplitudes;
}

Expand Down

0 comments on commit cba1854

Please sign in to comment.