Skip to content

Commit 6156e5a

Browse files
committed
Updated orbiter and some UI
1 parent dbbaa69 commit 6156e5a

File tree

8 files changed

+97
-38
lines changed

8 files changed

+97
-38
lines changed

src/app/globals.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
--glass-bg: rgba(255, 255, 255, 0.1);
5757
--glass-border: rgba(255, 255, 255, 0.2);
5858
--glass-shadow: rgba(0, 0, 0, 0.1);
59+
--warning-area: rgb(255, 205, 67);
5960
}
6061

6162
/* Light Theme */
@@ -83,6 +84,7 @@
8384
--glass-shadow: rgba(0, 0, 0, 0.15);
8485
--play-background: rgba(200, 200, 200, 0.8);
8586
--notice-shadow: rgb(230, 230, 230);
87+
--warning-area: rgba(255, 205, 67, 0.616);
8688
}
8789

8890
/* Dark Theme */
@@ -141,6 +143,7 @@
141143
--glass-shadow: rgba(0, 0, 0, 0.3);
142144
--play-background: rgba(200, 200, 200, 0.8);
143145
--notice-shadow: rgb(70, 70, 70);
146+
--warning-area: rgba(255, 183, 49, 0.555);
144147
}
145148

146149
@layer utilities {

src/components/plots/KeyFramePreviewer.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import React, { useEffect, useMemo, useRef } from 'react'
55
import { useShallow } from 'zustand/shallow'
66
import * as THREE from 'three'
77
import { lerp } from 'three/src/math/MathUtils.js';
8-
8+
import { deg2rad } from '@/utils/HelperFuncs';
99

1010
export const KeyFramePreviewer = () => {
1111
const {keyFrames, currentFrame, previewKeyFrames, frames,
12-
frameRate, useTime, timeRate, orbit, loopTime,} = useImageExportStore(useShallow(state => ({
12+
frameRate, useTime, timeRate, orbit, orbitDeg, orbitDir, loopTime,} = useImageExportStore(useShallow(state => ({
1313
keyFrames:state.keyFrames, currentFrame:state.currentFrame, previewKeyFrames:state.previewKeyFrames,
1414
frames:state.frames, frameRate:state.frameRate, useTime:state.useTime, timeRate:state.timeRate,
15-
orbit:state.orbit, loopTime:state.loopTime
15+
orbit:state.orbit, orbitDeg:state.orbitDeg, orbitDir:state.orbitDir, loopTime:state.loopTime
1616
})))
1717

1818
const {camera} = useThree();
@@ -121,8 +121,8 @@ export const KeyFramePreviewer = () => {
121121
}
122122
}
123123
if (orbit){
124-
const angle = (currentFrame / (frames+1)) * Math.PI * 2;
125-
const newAngle = originalAngle + angle;
124+
const angle = (currentFrame / (frames+1)) * deg2rad(orbitDeg);
125+
const newAngle = originalAngle + (orbitDir ? -angle : angle);
126126
camera.position.x = radius * Math.sin(newAngle);
127127
camera.position.z = radius * Math.cos(newAngle);
128128
camera.lookAt(0, 0, 0);
@@ -177,8 +177,8 @@ export const KeyFramePreviewer = () => {
177177
}
178178
}
179179
if (orbit){
180-
const angle = (frame / (frames+1)) * Math.PI * 2;
181-
const newAngle = originalAngle + angle;
180+
const angle = (frame / (frames+1)) * deg2rad(orbitDeg);
181+
const newAngle = originalAngle + (orbitDir ? -angle : angle);
182182
camera.position.x = radius * Math.sin(newAngle);
183183
camera.position.z = radius * Math.cos(newAngle);
184184
camera.lookAt(0, 0, 0);

src/components/ui/ExportImageSettings.tsx

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,29 @@ import { Switch } from './switch';
2222
import Hider from './Hider';
2323
import { Button } from './button';
2424
import { BsBoxArrowRight } from "react-icons/bs";
25+
import { Switcher } from './Switcher';
26+
import { FaLongArrowAltRight } from "react-icons/fa";
27+
2528

2629
const ExportImageSettings = () => {
2730
const {
2831
includeBackground, includeColorbar, doubleSize, cbarLoc, cbarNum,
2932
useCustomRes, customRes, includeAxis, mainTitle, cbarLabel, cbarUnits, animate, timeRate,
30-
frames, frameRate, orbit, useTime, loopTime, keyFrameEditor, preview, pingpong
33+
frames, frameRate, orbit, useTime, loopTime, orbitDeg, orbitDir, preview, pingpong
3134
} = useImageExportStore(useShallow(state => ({
3235
includeBackground: state.includeBackground, includeColorbar: state.includeColorbar,
3336
doubleSize: state.doubleSize, cbarLoc: state.cbarLoc, cbarNum: state.cbarNum, useCustomRes: state.useCustomRes,
3437
customRes: state.customRes, includeAxis: state.includeAxis, mainTitle: state.mainTitle, cbarLabel: state.cbarLabel,
3538
cbarUnits:state.cbarUnits, animate: state.animate, timeRate:state.timeRate, frames: state.frames, frameRate: state.frameRate,
36-
orbit: state.orbit, useTime:state.useTime, loopTime: state.loopTime, keyFrameEditor:state.keyFrameEditor,
39+
orbit: state.orbit, useTime:state.useTime, loopTime: state.loopTime, orbitDeg:state.orbitDeg, orbitDir:state.orbitDir,
3740
preview:state.preview, pingpong:state.pingpong
3841
})))
3942

4043
const {ExportImg, EnableExport, setIncludeBackground, setIncludeColorbar,
4144
setDoubleSize, setCbarLoc, setCbarNum, setUseCustomRes, setCustomRes, setIncludeAxis,
4245
setHideAxis, setHideAxisControls, setMainTitle, setCbarLabel, setAnimate,
4346
setFrames, setFrameRate, setTimeRate, setOrbit, setUseTime, setLoopTime, setKeyFrameEditor,
44-
setCbarUnits, setPingpong, setPreview, setPreviewExtent} = useImageExportStore.getState()
47+
setCbarUnits, setPingpong, setPreview, setPreviewExtent, setOrbitDeg} = useImageExportStore.getState()
4548

4649
interface CapitalizeFn {
4750
(str: string): string;
@@ -131,7 +134,7 @@ const ExportImageSettings = () => {
131134
/>
132135
</button>
133136
<Hider show={showSettings} className='col-span-2'>
134-
<div className="grid grid-cols-[auto_60px] items-center gap-2">
137+
<div className="grid grid-cols-[auto_60px] items-center gap-1">
135138
<label htmlFor="includeBG">Include Background</label>
136139
<Switch id='includeBG' checked={includeBackground} onCheckedChange={e => setIncludeBackground(e)}/>
137140
{plotType != 'sphere' &&
@@ -147,8 +150,8 @@ const ExportImageSettings = () => {
147150
<div className='col-span-2 flex justify-between'>
148151
<label htmlFor="colorbar-loc ">Colorbar <br/> Location</label>
149152
<div id='colorbar-loc'>
150-
<Select value={cbarLoc} onValueChange={e=>setCbarLoc(e)}>
151-
<SelectTrigger >
153+
<Select value={cbarLoc} onValueChange={e=>setCbarLoc(e)}>
154+
<SelectTrigger >
152155
<SelectValue placeholder={cbarLoc}/>
153156
</SelectTrigger>
154157
<SelectContent>
@@ -161,7 +164,7 @@ const ExportImageSettings = () => {
161164
</div>
162165
<div className="grid grid-cols-[auto_60px] items-center gap-2">
163166
<label htmlFor="cbarNum" >Number of Ticks</label>
164-
<Input id='cbarNum' type="number" min={0} max={20} step={1} value={cbarNum} onChange={e => setCbarNum(parseInt(e.target.value))}/>
167+
<Input className='h-[26px]' id='cbarNum' type="number" min={0} max={20} step={1} value={cbarNum} onChange={e => setCbarNum(parseInt(e.target.value))}/>
165168
</div>
166169
<div className='border-b my-2' />
167170
</Hider>
@@ -173,11 +176,11 @@ const ExportImageSettings = () => {
173176
<div className='grid grid-cols-[50%_50%] col-span-2 '>
174177
<div className='flex flex-col items-center'>
175178
<h1>Width</h1>
176-
<Input id='cbarNum' type="number" value={customRes[0]} onChange={e => setCustomRes([parseInt(e.target.value), customRes[1]])}/>
179+
<Input className='h-[26px]' id='cbarNum' type="number" value={customRes[0]} onChange={e => setCustomRes([parseInt(e.target.value), customRes[1]])}/>
177180
</div>
178181
<div className='flex flex-col items-center'>
179182
<h1>Height</h1>
180-
<Input id='cbarNum' type="number" value={customRes[1]} onChange={e => setCustomRes([customRes[0], parseInt(e.target.value)])}/>
183+
<Input className='h-[26px]' id='cbarNum' type="number" value={customRes[1]} onChange={e => setCustomRes([customRes[0], parseInt(e.target.value)])}/>
181184
</div>
182185
<Button className={`col-span-2 ${previewState ? 'bg-red-600' : ''}`}
183186
variant='outline'
@@ -216,24 +219,37 @@ const ExportImageSettings = () => {
216219
<Switch id='useAnimate' checked={animate} onCheckedChange={e => setAnimate(e)}/>
217220
{/* Animation Settings */}
218221
<Hider show={animate} className='col-span-2 '>
219-
<div className="grid grid-cols-[auto_80px] items-center gap-2 mb-2">
222+
<div className="grid grid-cols-[auto_80px] items-center gap-1 mb-2">
220223
<label htmlFor="frames">Frames</label>
221-
<Input id="frames" type='number' step={1} value={frames} onChange={e => setFrames(parseInt(e.target.value))} />
222-
224+
<Input className='h-[26px]' id="frames" type='number' step={1} value={frames} onChange={e => setFrames(parseInt(e.target.value))} />
223225
<label htmlFor="fps">FPS</label>
224-
<Input id="fps" type='number' step={1} value={frameRate} onChange={e => setFrameRate(parseInt(e.target.value))} />
226+
<Input className='h-[26px]' id="fps" type='number' step={1} value={frameRate} onChange={e => setFrameRate(parseInt(e.target.value))} />
225227
</div>
226228
<div className="grid grid-cols-[auto_60px] items-center gap-2">
227229

228-
229230
<label htmlFor="useOrbit">Orbit</label>
230231
<Switch id="useOrbit" checked={orbit} onCheckedChange={e=> setOrbit(e)} />
231232

232233
<Hider show={orbit} className='col-span-2'>
233-
<div className="grid grid-cols-[auto_60px] items-center gap-2 mb-2">
234+
<div className="grid grid-cols-[auto_80px] items-center gap-2 mb-2">
235+
<label htmlFor="orbitDeg">Orbit Degrees</label>
236+
<Input id="orbitDeg" type='number' step={1} value={orbitDeg} onChange={e => setOrbitDeg(parseInt(e.target.value))} />
237+
</div>
238+
<div className="grid grid-cols-[auto_60px] items-center gap-2 mb-2">
239+
<label htmlFor="orbitDir">Direction</label>
240+
<FaLongArrowAltRight id='orbitDir' onClick={()=>useImageExportStore.getState().flipOrbitDir()}
241+
style={{
242+
transform: `${orbitDir ? "rotate(180deg)" : ""}`,
243+
transition: ".25s",
244+
cursor:"pointer"
245+
}}
246+
size={26}
247+
/>
234248
<label htmlFor="useOrbit">Ping-Pong</label>
235249
<Switch id="useOrbit" checked={pingpong} onCheckedChange={e=> setPingpong(e)} />
236250
</div>
251+
252+
<div className='border-b my-2' />
237253
</Hider>
238254

239255
<label htmlFor="useTime">Animate Time</label>
@@ -258,11 +274,8 @@ const ExportImageSettings = () => {
258274
</Button>
259275

260276
</div>
261-
<div className="grid grid-cols-[auto_60px] items-center gap-2 my-2">
262-
<label htmlFor="usePreview">Export Preview</label>
263-
<Switch id="usePreview" checked={preview} onCheckedChange={e=> setPreview(e)} />
264-
</div>
265-
277+
<div className='my-2'/>
278+
<Switcher leftText='Preview' rightText='Final' state={preview} onClick={()=> setPreview(!preview)} />
266279
</Hider>
267280
</div>
268281
</Hider>

src/components/ui/KeyFrames.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import './css/KeyFrames.css'
88
import { TbDiamondsFilled } from "react-icons/tb";
99
import { Input } from './input';
1010
import { IoCloseCircleSharp } from "react-icons/io5";
11+
import { CiWarning } from "react-icons/ci";
1112

1213
function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
1314
return keys.reduce((acc, key) => {
@@ -93,10 +94,18 @@ const KeyFrames = () => {
9394
/>
9495
<div className='flex justify-between items-center'>
9596
{/* Information */}
96-
<div className='ml-4'>
97-
<div style={{visibility: orbit? "visible" : "hidden"}}>
98-
<b>Camera Motion overwriten by orbit</b>
97+
<div className={`ml-4 bg-[var(--warning-area)] min-w-[20%] px-4 py-2 rounded-md`}
98+
style={{
99+
visibility:(orbit || useTime) ? "visible" : "hidden"
100+
}}
101+
>
102+
<div style={{display: orbit? "flex" : "none", alignItems:"center", width:"100%", justifyContent:"space-between"}}>
103+
<CiWarning /><b> Camera Motion overwriten by orbit</b><CiWarning />
104+
</div>
105+
<div style={{display: (orbit && !keyFrames)? "flex" : "none", alignItems:"center", width:"100%", justifyContent:"space-between"}}>
106+
<CiWarning /><b>Keyframe required to preview orbit</b><CiWarning />
99107
</div>
108+
100109
</div>
101110

102111
{/* Buttons */}

src/components/ui/Switcher.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react'
2+
3+
export const Switcher = ({leftText, rightText, state, onClick} : {leftText: string, rightText: string, state: boolean, onClick: () => void}) => {
4+
5+
return (
6+
<div
7+
className='relative w-full text-center h-10 bg-primary rounded-full cursor-pointer mb-2 flex items-center justify-between px-4'
8+
onClick={onClick}
9+
>
10+
<span className={`z-10 font-semibold transition-colors ${state ? 'text-primary' : 'text-secondary'}`}>
11+
{leftText}
12+
</span>
13+
<span className={`z-10 font-semibold transition-colors ${!state ? 'text-primary' : 'text-secondary'}`}>
14+
{rightText}
15+
</span>
16+
<div
17+
className={`absolute top-1 h-8 w-[calc(50%-8px)] bg-secondary shadow-xs hover:bg-secondary/80 rounded-full transition-all duration-300 ${
18+
state ? 'left-1' : 'left-[calc(50%+4px)]'
19+
}`}
20+
/>
21+
</div>
22+
)
23+
}
24+

src/components/ui/css/KeyFrames.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.keyframes-container{
22
position: fixed;
33
width: 80%;
4+
max-width: 1200px;
45
left: 50%;
56
transform: translateX(-50%);
67
bottom: 10%;

src/utils/ExportCanvas.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as THREE from 'three'
88
import { useShallow } from 'zustand/shallow';
99
import { lerp } from 'three/src/math/MathUtils.js';
1010
import { FFmpeg } from '@ffmpeg/ffmpeg';
11-
11+
import { deg2rad } from './HelperFuncs';
1212
const DrawComposite = (
1313
compositeCanvas: HTMLCanvasElement,
1414
gl: THREE.WebGLRenderer,
@@ -27,7 +27,7 @@ const DrawComposite = (
2727

2828
ctx.imageSmoothingEnabled = true;
2929
ctx.imageSmoothingQuality = 'high';
30-
if (includeBackground) {
30+
if (includeBackground || animate) {
3131
ctx.fillStyle = bgColor;
3232
ctx.fillRect(0, 0, width, height);
3333
} else {
@@ -238,13 +238,14 @@ async function DrawTextOverlay(
238238

239239

240240
const ExportCanvas = ({show}:{show: boolean}) => {
241-
const {exportImg, enableExport, animate, frames, frameRate, useTime, timeRate, orbit, loopTime,
241+
const {exportImg, enableExport, animate, frames, frameRate, useTime, timeRate, orbit, orbitDeg, orbitDir, loopTime,
242242
preview, useCustomRes, customRes, doubleSize, setHideAxis, setHideAxisControls
243243
} = useImageExportStore(useShallow(state => ({
244244
exportImg: state.exportImg, enableExport:state.enableExport, animate:state.animate,
245245
frames:state.frames, frameRate:state.frameRate, useTime:state.useTime, timeRate:state.timeRate,
246-
orbit:state.orbit, loopTime:state.loopTime, preview:state.preview, useCustomRes:state.useCustomRes,
247-
customRes:state.customRes, doubleSize:state.doubleSize, setHideAxis:state.setHideAxis, setHideAxisControls:state.setHideAxisControls
246+
orbit:state.orbit, orbitDeg:state.orbitDeg, orbitDir:state.orbitDir, loopTime:state.loopTime, preview:state.preview,
247+
useCustomRes:state.useCustomRes, customRes:state.customRes, doubleSize:state.doubleSize,
248+
setHideAxis:state.setHideAxis, setHideAxisControls:state.setHideAxisControls
248249
})))
249250
const {setAnimProg, setQuality} = usePlotStore.getState()
250251
const {setStatus, setProgress} = useGlobalStore.getState()
@@ -330,7 +331,7 @@ const ExportCanvas = ({show}:{show: boolean}) => {
330331

331332
ffmpeg.on('progress', ({ progress, time }) => {
332333
// progress is a value between 0 and 1
333-
setProgress(Math.round(progress * 100));
334+
setProgress(Math.min(Math.round(progress * 100), 100));
334335
});
335336

336337
setStatus("Rendering Frames")
@@ -348,8 +349,8 @@ const ExportCanvas = ({show}:{show: boolean}) => {
348349
for (let frame=0; frame<frames; frame++){
349350
// ----- UPDATE VISUALS ---- //
350351
if (orbit){
351-
const angle = (frame / (frames+1)) * Math.PI * 2;
352-
const newAngle = originalAngle + angle;
352+
const angle = (frame / (frames+1)) * deg2rad(orbitDeg);
353+
const newAngle = originalAngle + (orbitDir ? -angle : angle);
353354
camera.position.x = radius * Math.sin(newAngle);
354355
camera.position.z = radius * Math.cos(newAngle);
355356
camera.lookAt(0, 0, 0);

src/utils/GlobalStates.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,8 @@ type ImageExportState = {
540540
frames: number;
541541
frameRate: number;
542542
orbit: boolean;
543+
orbitDeg: number;
544+
orbitDir: boolean;
543545
pingpong: boolean;
544546
useTime: boolean;
545547
timeRate: number;
@@ -576,6 +578,8 @@ type ImageExportState = {
576578
setFrames: (frames: number) => void;
577579
setFrameRate: (frameRate: number) => void;
578580
setOrbit: (orbit: boolean) => void;
581+
setOrbitDeg: (orbitDeg: number) => void;
582+
flipOrbitDir: () => void;
579583
setPingpong: (pingpong: boolean) => void;
580584
setUseTime: (useTime: boolean) => void;
581585
setTimeRate: (timeRate: number) => void;
@@ -610,6 +614,8 @@ export const useImageExportStore = create<ImageExportState>((set, get) => ({
610614
frames: 60,
611615
frameRate: 12,
612616
orbit: false,
617+
orbitDeg: 360,
618+
orbitDir: false,
613619
pingpong: false,
614620
useTime: false,
615621
timeRate: 12,
@@ -648,6 +654,8 @@ export const useImageExportStore = create<ImageExportState>((set, get) => ({
648654
setFrames: (frames) => set({ frames }),
649655
setFrameRate: (frameRate) => set({ frameRate }),
650656
setOrbit: (orbit) => set({ orbit }),
657+
setOrbitDeg: (orbitDeg) => set({ orbitDeg }),
658+
flipOrbitDir: () => set({ orbitDir: !get().orbitDir }),
651659
setPingpong: (pingpong) => set({ pingpong }),
652660
setUseTime: (useTime) => set({ useTime }),
653661
setTimeRate: (timeRate) => set({ timeRate }),

0 commit comments

Comments
 (0)