Skip to content

Commit 5e5392a

Browse files
committed
✨ refactor App
1 parent 10bcfa3 commit 5e5392a

File tree

16 files changed

+635
-196
lines changed

16 files changed

+635
-196
lines changed

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
height: 100vh;
2121
top: 0;
2222
left: 0;
23-
pointer-events: none;
23+
/* pointer-events: none; */
2424
}
2525
</style>
2626
</head>

src/App/App.tsx

Lines changed: 166 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,143 +1,214 @@
11
import * as React from "react";
22
import * as THREE from "three";
3-
import { Environment, useProgress } from "@react-three/drei";
43
import { GithubLogo } from "./Ui/GithubLogo";
5-
import { Canvas } from "@react-three/fiber";
4+
import { Canvas, useFrame, useThree } from "@react-three/fiber";
65
import { XR8Controls } from "../XR8Canvas/XR8Controls";
76
import { useXR8 } from "../XR8Canvas/useXR8";
8-
import { xr8Hosted } from "../XR8Canvas/getXR8";
7+
import { getXR8, loadXR8, xr8Hosted } from "../XR8Canvas/getXR8";
98
import { Game } from "./Game";
109
import { Dice } from "./Scene/Dice";
1110
// @ts-ignore
1211
import { Visualizer } from "react-touch-visualizer";
1312
import tunnel from "tunnel-rat";
1413
import { Ground } from "./Scene/Ground";
14+
import { WebXRControls } from "../WebXRCanvas/WebXRControls";
1515
import { createPortal } from "react-dom";
16+
import { XR8 } from "../XR8Canvas/XR8";
17+
import { Environment } from "./Scene/Environment";
18+
import { TrackingHint } from "./Ui/Hints/TrackingHint";
19+
import { useProgress } from "@react-three/drei";
20+
import { useIsWebXRSupported } from "../WebXRCanvas/useWebXRSession";
21+
import { useDelay } from "./Ui/useDelay";
22+
import { PageRules } from "./Ui/PageRules";
23+
import { LoadingScreen } from "./Ui/LoadingScreen";
1624

1725
// @ts-ignore
1826
const xr8ApiKey: string | undefined = import.meta.env.VITE_XR8_API_KEY;
1927
const touchSupported = "ontouchend" in document;
2028

