-
Limites Actuelles (worldBounds):
-
+
+
+ Limites Actuelles (worldBounds):
+
+
Calcul...
-
- *Si vous décadrez à la souris, vous devrez copier ces valeurs exactes dans la prop
worldBounds de votre composant
EbikeGPSMap !
-
- Astuce : Utilisez le
Cadrage Automatique pour ne rien avoir à configurer.
+
+ *Si vous décadrez à la souris, vous devrez copier ces valeurs
+ exactes dans la prop worldBounds de votre composant{" "}
+ EbikeGPSMap !
+
+
+ Astuce : Utilisez le Cadrage Automatique pour ne rien avoir à
+ configurer.
diff --git a/src/pages/docs/animation/page.tsx b/src/pages/docs/animation/page.tsx
index af53225..ac456f6 100644
--- a/src/pages/docs/animation/page.tsx
+++ b/src/pages/docs/animation/page.tsx
@@ -5,7 +5,6 @@ export function DocsAnimationPage(): React.JSX.Element {
return (
diff --git a/src/pages/docs/architecture/page.tsx b/src/pages/docs/architecture/page.tsx
index 99a3b6d..13b7e9c 100644
--- a/src/pages/docs/architecture/page.tsx
+++ b/src/pages/docs/architecture/page.tsx
@@ -5,7 +5,6 @@ export function DocsArchitecturePage(): React.JSX.Element {
return (
diff --git a/src/pages/docs/audio/page.tsx b/src/pages/docs/audio/page.tsx
index cb53011..4392f5f 100644
--- a/src/pages/docs/audio/page.tsx
+++ b/src/pages/docs/audio/page.tsx
@@ -3,11 +3,6 @@ import { DocsDocument } from "@/components/docs/DocsDocument";
export function DocsAudioPage(): React.JSX.Element {
return (
-
+
);
}
diff --git a/src/pages/docs/code-review/page.tsx b/src/pages/docs/code-review/page.tsx
index 41682bd..47dc54c 100644
--- a/src/pages/docs/code-review/page.tsx
+++ b/src/pages/docs/code-review/page.tsx
@@ -3,11 +3,6 @@ import { DocsDocument } from "@/components/docs/DocsDocument";
export function DocsCodeReviewPage(): React.JSX.Element {
return (
-
+
);
}
diff --git a/src/pages/docs/editor/page.tsx b/src/pages/docs/editor/page.tsx
index 57712b9..1befe52 100644
--- a/src/pages/docs/editor/page.tsx
+++ b/src/pages/docs/editor/page.tsx
@@ -2,12 +2,5 @@ import editor from "../../../../docs/user/editor.md?raw";
import { DocsDocument } from "@/components/docs/DocsDocument";
export function DocsEditorPage(): React.JSX.Element {
- return (
-
- );
+ return ;
}
diff --git a/src/pages/docs/features/page.tsx b/src/pages/docs/features/page.tsx
index 61a026a..89fed18 100644
--- a/src/pages/docs/features/page.tsx
+++ b/src/pages/docs/features/page.tsx
@@ -2,12 +2,5 @@ import features from "../../../../docs/user/features.md?raw";
import { DocsDocument } from "@/components/docs/DocsDocument";
export function DocsFeaturesPage(): React.JSX.Element {
- return (
-
- );
+ return ;
}
diff --git a/src/pages/docs/hand-tracking/page.tsx b/src/pages/docs/hand-tracking/page.tsx
index bafa44f..b07dd6f 100644
--- a/src/pages/docs/hand-tracking/page.tsx
+++ b/src/pages/docs/hand-tracking/page.tsx
@@ -5,7 +5,6 @@ export function DocsHandTrackingPage(): React.JSX.Element {
return (
diff --git a/src/pages/docs/interaction/page.tsx b/src/pages/docs/interaction/page.tsx
index f9ae53b..7d97917 100644
--- a/src/pages/docs/interaction/page.tsx
+++ b/src/pages/docs/interaction/page.tsx
@@ -3,11 +3,6 @@ import { DocsDocument } from "@/components/docs/DocsDocument";
export function DocsInteractionPage(): React.JSX.Element {
return (
-
+
);
}
diff --git a/src/pages/docs/main-feature/page.tsx b/src/pages/docs/main-feature/page.tsx
index f094d52..229fb06 100644
--- a/src/pages/docs/main-feature/page.tsx
+++ b/src/pages/docs/main-feature/page.tsx
@@ -2,12 +2,5 @@ import mainFeature from "../../../../docs/user/main-feature.md?raw";
import { DocsDocument } from "@/components/docs/DocsDocument";
export function DocsMainFeaturePage(): React.JSX.Element {
- return (
-
- );
+ return ;
}
diff --git a/src/pages/docs/map-performance/page.tsx b/src/pages/docs/map-performance/page.tsx
new file mode 100644
index 0000000..db8f1b7
--- /dev/null
+++ b/src/pages/docs/map-performance/page.tsx
@@ -0,0 +1,8 @@
+import mapPerformance from "../../../../docs/technical/map-performance.md?raw";
+import { DocsDocument } from "@/components/docs/DocsDocument";
+
+export function DocsMapPerformancePage(): React.JSX.Element {
+ return (
+
+ );
+}
diff --git a/src/pages/docs/page.tsx b/src/pages/docs/page.tsx
index e3d3d7f..1de5e50 100644
--- a/src/pages/docs/page.tsx
+++ b/src/pages/docs/page.tsx
@@ -2,12 +2,5 @@ import readme from "../../../README.md?raw";
import { DocsDocument } from "@/components/docs/DocsDocument";
export function DocsReadmePage(): React.JSX.Element {
- return (
-
- );
+ return ;
}
diff --git a/src/pages/docs/repair-game/page.tsx b/src/pages/docs/repair-game/page.tsx
index 130e593..faf5028 100644
--- a/src/pages/docs/repair-game/page.tsx
+++ b/src/pages/docs/repair-game/page.tsx
@@ -2,12 +2,5 @@ import repairGame from "../../../../docs/technical/repair-game.md?raw";
import { DocsDocument } from "@/components/docs/DocsDocument";
export function DocsRepairGamePage(): React.JSX.Element {
- return (
-
- );
+ return ;
}
diff --git a/src/pages/docs/scene-runtime/page.tsx b/src/pages/docs/scene-runtime/page.tsx
index 3d2c267..50f6fa2 100644
--- a/src/pages/docs/scene-runtime/page.tsx
+++ b/src/pages/docs/scene-runtime/page.tsx
@@ -3,11 +3,6 @@ import { DocsDocument } from "@/components/docs/DocsDocument";
export function DocsSceneRuntimePage(): React.JSX.Element {
return (
-
+
);
}
diff --git a/src/pages/docs/target-architecture/page.tsx b/src/pages/docs/target-architecture/page.tsx
index 1f44783..95f6b2e 100644
--- a/src/pages/docs/target-architecture/page.tsx
+++ b/src/pages/docs/target-architecture/page.tsx
@@ -5,7 +5,6 @@ export function DocsTargetArchitecturePage(): React.JSX.Element {
return (
diff --git a/src/pages/docs/technical-editor/page.tsx b/src/pages/docs/technical-editor/page.tsx
index 3620ebd..af22a06 100644
--- a/src/pages/docs/technical-editor/page.tsx
+++ b/src/pages/docs/technical-editor/page.tsx
@@ -5,7 +5,6 @@ export function DocsTechnicalEditorPage(): React.JSX.Element {
return (
diff --git a/src/pages/docs/three-debugging/page.tsx b/src/pages/docs/three-debugging/page.tsx
index 9324f9b..38848ae 100644
--- a/src/pages/docs/three-debugging/page.tsx
+++ b/src/pages/docs/three-debugging/page.tsx
@@ -3,11 +3,6 @@ import { DocsDocument } from "@/components/docs/DocsDocument";
export function DocsThreeDebuggingPage(): React.JSX.Element {
return (
-
+
);
}
diff --git a/src/pages/docs/zustand/page.tsx b/src/pages/docs/zustand/page.tsx
index bd22028..44094d0 100644
--- a/src/pages/docs/zustand/page.tsx
+++ b/src/pages/docs/zustand/page.tsx
@@ -2,12 +2,5 @@ import zustand from "../../../../docs/technical/zustand.md?raw";
import { DocsDocument } from "@/components/docs/DocsDocument";
export function DocsZustandPage(): React.JSX.Element {
- return (
-
- );
+ return ;
}
diff --git a/src/pages/editor/page.tsx b/src/pages/editor/page.tsx
index 0f8c5b1..26145bd 100644
--- a/src/pages/editor/page.tsx
+++ b/src/pages/editor/page.tsx
@@ -1,52 +1,56 @@
-import { Suspense, useCallback, useEffect, useState } from "react";
-import { Canvas } from "@react-three/fiber";
-import { useProgress } from "@react-three/drei";
+import { useCallback, useEffect, useState } from "react";
+import { Canvas, useThree } from "@react-three/fiber";
import { EditorControls } from "@/components/editor/EditorControls";
import { EditorScene } from "@/components/editor/scene/EditorScene";
-import type { EditorCinematicPreviewRequest } from "@/components/editor/scene/EditorScene";
import { SceneLoadingOverlay } from "@/components/ui/SceneLoadingOverlay";
import { Subtitles } from "@/components/ui/Subtitles";
import { useEditorHistory } from "@/hooks/editor/useEditorHistory";
import type { CinematicDefinition } from "@/types/cinematics/cinematics";
import { useEditorSceneData } from "@/hooks/editor/useEditorSceneData";
-import type { MapNode, SceneData, TransformMode } from "@/types/editor/editor";
+import type {
+ EditorCinematicPreviewRequest,
+ MapNode,
+ TransformMode,
+} from "@/types/editor/editor";
+import type { SceneLoadingState } from "@/types/world/sceneLoading";
+import { logger } from "@/utils/core/Logger";
import {
- INITIAL_SCENE_LOADING_STATE,
- type SceneLoadingChangeHandler,
- type SceneLoadingState,
-} from "@/types/world/sceneLoading";
+ addTreeNode,
+ createNewMapNode,
+ mergeFlatNodeTransformsIntoTree,
+ removeEditorMetadata,
+ removeTreeNodeAtPath,
+ serializeMapNodes,
+ updateSceneDataTree,
+ updateTreeNodeAtPath,
+} from "@/utils/editor/editorMapTree";
const SAVE_ERROR_MESSAGE = "Erreur lors de l'enregistrement";
+const DEFAULT_NEW_NODE_NAME = "new-model";
-interface EditorSceneLoadingTrackerProps {
- onLoadingStateChange: SceneLoadingChangeHandler;
-}
-
-function serializeMapNodes(sceneData: SceneData): string {
- return JSON.stringify(sceneData.mapNodes, null, 2);
-}
-
-function EditorSceneLoadingTracker({
- onLoadingStateChange,
-}: EditorSceneLoadingTrackerProps): null {
- const { active, progress } = useProgress();
+function EditorWebGLContextLogger(): null {
+ const gl = useThree((state) => state.gl);
useEffect(() => {
- if (active) {
- onLoadingStateChange({
- currentStep: "Importation des models",
- progress: 0.2 + (progress / 100) * 0.7,
- status: "loading",
- });
- return;
- }
+ gl.setClearColor("#050505");
- onLoadingStateChange({
- currentStep: "Gameplay prêt",
- progress: 1,
- status: "ready",
- });
- }, [active, onLoadingStateChange, progress]);
+ const canvas = gl.domElement;
+ const handleContextLost = (event: Event) => {
+ event.preventDefault();
+ logger.error("WebGL", "Context lost - GPU resources exhausted");
+ };
+ const handleContextRestored = () => {
+ logger.info("WebGL", "Context restored");
+ };
+
+ canvas.addEventListener("webglcontextlost", handleContextLost);
+ canvas.addEventListener("webglcontextrestored", handleContextRestored);
+
+ return () => {
+ canvas.removeEventListener("webglcontextlost", handleContextLost);
+ canvas.removeEventListener("webglcontextrestored", handleContextRestored);
+ };
+ }, [gl]);
return null;
}
@@ -63,40 +67,33 @@ export function EditorPage(): React.JSX.Element {
const [selectedNodeIndex, setSelectedNodeIndex] = useState(
null,
);
+ const [selectedNodeIndexes, setSelectedNodeIndexes] = useState([]);
const [hoveredNodeIndex, setHoveredNodeIndex] = useState(null);
const [transformMode, setTransformMode] =
useState("translate");
const [isPlayerMode, setIsPlayerMode] = useState(false);
const [isSelectionLocked, setIsSelectionLocked] = useState(false);
- const [sceneLoadingState, setSceneLoadingState] = useState(
- {
- ...INITIAL_SCENE_LOADING_STATE,
- currentStep: "Montage progressif des models",
- progress: 0.2,
- },
+ const [snapToTerrain, setSnapToTerrain] = useState(true);
+ const [newNodeName, setNewNodeName] = useState(DEFAULT_NEW_NODE_NAME);
+ const [lockTerrainSelection, setLockTerrainSelection] = useState(true);
+ const [resetCameraRequest, setResetCameraRequest] = useState(0);
+ const [snapAllToTerrainRequest, setSnapAllToTerrainRequest] = useState(0);
+ const [focusSelectedCameraRequest, setFocusSelectedCameraRequest] =
+ useState(0);
+ const [cameraViewMode, setCameraViewMode] = useState<"home" | "object">(
+ "home",
);
- const handleSceneLoadingStateChange = useCallback(
- (nextState: SceneLoadingState) => {
- setSceneLoadingState((currentState) => {
- const shouldRestartProgress = currentState.status === "ready";
-
- return {
- ...nextState,
- progress: shouldRestartProgress
- ? nextState.progress
- : Math.max(currentState.progress, nextState.progress),
- };
- });
- },
- [],
- );
- const editorLoadingState = isMapLoading
+ const editorLoadingState: SceneLoadingState = isMapLoading
? {
- currentStep: "Récupération blocking",
+ currentStep: "Chargement de la carte",
progress: 0.08,
status: "loading" as const,
}
- : sceneLoadingState;
+ : {
+ currentStep: "Gameplay prêt",
+ progress: 1,
+ status: "ready" as const,
+ };
const [cinematicPreviewRequest, setCinematicPreviewRequest] =
useState(null);
@@ -111,16 +108,96 @@ export function EditorPage(): React.JSX.Element {
const handleSelectNode = useCallback((index: number | null) => {
setSelectedNodeIndex(index);
+ setSelectedNodeIndexes(index === null ? [] : [index]);
+
+ if (index !== null) {
+ setCameraViewMode("object");
+ return;
+ }
+
+ setCameraViewMode("home");
+ setResetCameraRequest((request) => request + 1);
}, []);
+ const handleToggleNodeSelection = useCallback(
+ (index: number) => {
+ const isSelected = selectedNodeIndexes.includes(index);
+ const nextIndexes = isSelected
+ ? selectedNodeIndexes.filter((item) => item !== index)
+ : [...selectedNodeIndexes, index];
+
+ setSelectedNodeIndexes(nextIndexes);
+ setSelectedNodeIndex(nextIndexes.at(-1) ?? null);
+ if (nextIndexes.length > 0) {
+ setCameraViewMode("object");
+ } else {
+ setCameraViewMode("home");
+ setResetCameraRequest((request) => request + 1);
+ }
+ },
+ [selectedNodeIndexes],
+ );
+
const handleClearSelection = useCallback(() => {
setSelectedNodeIndex(null);
+ setSelectedNodeIndexes([]);
+ setCameraViewMode("home");
+ setResetCameraRequest((request) => request + 1);
}, []);
const handleSelectionLockToggle = useCallback(() => {
setIsSelectionLocked((locked) => !locked);
}, []);
+ const handleSnapToTerrainToggle = useCallback(() => {
+ setSnapToTerrain((enabled) => !enabled);
+ }, []);
+
+ const handleSnapAllToTerrainRequest = useCallback(() => {
+ setSnapAllToTerrainRequest((request) => request + 1);
+ }, []);
+
+ const handleSnapAllToTerrain = useCallback(
+ (mapNodes: MapNode[]) => {
+ setSceneData((prev) => {
+ if (!prev) return null;
+
+ const nextSceneData = { ...prev, mapNodes };
+ if (!prev.mapTree) return nextSceneData;
+
+ const mapTree = mergeFlatNodeTransformsIntoTree(nextSceneData);
+ return updateSceneDataTree(nextSceneData, mapTree);
+ });
+ },
+ [setSceneData],
+ );
+
+ const handleNewNodeNameChange = useCallback((value: string) => {
+ setNewNodeName(value);
+ }, []);
+
+ const handleTerrainSelectionLockChange = useCallback(
+ (locked: boolean) => {
+ setLockTerrainSelection(locked);
+
+ if (!locked) return;
+
+ const nextIndexes = selectedNodeIndexes.filter(
+ (index) => sceneData?.mapNodes[index]?.name !== "terrain",
+ );
+ const selectedNode =
+ selectedNodeIndex !== null
+ ? sceneData?.mapNodes[selectedNodeIndex]
+ : null;
+
+ setSelectedNodeIndexes(nextIndexes);
+ setSelectedNodeIndex(
+ selectedNode?.name === "terrain" ? null : selectedNodeIndex,
+ );
+ },
+ [sceneData, selectedNodeIndex, selectedNodeIndexes],
+ );
+
const handleHoverNode = useCallback((index: number | null) => {
setHoveredNodeIndex(index);
}, []);
@@ -167,6 +244,17 @@ export function EditorPage(): React.JSX.Element {
setIsPlayerMode((prev) => !prev);
}, []);
+ const handleCameraAction = useCallback(() => {
+ if (selectedNodeIndex !== null && cameraViewMode === "home") {
+ setFocusSelectedCameraRequest((request) => request + 1);
+ setCameraViewMode("object");
+ return;
+ }
+
+ setResetCameraRequest((request) => request + 1);
+ setCameraViewMode("home");
+ }, [cameraViewMode, selectedNodeIndex]);
+
const handlePreviewCinematic = useCallback(
(cinematic: CinematicDefinition) => {
setCinematicPreviewRequest({
@@ -185,14 +273,112 @@ export function EditorPage(): React.JSX.Element {
(nodeIndex: number, updatedNode: MapNode) => {
setSceneData((prev) => {
if (!prev) return null;
- const newMapNodes = [...prev.mapNodes];
- newMapNodes[nodeIndex] = updatedNode;
- return { ...prev, mapNodes: newMapNodes };
+ const currentNode = prev.mapNodes[nodeIndex];
+ if (!currentNode) return prev;
+
+ if (!prev.mapTree || !currentNode.sourcePath) {
+ const mapNodes = [...prev.mapNodes];
+ mapNodes[nodeIndex] = updatedNode;
+ return { ...prev, mapNodes };
+ }
+
+ const mapTree = updateTreeNodeAtPath(
+ prev.mapTree,
+ currentNode.sourcePath,
+ (node) => ({
+ ...node,
+ position: updatedNode.position,
+ rotation: updatedNode.rotation,
+ scale: updatedNode.scale,
+ }),
+ );
+ return updateSceneDataTree(prev, mapTree);
});
},
[setSceneData],
);
+ const handleSelectedScaleChange = useCallback(
+ (axis: 0 | 1 | 2, value: number) => {
+ if (selectedNodeIndex === null || Number.isNaN(value)) return;
+
+ setSceneData((prev) => {
+ if (!prev) return null;
+ const currentNode = prev.mapNodes[selectedNodeIndex];
+ if (!currentNode) return prev;
+
+ const nextScale = [...currentNode.scale] as [number, number, number];
+ nextScale[axis] = value;
+
+ if (!prev.mapTree || !currentNode.sourcePath) {
+ const mapNodes = [...prev.mapNodes];
+ mapNodes[selectedNodeIndex] = { ...currentNode, scale: nextScale };
+ return { ...prev, mapNodes };
+ }
+
+ const mapTree = updateTreeNodeAtPath(
+ prev.mapTree,
+ currentNode.sourcePath,
+ (node) => ({ ...node, scale: nextScale }),
+ );
+
+ return updateSceneDataTree(prev, mapTree);
+ });
+ },
+ [selectedNodeIndex, setSceneData],
+ );
+
+ const handleAddNode = useCallback(() => {
+ if (!sceneData) return;
+
+ if (!sceneData.mapTree) {
+ const newNode = createNewMapNode(newNodeName);
+ const mapNodes = [...sceneData.mapNodes, removeEditorMetadata(newNode)];
+ const selectedIndex = mapNodes.length - 1;
+
+ setSceneData({ ...sceneData, mapNodes });
+ setSelectedNodeIndex(selectedIndex);
+ setSelectedNodeIndexes([selectedIndex]);
+ return;
+ }
+
+ const mapTree = addTreeNode(
+ sceneData.mapTree,
+ createNewMapNode(newNodeName),
+ );
+ const nextSceneData = updateSceneDataTree(sceneData, mapTree);
+ const selectedIndex = nextSceneData.mapNodes.length - 1;
+
+ setSceneData(nextSceneData);
+ setSelectedNodeIndex(selectedIndex);
+ setSelectedNodeIndexes([selectedIndex]);
+ }, [newNodeName, sceneData, setSceneData]);
+
+ const handleDeleteSelectedNode = useCallback(() => {
+ if (!sceneData || selectedNodeIndex === null) return;
+
+ const currentNode = sceneData.mapNodes[selectedNodeIndex];
+ if (!currentNode) return;
+
+ if (!sceneData.mapTree || !currentNode.sourcePath) {
+ setSceneData({
+ ...sceneData,
+ mapNodes: sceneData.mapNodes.filter(
+ (_node, index) => index !== selectedNodeIndex,
+ ),
+ });
+ } else {
+ const mapTree = removeTreeNodeAtPath(
+ sceneData.mapTree,
+ currentNode.sourcePath,
+ );
+ setSceneData(updateSceneDataTree(sceneData, mapTree));
+ }
+
+ setSelectedNodeIndex(null);
+ setSelectedNodeIndexes([]);
+ }, [sceneData, selectedNodeIndex, setSceneData]);
+
if (isMapLoading) {
return (
@@ -243,33 +429,39 @@ export function EditorPage(): React.JSX.Element {
@@ -279,6 +471,7 @@ export function EditorPage(): React.JSX.Element {
transformMode={transformMode}
onTransformModeChange={handleTransformModeChange}
selectedNodeIndex={selectedNodeIndex}
+ selectedNodeIndexes={selectedNodeIndexes}
mapNodes={sceneData.mapNodes}
nodesCount={sceneData.mapNodes.length}
selectedNodeName={
@@ -286,13 +479,34 @@ export function EditorPage(): React.JSX.Element {
? sceneData.mapNodes[selectedNodeIndex].name || null
: null
}
+ selectedNodeScale={
+ selectedNodeIndex !== null && sceneData.mapNodes[selectedNodeIndex]
+ ? sceneData.mapNodes[selectedNodeIndex].scale
+ : null
+ }
+ lockTerrainSelection={lockTerrainSelection}
+ onLockTerrainSelectionChange={handleTerrainSelectionLockChange}
isSelectionLocked={isSelectionLocked}
onSelectionLockToggle={handleSelectionLockToggle}
onClearSelection={handleClearSelection}
+ snapToTerrain={snapToTerrain}
+ onSnapToTerrainToggle={handleSnapToTerrainToggle}
+ onSnapAllToTerrain={handleSnapAllToTerrainRequest}
+ newNodeName={newNodeName}
+ onNewNodeNameChange={handleNewNodeNameChange}
+ onAddNode={handleAddNode}
+ onDeleteSelectedNode={handleDeleteSelectedNode}
+ onSelectedScaleChange={handleSelectedScaleChange}
undoCount={undoCount}
redoCount={redoCount}
onUndo={handleUndo}
onRedo={handleRedo}
+ cameraActionLabel={
+ selectedNodeIndex !== null && cameraViewMode === "home"
+ ? "Center on object"
+ : "Reset camera"
+ }
+ onCameraAction={handleCameraAction}
onExportJson={handleExportJson}
onSaveToServer={import.meta.env.DEV ? handleSaveToServer : undefined}
onPlayerMode={handlePlayerMode}
diff --git a/src/pages/page.tsx b/src/pages/page.tsx
index a34c6aa..deae3e3 100644
--- a/src/pages/page.tsx
+++ b/src/pages/page.tsx
@@ -6,12 +6,11 @@ import { DialogMessage } from "@/components/ui/DialogMessage";
import { GameUI } from "@/components/ui/GameUI";
import { BienvenueDisplay, IntroUI } from "@/components/ui/IntroUI";
import { SceneLoadingOverlay } from "@/components/ui/SceneLoadingOverlay";
+import { INITIAL_SCENE_LOADING_STATE } from "@/data/world/sceneLoadingConfig";
import { useGameStore } from "@/managers/stores/useGameStore";
import { HandTrackingProvider } from "@/providers/gameplay/HandTrackingProvider";
-import {
- INITIAL_SCENE_LOADING_STATE,
- type SceneLoadingState,
-} from "@/types/world/sceneLoading";
+import type { SceneLoadingState } from "@/types/world/sceneLoading";
+import { logger } from "@/utils/core/Logger";
import { World } from "@/world/World";
export function HomePage(): React.JSX.Element {
@@ -51,11 +50,43 @@ export function HomePage(): React.JSX.Element {
[],
);
+ const handleCanvasCreated = useCallback(
+ ({ gl }: { gl: THREE.WebGLRenderer }) => {
+ const canvas = gl.domElement;
+
+ gl.shadowMap.enabled = true;
+ gl.shadowMap.type = THREE.PCFShadowMap;
+ gl.shadowMap.autoUpdate = true;
+
+ const handleContextLost = (event: Event) => {
+ event.preventDefault();
+ logger.error("WebGL", "Context lost - GPU resources exhausted");
+ };
+
+ const handleContextRestored = () => {
+ gl.shadowMap.enabled = true;
+ gl.shadowMap.type = THREE.PCFShadowMap;
+ gl.shadowMap.autoUpdate = true;
+ logger.info("WebGL", "Context restored");
+ };
+
+ canvas.addEventListener("webglcontextlost", handleContextLost);
+ canvas.addEventListener("webglcontextrestored", handleContextRestored);
+ },
+ [],
+ );
+
return (
-
))
@@ -692,13 +809,16 @@ export const WaypointEditorPage: React.FC = () => {
{/* Details & editing for selected node */}
{selectedNode && (
-
Détails Waypoint {selectedNode.id}
+
+ Détails Waypoint {selectedNode.id}
+
Pos X: {selectedNode.x}
- Pos Y: {selectedNode.y} (Hauteur)
+ Pos Y:{" "}
+ {selectedNode.y} (Hauteur)
Pos Z: {selectedNode.z}
@@ -706,17 +826,21 @@ export const WaypointEditorPage: React.FC = () => {
-
startConnecting(selectedNode.id)}
>
-
- {isConnectingMode && activeConnectionStartId === selectedNode.id
- ? 'Cliquez sur un autre...'
- : 'Relier à...'}
+
+ {isConnectingMode &&
+ activeConnectionStartId === selectedNode.id
+ ? "Cliquez sur un autre..."
+ : "Relier à..."}
-
handleDeleteNode(selectedNode.id)}
>
Supprimer
@@ -729,20 +853,27 @@ export const WaypointEditorPage: React.FC = () => {
{selectedConnection && (
Liaison Sélectionnée
-
+
- Point A: ID {selectedConnection.idA}
+ Point A:{" "}
+ ID {selectedConnection.idA}
- Point B: ID {selectedConnection.idB}
+ Point B:{" "}
+ ID {selectedConnection.idB}
Type: Lien Bidirectionnel
-
deleteSelectedConnection(selectedConnection.idA, selectedConnection.idB)}
+
+ deleteSelectedConnection(
+ selectedConnection.idA,
+ selectedConnection.idB,
+ )
+ }
>
Supprimer la Liaison
@@ -751,7 +882,7 @@ export const WaypointEditorPage: React.FC = () => {
{/* 3. Three.js Canvas */}
- e.preventDefault()}
@@ -763,32 +894,41 @@ export const WaypointEditorPage: React.FC = () => {
{isConnectingMode && (
- Mode Connexion Actif : Cliquez sur le deuxième waypoint pour lier le point {activeConnectionStartId}.
+ Mode Connexion Actif : Cliquez sur le deuxième
+ waypoint pour lier le point {activeConnectionStartId}.
)}
{dragStartNodeId !== null && (
- Relier le point {dragStartNodeId}... Glissez et relâchez sur un autre point.
+ Relier le point {dragStartNodeId}... Glissez
+ et relâchez sur un autre point.
)}