diff --git a/src/components/three/world/SkyModel.tsx b/src/components/three/world/SkyModel.tsx
index 56fd5eb..a3e89cd 100644
--- a/src/components/three/world/SkyModel.tsx
+++ b/src/components/three/world/SkyModel.tsx
@@ -1,34 +1,126 @@
import { useFrame, useThree } from "@react-three/fiber";
import { useGLTF } from "@react-three/drei";
-import { useRef } from "react";
+import { Component, useMemo, useRef, type ReactNode } from "react";
import * as THREE from "three";
-import { useClonedObject } from "@/hooks/three/useClonedObject";
import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF";
interface SkyModelProps {
modelPath: string;
+ fallbackModelPath?: string | undefined;
+ fallbackScale?: number | undefined;
+ scale?: number | undefined;
+}
+
+interface SkyModelContentProps {
+ modelPath: string;
+ scale: number;
+}
+
+interface SkyModelErrorBoundaryProps {
+ children: ReactNode;
+ fallback: ReactNode;
+}
+
+interface SkyModelErrorBoundaryState {
+ hasError: boolean;
}
const SKY_MODEL_SCALE = 1;
+const SKY_MODEL_RENDER_ORDER = -1000;
+const LEGACY_SKY_MODEL_PATH = "/models/sky/model.glb";
-export function SkyModel({ modelPath }: SkyModelProps): React.JSX.Element {
+class SkyModelErrorBoundary extends Component<
+ SkyModelErrorBoundaryProps,
+ SkyModelErrorBoundaryState
+> {
+ constructor(props: SkyModelErrorBoundaryProps) {
+ super(props);
+ this.state = { hasError: false };
+ }
+
+ static getDerivedStateFromError(): SkyModelErrorBoundaryState {
+ return { hasError: true };
+ }
+
+ render(): ReactNode {
+ if (this.state.hasError) {
+ return this.props.fallback;
+ }
+
+ return this.props.children;
+ }
+}
+
+export function SkyModel({
+ fallbackModelPath,
+ fallbackScale = SKY_MODEL_SCALE,
+ modelPath,
+ scale = SKY_MODEL_SCALE,
+}: SkyModelProps): React.JSX.Element {
+ const fallback = fallbackModelPath ? (
+
+ ) : null;
+
+ return (
+
+
+
+ );
+}
+
+function SkyModelContent({
+ modelPath,
+ scale,
+}: SkyModelContentProps): React.JSX.Element {
const camera = useThree((state) => state.camera);
const groupRef = useRef(null);
const { scene } = useLoggedGLTF(modelPath, {
scope: "SkyModel",
- scale: SKY_MODEL_SCALE,
+ scale,
});
- const model = useClonedObject(scene);
+ const model = useMemo(() => createSkyModel(scene), [scene]);
useFrame(() => {
groupRef.current?.position.copy(camera.position);
});
return (
-
+
);
}
-useGLTF.preload("/models/sky/model.glb");
+function createSkyModel(scene: THREE.Object3D): THREE.Object3D {
+ const model = scene.clone(true);
+
+ model.traverse((object) => {
+ object.frustumCulled = false;
+ object.renderOrder = SKY_MODEL_RENDER_ORDER;
+
+ if (!(object instanceof THREE.Mesh)) return;
+
+ object.material = Array.isArray(object.material)
+ ? object.material.map(createSkyMaterial)
+ : createSkyMaterial(object.material);
+ });
+
+ return model;
+}
+
+function createSkyMaterial(material: T): T {
+ const skyMaterial = material.clone();
+ skyMaterial.side = THREE.BackSide;
+ skyMaterial.depthTest = false;
+ skyMaterial.depthWrite = false;
+
+ return skyMaterial as T;
+}
+
+useGLTF.preload("/models/skybox/skybox.gltf");
+useGLTF.preload(LEGACY_SKY_MODEL_PATH);
diff --git a/src/data/world/environmentConfig.ts b/src/data/world/environmentConfig.ts
index f5f1222..bf287f7 100644
--- a/src/data/world/environmentConfig.ts
+++ b/src/data/world/environmentConfig.ts
@@ -1,2 +1,5 @@
-export const GAME_SCENE_SKY_MODEL_PATH = "/models/sky/model.glb";
+export const GAME_SCENE_SKY_MODEL_PATH = "/models/skybox/skybox.gltf";
+export const GAME_SCENE_FALLBACK_SKY_MODEL_PATH = "/models/sky/model.glb";
+export const GAME_SCENE_SKY_MODEL_SCALE = 300;
+export const GAME_SCENE_FALLBACK_SKY_MODEL_SCALE = 1;
export const PHYSICS_SCENE_BACKGROUND_COLOR = "#0b1018";
diff --git a/src/world/Environment.tsx b/src/world/Environment.tsx
index 152234f..a9a7627 100644
--- a/src/world/Environment.tsx
+++ b/src/world/Environment.tsx
@@ -1,5 +1,8 @@
import {
+ GAME_SCENE_FALLBACK_SKY_MODEL_PATH,
+ GAME_SCENE_FALLBACK_SKY_MODEL_SCALE,
GAME_SCENE_SKY_MODEL_PATH,
+ GAME_SCENE_SKY_MODEL_SCALE,
PHYSICS_SCENE_BACKGROUND_COLOR,
} from "@/data/world/environmentConfig";
import { useSceneMode } from "@/hooks/debug/useSceneMode";
@@ -14,5 +17,12 @@ export function Environment(): React.JSX.Element {
);
}
- return ;
+ return (
+
+ );
}