21-
type Props = {
22-
started: boolean;
23-
onReady: () => void;
24-
onProgress?: (x: number, label: string) => void;
25-
};
29+
export const App = () => {
30+
const [state, setState] = React.useState<
31+
| { type: "loading" }
32+
| { type: "waiting-user-input" }
33+
| {
34+
type: "webXR";
35+
poseFound?: boolean;
36+
cameraFeedDisplayed?: boolean;
37+
webXRSession?: XRSession;
38+
}
39+
| {
40+
type: "xr8";
41+
poseFound?: boolean;
42+
cameraFeedDisplayed?: boolean;
43+
xr8?: XR8;
44+
}
45+
| { type: "flat" }
46+
>({ type: "waiting-user-input" });
47+
48+
const uiTunnel = React.useMemo(tunnel, []);
2649

27-
export const App = ({ onReady, onProgress, started }: Props) => {
2850
const [error, setError] = React.useState<Error>();
2951
if (error) throw error;
3052

31-
const [xr8Ready, setXr8Ready] = React.useState(false);
53+
const startWebXR = () => {
54+
setState({ type: "webXR" });
3255

33-
const xr8Supported = (xr8ApiKey || xr8Hosted) && touchSupported;
56+
// this call must be made after a user input
57+
return navigator.xr
58+
?.requestSession("immersive-ar", {
59+
optionalFeatures: ["dom-overlay", "local-floor"],
60+
domOverlay: { root: document.getElementById("overlay")! },
61+
})
62+
.then((webXRSession) =>
63+
setState({
64+
type: "webXR",
65+
webXRSession,
66+
})
67+
)
68+
.catch(setError);
69+
};
3470

35-
const xr8 = xr8Supported ? useXR8(xr8ApiKey) : null;
71+
const startXR8 = () => {
72+
setState({ type: "xr8" });
73+
loadXR8(xr8ApiKey)
74+
.then((xr8) => setState({ type: "xr8", xr8 }))
75+
.catch(setError);
76+
};
3677

37-
const { active, progress, total } = useProgress();
38-
const ready = (!xr8Supported || xr8Ready) && !active;
78+
const startFlat = () => setState({ type: "flat" });
3979

40-
let progressValue = total > 1 ? progress / 100 : 0;
41-
let progressLabel = "loading assets";
80+
const webXRSupported = useIsWebXRSupported();
4281

43-
if (xr8Supported) {
44-
if (progressValue < 1) {
45-
progressValue *= 0.6;
46-
} else {
47-
if (!xr8) {
48-
progressValue = 0.6;
49-
progressLabel = "loading xr8 library";
50-
} else {
51-
progressValue = 0.8;
52-
progressLabel = "tracking in progress";
53-
}
54-
}
55-
}
82+
const xr8Supported = (!!xr8ApiKey || xr8Hosted) && touchSupported;
5683

57-
React.useEffect(
58-
() => void onProgress?.(progressValue, progressLabel),
59-
[progressValue, progressLabel]
60-
);
61-
React.useEffect(() => void (ready && onReady()), [ready]);
84+
const sceneAssetLoaded = useProgress(({ active }) => !active);
6285

63-
const uiTunnel = React.useMemo(tunnel, []);
86+
const readyForRender =
87+
sceneAssetLoaded &&
88+
(state.type === "flat" ||
89+
(state.type === "webXR" && state.cameraFeedDisplayed) ||
90+
(state.type === "xr8" && state.cameraFeedDisplayed));
91+
92+
const readyForGame =
93+
readyForRender &&
94+
!(state.type === "webXR" && !state.poseFound) &&
95+
!(state.type === "xr8" && !state.cameraFeedDisplayed);
96+
97+
const hint = useDelay(readyForRender && !readyForGame && "tracking", 2500);
98+
99+
if (webXRSupported === "loading") return null;
100+
101+
if (state.type === "loading") return null;
64102

65103
return (
66104
<>
67-
{false && <Visualizer />}
68-
69105
<Canvas
70-
camera={{ position: new THREE.Vector3(0, 6, 6) }}
106+
camera={{ position: new THREE.Vector3(0, 6, 6), near: 0.1, far: 1000 }}
71107
shadows
72108
style={{
73109
position: "fixed",
74110
top: 0,
75111
left: 0,
76112
right: 0,
77113
bottom: 0,
78-
opacity: started ? 1 : 0,
79114
touchAction: "none",
115+
opacity: readyForRender ? 1 : 0,
80116
}}
81117
>
82-
<ErrorBoundary onError={setError}>
83-
{xr8 && <XR8Controls xr8={xr8} onReady={() => setXr8Ready(true)} />}
84-
85-
<React.Suspense fallback={null}>
86-
<Environment path={"assets/"} files={"lebombo_1k.hdr"} />
87-
88-
{started && <Game UiPortal={uiTunnel.In} />}
89-
90-
{active && <Dice value={1} /> /* ensure the model is loaded */}
91-
</React.Suspense>
92-
93-
<directionalLight position={[10, 8, 6]} intensity={0} castShadow />
94-
95-
<Ground />
96-
</ErrorBoundary>
118+
{state.type === "xr8" && state.xr8 && (
119+
<XR8Controls
120+
xr8={state.xr8}
121+
onPoseFound={() => setState((s) => ({ ...s, poseFound: true }))}
122+
onCameraFeedDisplayed={() =>
123+
setState((s) => ({ ...s, cameraFeedDisplayed: true }))
124+
}
125+
/>
126+
)}
127+
128+
{state.type === "webXR" && state.webXRSession && (
129+
<WebXRControls
130+
worldSize={8}
131+
webXRSession={state.webXRSession}
132+
onPoseFound={() => setState((s) => ({ ...s, poseFound: true }))}
133+
onCameraFeedDisplayed={() =>
134+
setState((s) => ({ ...s, cameraFeedDisplayed: true }))
135+
}
136+
/>
137+
)}
138+
139+
<React.Suspense fallback={null}>
140+
<Environment />
141+
142+
{
143+
/* preload the dice model */
144+
!readyForGame && (
145+
<Dice
146+
position={[999, 999, 9999]}
147+
scale={[0.0001, 0.0001, 0.0001]}
148+
/>
149+
)
150+
}
151+
152+
{readyForGame && <Game UiPortal={uiTunnel.In} />}
153+
</React.Suspense>
154+
155+
<directionalLight position={[10, 8, 6]} intensity={0} castShadow />
156+
157+
<Ground />
97158
</Canvas>
98159

99-
{createPortal(
100-
<>
101-
<a href="https://github.com/platane/yAR-htzee" title="github">
102-
<button
103-
style={{
104-
position: "absolute",
105-
width: "40px",
106-
height: "40px",
107-
bottom: "10px",
108-
right: "10px",
109-
pointerEvents: "auto",
110-
zIndex: 1,
111-
}}
112-
>
113-
<GithubLogo />
114-
</button>
115-
</a>
116-
117-
{React.createElement(uiTunnel.Out)}
118-
</>,
119-
document.getElementById("overlay")!
120-
)}
160+
<OverlayPortal>
161+
{false && <Visualizer />}
162+
163+
<a href="https://github.com/platane/yAR-htzee" title="github">
164+
<button
165+
style={{
166+
position: "absolute",
167+
width: "40px",
168+
height: "40px",
169+
bottom: "10px",
170+
right: "10px",
171+
pointerEvents: "auto",
172+
zIndex: 1,
173+
}}
174+
>
175+
<GithubLogo />
176+
</button>
177+
</a>
178+
179+
{React.createElement(uiTunnel.Out)}
180+
181+
{hint === "tracking" && <TrackingHint />}
182+
183+
{!readyForRender && (
184+
<Over>
185+
<LoadingScreen
186+
loading={state.type !== "waiting-user-input"}
187+
onStartFlat={startFlat}
188+
onStartWebXR={webXRSupported && startWebXR}
189+
onStartXR8={xr8Supported && startXR8}
190+
/>
191+
</Over>
192+
)}
193+
</OverlayPortal>
121194
</>
122195
);
123196
};
124197

125-
class ErrorBoundary extends React.Component<{
126-
onError: (error: Error) => void;
127-
children?: any;
128-
}> {
129-
static getDerivedStateFromError = (error: Error) => ({ error });
130-
131-
state: { error?: Error } = {};
132-
133-
componentDidCatch(error: Error) {
134-
this.props.onError(error);
135-
}
136-
137-
render() {
138-
if (this.state.error) return null;
139-
return this.props.children;
140-
}
141-
}
142-
143-
export default App;
198+
const OverlayPortal = ({ children }: { children?: any }) =>
199+
createPortal(children, document.getElementById("overlay")!);
200+
201+
const Over = ({ children }: { children?: any }) => (
202+
<div
203+
style={{
204+
position: "fixed",
205+
top: 0,
206+
left: 0,
207+
width: "100%",
208+
height: "100%",
209+
backgroundColor: "white",
210+
}}
211+
>
212+
{children}
213+
</div>
214+
);

src/App/Boot.tsx

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

0 commit comments

Comments
 (0)