Skip to content

Commit

Permalink
[WIP] Feature/ribbons (#32)
Browse files Browse the repository at this point in the history
* Cleanup auto camera

* Adds WIP ribbons visual

* Updates for ribbon material
  • Loading branch information
dcyoung authored Feb 16, 2024
1 parent 17dde84 commit e11894c
Show file tree
Hide file tree
Showing 12 changed files with 383 additions and 39 deletions.
120 changes: 120 additions & 0 deletions app/src/components/canvas/AutoOrbitCamera.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { useVisualContext } from "@/context/visual";
import { useFrame, useThree } from "@react-three/fiber";
import { Spherical, type Vector3 } from "three";

const setFromSphericalZUp = (vec: Vector3, s: Spherical) => {
const sinPhiRadius = Math.sin(s.phi) * s.radius;
vec.x = sinPhiRadius * Math.sin(s.theta);
vec.z = Math.cos(s.phi) * s.radius;
vec.y = sinPhiRadius * Math.cos(s.theta);
return vec;
};

const useSphericalLimits = () => {
const { visual } = useVisualContext();
// r is the Radius
// theta is the equator angle
// phi is the polar angle
switch (visual) {
case "ribbons":
return {
rMin: 10,
rMax: 15,
rSpeed: 0.1,
thetaMin: Math.PI / 8,
thetaMax: 2 * Math.PI - Math.PI / 8,
thetaSpeed: 0.025,
phiMin: Math.PI / 3,
phiMax: Math.PI / 2.1,
phiSpeed: 0.25,
};
case "sphere":
return {
rMin: 10,
rMax: 15,
rSpeed: 0.1,
thetaMin: 0,
thetaMax: 2 * Math.PI,
thetaSpeed: 0.025,
phiMin: Math.PI / 3,
phiMax: Math.PI / 2,
phiSpeed: 0.25,
};
case "cube":
return {
rMin: 12,
rMax: 20,
rSpeed: 0.1,
thetaMin: 0,
thetaMax: 2 * Math.PI,
thetaSpeed: 0.025,
phiMin: Math.PI / 4,
phiMax: Math.PI / 2,
phiSpeed: 0.25,
};
case "diffusedRing":
return {
rMin: 10,
rMax: 18,
rSpeed: 0.1,
thetaMin: 0,
thetaMax: 2 * Math.PI,
thetaSpeed: 0.025,
phiMin: Math.PI / 8,
phiMax: Math.PI / 2.25,
phiSpeed: 0.25,
};
case "boxes":
case "dna":
case "grid":
return {
rMin: 15,
rMax: 22,
rSpeed: 0.1,
thetaMin: 0,
thetaMax: 2 * Math.PI,
thetaSpeed: 0.025,
phiMin: Math.PI / 3,
phiMax: Math.PI / 2,
phiSpeed: 0.25,
};
default:
return visual satisfies never;
}
};

export const AutoOrbitCameraControls = () => {
const camera = useThree((state) => state.camera);
// r is the Radius
// theta is the equator angle
// phi is the polar angle
const {
rMin,
rMax,
rSpeed,
thetaMin,
thetaMax,
thetaSpeed,
phiMin,
phiMax,
phiSpeed,
} = useSphericalLimits();
const target = new Spherical();

useFrame(({ clock }) => {
const t = clock.elapsedTime;

const rAlpha = 0.5 * (1 + Math.sin(t * rSpeed));
const r = rMin + rAlpha * (rMax - rMin);

const thetaAlpha = 0.5 * (1 + Math.cos(t * thetaSpeed));
const theta = thetaMin + thetaAlpha * (thetaMax - thetaMin);

const phiAlpha = 0.5 * (1 + Math.cos(t * phiSpeed));
const phi = phiMin + phiAlpha * (phiMax - phiMin);

setFromSphericalZUp(camera.position, target.set(r, phi, theta));
camera.lookAt(0, 0, 0);
});
return null;
};
29 changes: 2 additions & 27 deletions app/src/components/canvas/Visual3D.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import { useVisualContext } from "@/context/visual";
import { APPLICATION_MODE } from "@/lib/applicationModes";
import { useUser } from "@/lib/appState";
import { OrbitControls } from "@react-three/drei";
import { Canvas, useFrame, useThree } from "@react-three/fiber";
import { Canvas, useFrame } from "@react-three/fiber";

import { AutoOrbitCameraControls } from "./AutoOrbitCamera";
import { PaletteTracker } from "./paletteTracker";

const VisualizerComponent = ({
Expand All @@ -36,32 +37,6 @@ const VisualizerComponent = ({
}
};

const AutoOrbitCameraControls = () => {
const camera = useThree((state) => state.camera);
const [rMin, rMax, rSpeed] = [15, 22, 0.1];
const thetaSpeed = 0.025;
const [polarMin, polarMax, polarSpeed] = [Math.PI / 3, Math.PI / 2, 0.25];
useFrame(({ clock }) => {
const t = clock.elapsedTime;

// r is the Radius
// theta is the horizontal angle from the X axis
// polar is the vertical angle from the Z axis
const rAlpha = 0.5 * (1 + Math.sin(t * rSpeed));
const r = rMin + rAlpha * (rMax - rMin);

const thetaAlpha = 0.5 * (1 + Math.cos(t * thetaSpeed));
const theta = thetaAlpha * (2 * Math.PI);
const polarAlpha = 0.5 * (1 + Math.cos(t * polarSpeed));
const polar = polarMin + polarAlpha * (polarMax - polarMin);
camera.position.x = r * Math.sin(polar) * Math.cos(theta);
camera.position.y = r * Math.sin(polar) * Math.sin(theta);
camera.position.z = r * Math.cos(polar);
camera.lookAt(0, 0, 0);
});
return null;
};

const CameraControls = () => {
const { mode, autoOrbitAfterSleepMs } = useCameraControlsContext();
const { setMode } = useCameraControlsContextSetters();
Expand Down
1 change: 1 addition & 0 deletions app/src/components/controls/visualSettingsSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const VisualSettingsControls = () => {
return SphereVisualSettingsControls();
case "diffusedRing":
return DiffusedRingVisualSettingsControls();
case "ribbons":
case "dna":
case "boxes":
return null;
Expand Down
12 changes: 11 additions & 1 deletion app/src/components/controls/visualsDock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ import {
type VisualType,
} from "@/components/visualizers/common";
import { useVisualContext, useVisualContextSetters } from "@/context/visual";
import { Box, Boxes, CircleDashed, Dna, Globe, Grid3x3 } from "lucide-react";
import {
Box,
Boxes,
CircleDashed,
Dna,
Globe,
Grid3x3,
Ribbon,
} from "lucide-react";

import { Dock, DockItem, DockNav } from "./dock";

Expand All @@ -22,6 +30,8 @@ const VisualIcon = ({ visual }: { visual: VisualType }) => {
return <Dna />;
case "boxes":
return <Boxes />;
case "ribbons":
return <Ribbon />;
default:
return visual satisfies never;
}
Expand Down
1 change: 1 addition & 0 deletions app/src/components/visualizers/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const AVAILABLE_VISUALS = [
"diffusedRing",
"dna",
"boxes",
"ribbons",
// "stencil",
// "swarm",
] as const;
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/visualizers/dna/multi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const MultiStrand = (props: BaseDoubleHelixProps) => {
const strandCount = strandRefs.length;
const bounds = 15;

const strandPositions = Array.from({ length: strandCount }).map((x, i) => {
const strandPositions = Array.from({ length: strandCount }).map((_, i) => {
return new Vector3()
.fromArray(
Array.from({ length: 3 }).map(
Expand Down
156 changes: 156 additions & 0 deletions app/src/components/visualizers/ribbons/base.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { useMemo, useRef } from "react";
import { usePalette } from "@/lib/appState";
import {
COORDINATE_TYPE,
type ICoordinateMapper,
} from "@/lib/mappers/coordinateMappers/common";
import { ColorPalette } from "@/lib/palettes";
import { Plane } from "@react-three/drei";
import { useFrame } from "@react-three/fiber";
import { DoubleSide, Vector3, type Mesh } from "three";

const BaseRibbons = ({
coordinateMapper,
ribbonWidth = 1,
ribbonHeight = 10,
ribbonWidthSegments = 1,
ribbonHeightSegments = 64,
zScale = 2,
}: {
coordinateMapper: ICoordinateMapper;
ribbonWidth?: number;
ribbonHeight?: number;
ribbonWidthSegments?: number;
ribbonHeightSegments?: number;
zScale?: number;
}) => {
const ribbonRefs = [
useRef<Mesh>(null!),
useRef<Mesh>(null!),
useRef<Mesh>(null!),
useRef<Mesh>(null!),
useRef<Mesh>(null!),
];
const ribbonCount = ribbonRefs.length;

const gridHalfWidth = (ribbonWidth * ribbonCount) / 2;
const gridHalfHeight = ribbonHeight / 2;
const ribbonPositions = Array.from({ length: ribbonCount }).map(
(_, i) =>
new Vector3(gridHalfWidth - ribbonWidth * i - ribbonWidth / 2, 0, 0),
);
const palette = usePalette();
const colorPalette = ColorPalette.getPalette(palette);
// const lut = ColorPalette.getPalette(palette).buildLut();

const material = useMemo(() => {
return (
<meshStandardMaterial
color={colorPalette.colorsHex[Math.floor(colorPalette.nColors / 2)]}
roughness={0.25}
metalness={0.25}
side={DoubleSide}
flatShading={false}
/>
);
}, [colorPalette]);

useFrame(({ clock }) => {
//in ms
const elapsedTimeSec = clock.getElapsedTime();

let w, h, vIdx, normX, normY, z, alpha;
ribbonRefs.forEach((ribbonRef, ribbonIdx) => {
if (!ribbonRef.current) {
return;
}

const positionsBuffer = ribbonRef.current.geometry.attributes.position;
for (h = 0; h <= ribbonHeightSegments; h++) {
alpha =
1 -
Math.abs(h - ribbonHeightSegments / 2) / (ribbonHeightSegments / 2);
for (w = 0; w <= ribbonWidthSegments; w++) {
normX = (ribbonIdx + 0.5) / ribbonCount;
normY = (h + 0.5) / ribbonHeightSegments;
vIdx = h * (ribbonWidthSegments + 1) + w;
z =
zScale *
coordinateMapper.map(
COORDINATE_TYPE.CARTESIAN_2D,
normX,
normY,
0,
elapsedTimeSec,
);

positionsBuffer.setZ(vIdx, z * alpha);
}
}
positionsBuffer.needsUpdate = true;
});
});

const planeSize = 250;
return (
<>
<ambientLight />
<pointLight position={[2, 2, 5]} intensity={150} />
<Plane
args={[planeSize / 2, planeSize, 1, 1]}
position={[gridHalfWidth + planeSize / 4, 0, 0]}
castShadow={true}
receiveShadow={true}
>
{material}
</Plane>
<Plane
args={[planeSize / 2, planeSize, 1, 1]}
position={[-gridHalfWidth - planeSize / 4, 0, 0]}
castShadow={true}
receiveShadow={true}
>
{material}
</Plane>
<Plane
args={[planeSize / 4, planeSize / 2 - gridHalfHeight, 1, 1]}
position={[
0,
-gridHalfHeight - (planeSize / 2 - gridHalfHeight) / 2,
0,
]}
castShadow={true}
receiveShadow={true}
>
{material}
</Plane>
<Plane
args={[planeSize / 4, planeSize / 2 - gridHalfHeight, 1, 1]}
position={[0, gridHalfHeight + (planeSize / 2 - gridHalfHeight) / 2, 0]}
castShadow={true}
receiveShadow={true}
>
{material}
</Plane>
{ribbonRefs.map((ref, i) => (
<Plane
key={`ribbon-plane-${i}`}
args={[
ribbonWidth,
ribbonHeight,
ribbonWidthSegments,
ribbonHeightSegments,
]}
ref={ref}
position={ribbonPositions[i]}
castShadow={true}
receiveShadow={true}
>
{material}
</Plane>
))}
</>
);
};

export default BaseRibbons;
13 changes: 13 additions & 0 deletions app/src/components/visualizers/ribbons/reactive.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { type VisualProps } from "@/components/visualizers/common";

import BaseRibbons from "./base";

const RibbonsVisual = ({ coordinateMapper }: VisualProps) => {
return (
<>
<BaseRibbons coordinateMapper={coordinateMapper} />
</>
);
};

export default RibbonsVisual;
Loading

0 comments on commit e11894c

Please sign in to comment.