Skip to content

Commit e159ac7

Browse files
committed
cpu demo
1 parent f7721f7 commit e159ac7

9 files changed

Lines changed: 349 additions & 274 deletions

File tree

packages/playground/src/App.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import { ModulesSidebar } from "./components/ModulesSidebar";
99
import { Toolbar } from "./components/ToolBar";
1010
import { SystemSidebar } from "./components/SystemSidebar";
1111
import { GlobalHotkeys } from "./components/GlobalHotkeys";
12-
import { GyroscopeDebugLabel } from "./components/GyroscopeDebugLabel";
1312
import { Homepage } from "./components/Homepage";
1413
import { Stats } from "./components/Stats";
14+
import { CpuGyroDemoController } from "./components/CpuGyroDemoController";
1515
import { useUI } from "./hooks/useUI";
1616
import { useRender } from "./hooks/useRender";
1717
import { useDemo } from "./hooks/useDemo";
@@ -36,7 +36,7 @@ import "./App.css";
3636
function AppContent() {
3737
const { barsVisible, restoreBarsFromFullscreenMode, setBarsVisibility } = useUI();
3838
const { invertColors, setInvertColors } = useRender();
39-
const { hasStarted, isPlaying: isDemoPlaying, stop, play: playDemo, gyroData } = useDemo();
39+
const { hasStarted, isPlaying: isDemoPlaying, stop, play: playDemo } = useDemo();
4040
const { spawnParticles, play: playEngine } = useEngine();
4141
const { clearModuleOscillators } = useOscillators();
4242
const { setIsResetting } = useReset();
@@ -167,6 +167,7 @@ function AppContent() {
167167
className={`app ${barsVisible && hasStarted ? "bars-visible" : "bars-hidden"
168168
}`}
169169
>
170+
<CpuGyroDemoController isHomepage={isHomepage} />
170171
<TopBar
171172
isDemoPlaying={isDemoPlaying}
172173
stopDemo={stop}
@@ -184,7 +185,6 @@ function AppContent() {
184185
<Canvas className="canvas" isPlaying={isDemoPlaying} />
185186
<Overlay isPlaying={isDemoPlaying} />
186187
<Homepage onPlay={handlePlay} isVisible={isHomepage} />
187-
<GyroscopeDebugLabel gyroData={gyroData} />
188188
<Stats isVisible={isHomepage} />
189189
</div>
190190
</div>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { useCallback, useEffect, useMemo, useState } from "react";
2+
import { useEngine } from "../hooks/useEngine";
3+
import { useEnvironment } from "../hooks/modules/useEnvironment";
4+
import { GyroscopePermissionBanner } from "./GyroscopePermissionBanner";
5+
import { useGyroscope } from "../hooks/useGyroscope";
6+
import { isMobileDevice } from "../utils/deviceCapabilities";
7+
8+
/**
9+
* CPU fallback demo controller:
10+
* - Only active on the homepage when WebGPU is not available
11+
* - Uses device orientation (gyroscope) to drive gravity direction
12+
* - Shows a small top banner to request permission (iOS) only when needed
13+
*/
14+
export function CpuGyroDemoController(props: { isHomepage: boolean }) {
15+
const { isHomepage } = props;
16+
const { isWebGPU, isInitialized, isInitializing, spawnParticles } = useEngine();
17+
const { setGravityStrength, setMode, setCustomAngleDegrees } = useEnvironment();
18+
const [hasRequestedPermission, setHasRequestedPermission] = useState<boolean> (false);
19+
20+
const enabled = useMemo(
21+
() => isHomepage && isInitialized && !isInitializing && !isWebGPU,
22+
[isHomepage, isInitialized, isInitializing, isWebGPU]
23+
);
24+
25+
const gyro = useGyroscope({ enabled });
26+
27+
// Configure CPU fallback "demo" gravity settings once when enabled.
28+
useEffect(() => {
29+
if (!enabled) return;
30+
setGravityStrength(1000);
31+
setMode("custom");
32+
setCustomAngleDegrees(90);
33+
setTimeout(() => {
34+
spawnParticles({
35+
numParticles: isMobileDevice() ? 600 : 1000,
36+
shape: "circle",
37+
spacing: 0,
38+
particleSize: 5,
39+
radius: 100,
40+
})
41+
}, 16);
42+
}, [enabled, setGravityStrength, setMode, setCustomAngleDegrees,spawnParticles]);
43+
44+
// Drive gravity direction from gyroscope angle.
45+
useEffect(() => {
46+
if (!enabled) return;
47+
if (!gyro.data) return;
48+
setCustomAngleDegrees(gyro.data.angle);
49+
}, [enabled, gyro.data, setCustomAngleDegrees]);
50+
51+
const shouldShowBanner =
52+
enabled &&
53+
gyro.requiresPermission &&
54+
!hasRequestedPermission &&
55+
gyro.permissionState !== "granted" &&
56+
gyro.permissionState !== "unsupported" &&
57+
gyro.permissionState !== "insecure_context"
58+
&& gyro.isSecureContext;
59+
60+
61+
62+
const onRequestPermission = useCallback(() => {
63+
gyro
64+
.requestPermission()
65+
.catch((err: unknown) => {
66+
const msg =
67+
err instanceof Error ? err.message : "Failed to request permission.";
68+
alert(msg);
69+
}).then(() => {
70+
setHasRequestedPermission(true);
71+
});
72+
}, [gyro]);
73+
74+
return (
75+
<GyroscopePermissionBanner
76+
isVisible={shouldShowBanner}
77+
onRequestPermission={onRequestPermission}
78+
message="Enable Gyroscope"
79+
/>
80+
81+
);
82+
}
83+
84+

packages/playground/src/components/GyroscopeDebugLabel.css

Lines changed: 0 additions & 21 deletions
This file was deleted.

packages/playground/src/components/GyroscopeDebugLabel.tsx

Lines changed: 0 additions & 75 deletions
This file was deleted.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
.gyro-banner {
2+
position: absolute;
3+
top: 0;
4+
z-index: 500;
5+
width: 100%;
6+
background: rgba(20, 20, 20, 0.92);
7+
color: #fff;
8+
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
9+
backdrop-filter: blur(8px);
10+
}
11+
12+
.gyro-banner__content {
13+
display: flex;
14+
align-items: center;
15+
justify-content: space-between;
16+
gap: 12px;
17+
padding: 10px 12px;
18+
}
19+
20+
.gyro-banner__text {
21+
font-size: 13px;
22+
line-height: 1.2;
23+
opacity: 0.95;
24+
}
25+
26+
.gyro-banner__button {
27+
font-size: 12px;
28+
padding: 8px 10px;
29+
border-radius: 8px;
30+
border: 1px solid rgba(255, 255, 255, 0.2);
31+
background: rgba(255, 255, 255, 0.12);
32+
color: #fff;
33+
cursor: pointer;
34+
}
35+
36+
.gyro-banner__button:hover {
37+
background: rgba(255, 255, 255, 0.18);
38+
}
39+
40+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import "./GyroscopePermissionBanner.css";
2+
3+
export function GyroscopePermissionBanner(props: {
4+
isVisible: boolean;
5+
onRequestPermission: () => void;
6+
message?: string;
7+
}) {
8+
const { isVisible, onRequestPermission, message } = props;
9+
10+
if (!isVisible) return null;
11+
12+
return (
13+
<div className="gyro-banner" role="region" aria-label="Gyroscope permission">
14+
<div className="gyro-banner__content">
15+
<div className="gyro-banner__text">
16+
{message ?? "Enable gyroscope for tilt controls."}
17+
</div>
18+
<button className="gyro-banner__button" onClick={onRequestPermission}>
19+
Enable
20+
</button>
21+
</div>
22+
</div>
23+
);
24+
}
25+
26+

packages/playground/src/components/Homepage.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ interface HomepageProps {
1313
export function Homepage({ onPlay, isVisible }: HomepageProps) {
1414
const [showWarning, setShowWarning] = useState(false);
1515
const isMobile = isMobileDeviceSync();
16-
const { canvasRef, screenToWorld } = useEngine();
16+
const { canvasRef, screenToWorld, isWebGPU } = useEngine();
1717
const { setPosition, setActive, setMode, setStrength, setRadius } = useInteraction();
1818
const isMouseDownRef = useRef(false);
1919

@@ -31,8 +31,13 @@ export function Homepage({ onPlay, isVisible }: HomepageProps) {
3131

3232
// Configure interaction for homepage demo
3333
setMode("attract");
34-
setStrength(50000);
35-
setRadius(400);
34+
if (isWebGPU) {
35+
setStrength(50000);
36+
setRadius(400);
37+
} else {
38+
setStrength(isMobile ? 5000 : 5000);
39+
setRadius(isMobile ? 400 : 400);
40+
}
3641

3742
const handleMouseDown = (e: MouseEvent) => {
3843
// Only handle left mouse button
@@ -102,7 +107,7 @@ export function Homepage({ onPlay, isVisible }: HomepageProps) {
102107
setActive(false);
103108
isMouseDownRef.current = false;
104109
};
105-
}, [isVisible, showWarning, canvasRef, screenToWorld, setPosition, setActive, setMode, setStrength, setRadius]);
110+
}, [isVisible, showWarning, canvasRef, screenToWorld, setPosition, setActive, setMode, setStrength, setRadius, isWebGPU, isMobile]);
106111

107112
if (!isVisible) return null;
108113

0 commit comments

Comments
 (0)