Feat/map-environment #6
@@ -74,7 +74,7 @@ These vegetation and crop assets account for almost all of the current `~69M` tr
|
|||||||
|
|
||||||
## Debug Performance Controls
|
## Debug Performance Controls
|
||||||
|
|
||||||
The next useful runtime tool is a debug-only performance folder that can isolate model families. This should be mounted only when `?debug` is enabled.
|
The debug-only performance folder can isolate model families when `?debug` is enabled.
|
||||||
|
|
||||||
Proposed controls:
|
Proposed controls:
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,12 @@ import * as THREE from "three";
|
|||||||
import type { OrbitControls as OrbitControlsImpl } from "three-stdlib";
|
import type { OrbitControls as OrbitControlsImpl } from "three-stdlib";
|
||||||
import { EditorMap } from "@/components/editor/scene/EditorMap";
|
import { EditorMap } from "@/components/editor/scene/EditorMap";
|
||||||
import { FlyController } from "@/controls/editor/FlyController";
|
import { FlyController } from "@/controls/editor/FlyController";
|
||||||
import type { CinematicDefinition } from "@/types/cinematics/cinematics";
|
import type {
|
||||||
import type { MapNode, TransformMode, SceneData } from "@/types/editor/editor";
|
EditorCinematicPreviewRequest,
|
||||||
|
MapNode,
|
||||||
|
TransformMode,
|
||||||
|
SceneData,
|
||||||
|
} from "@/types/editor/editor";
|
||||||
|
|
||||||
const EDITOR_CAMERA_HOME_POSITION = new THREE.Vector3(0, 50, 100);
|
const EDITOR_CAMERA_HOME_POSITION = new THREE.Vector3(0, 50, 100);
|
||||||
const EDITOR_CAMERA_HOME_TARGET = new THREE.Vector3(0, 0, 0);
|
const EDITOR_CAMERA_HOME_TARGET = new THREE.Vector3(0, 0, 0);
|
||||||
@@ -23,11 +27,6 @@ function isEditableShortcutTarget(target: EventTarget | null): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditorCinematicPreviewRequest {
|
|
||||||
id: string;
|
|
||||||
cinematic: CinematicDefinition;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EditorSceneProps {
|
interface EditorSceneProps {
|
||||||
sceneData: SceneData;
|
sceneData: SceneData;
|
||||||
selectedNodeIndex: number | null;
|
selectedNodeIndex: number | null;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useGLTF } from "@react-three/drei";
|
|||||||
import { Component, useEffect, useMemo, useRef, type ReactNode } from "react";
|
import { Component, useEffect, useMemo, useRef, type ReactNode } from "react";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF";
|
import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF";
|
||||||
|
import { logger } from "@/utils/core/Logger";
|
||||||
|
|
||||||
interface SkyModelProps {
|
interface SkyModelProps {
|
||||||
fallbackModelScale?: number | undefined;
|
fallbackModelScale?: number | undefined;
|
||||||
@@ -20,6 +21,8 @@ interface SkyModelContentProps {
|
|||||||
interface SkyModelErrorBoundaryProps {
|
interface SkyModelErrorBoundaryProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
fallback: ReactNode;
|
fallback: ReactNode;
|
||||||
|
label: string;
|
||||||
|
modelPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SkyModelErrorBoundaryState {
|
interface SkyModelErrorBoundaryState {
|
||||||
@@ -43,6 +46,17 @@ class SkyModelErrorBoundary extends Component<
|
|||||||
return { hasError: true };
|
return { hasError: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error: Error): void {
|
||||||
|
logger.warn(
|
||||||
|
"SkyModel",
|
||||||
|
`${this.props.label} model failed; using fallback`,
|
||||||
|
{
|
||||||
|
error,
|
||||||
|
modelPath: this.props.modelPath,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
if (this.state.hasError) {
|
if (this.state.hasError) {
|
||||||
return this.props.fallback;
|
return this.props.fallback;
|
||||||
@@ -63,7 +77,12 @@ export function SkyModel({
|
|||||||
<color attach="background" args={[fallbackColor]} />
|
<color attach="background" args={[fallbackColor]} />
|
||||||
) : null;
|
) : null;
|
||||||
const fallback = fallbackModelPath ? (
|
const fallback = fallbackModelPath ? (
|
||||||
<SkyModelErrorBoundary key={fallbackModelPath} fallback={colorFallback}>
|
<SkyModelErrorBoundary
|
||||||
|
key={fallbackModelPath}
|
||||||
|
fallback={colorFallback}
|
||||||
|
label="Fallback sky"
|
||||||
|
modelPath={fallbackModelPath}
|
||||||
|
>
|
||||||
<SkyModelContent
|
<SkyModelContent
|
||||||
modelPath={fallbackModelPath}
|
modelPath={fallbackModelPath}
|
||||||
scale={fallbackModelScale}
|
scale={fallbackModelScale}
|
||||||
@@ -74,7 +93,12 @@ export function SkyModel({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SkyModelErrorBoundary key={modelPath} fallback={fallback}>
|
<SkyModelErrorBoundary
|
||||||
|
key={modelPath}
|
||||||
|
fallback={fallback}
|
||||||
|
label="Primary sky"
|
||||||
|
modelPath={modelPath}
|
||||||
|
>
|
||||||
<SkyModelContent modelPath={modelPath} scale={scale} />
|
<SkyModelContent modelPath={modelPath} scale={scale} />
|
||||||
</SkyModelErrorBoundary>
|
</SkyModelErrorBoundary>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import type {
|
|||||||
MissionStep,
|
MissionStep,
|
||||||
RepairMissionId,
|
RepairMissionId,
|
||||||
} from "@/types/gameplay/repairMission";
|
} from "@/types/gameplay/repairMission";
|
||||||
|
import { REPAIR_MISSION_IDS } from "@/types/gameplay/repairMission";
|
||||||
|
|
||||||
const REPAIR_MISSION_IDS = ["ebike", "pylon", "farm"] as const;
|
|
||||||
const REPAIR_MISSION_ID_VALUES: ReadonlySet<string> = new Set(
|
const REPAIR_MISSION_ID_VALUES: ReadonlySet<string> = new Set(
|
||||||
REPAIR_MISSION_IDS,
|
REPAIR_MISSION_IDS,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -83,6 +83,12 @@ export const VEGETATION_TYPE_KEYS = [
|
|||||||
|
|
||||||
export type VegetationType = (typeof VEGETATION_TYPE_KEYS)[number];
|
export type VegetationType = (typeof VEGETATION_TYPE_KEYS)[number];
|
||||||
|
|
||||||
|
export const VEGETATION_MAP_NODE_NAMES: ReadonlySet<string> = new Set(
|
||||||
|
Object.values(VEGETATION_TYPES)
|
||||||
|
.filter((config) => config.enabled)
|
||||||
|
.map((config) => config.mapName),
|
||||||
|
);
|
||||||
|
|
||||||
export function getVegetationModelScaleMultiplier(name: string): number {
|
export function getVegetationModelScaleMultiplier(name: string): number {
|
||||||
return (
|
return (
|
||||||
Object.values(VEGETATION_TYPES).find((config) => config.mapName === name)
|
Object.values(VEGETATION_TYPES).find((config) => config.mapName === name)
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ import { useCallback, useEffect, useState } from "react";
|
|||||||
import { Canvas, useThree } from "@react-three/fiber";
|
import { Canvas, useThree } from "@react-three/fiber";
|
||||||
import { EditorControls } from "@/components/editor/EditorControls";
|
import { EditorControls } from "@/components/editor/EditorControls";
|
||||||
import { EditorScene } from "@/components/editor/scene/EditorScene";
|
import { EditorScene } from "@/components/editor/scene/EditorScene";
|
||||||
import type { EditorCinematicPreviewRequest } from "@/components/editor/scene/EditorScene";
|
|
||||||
import { SceneLoadingOverlay } from "@/components/ui/SceneLoadingOverlay";
|
import { SceneLoadingOverlay } from "@/components/ui/SceneLoadingOverlay";
|
||||||
import { Subtitles } from "@/components/ui/Subtitles";
|
import { Subtitles } from "@/components/ui/Subtitles";
|
||||||
import { useEditorHistory } from "@/hooks/editor/useEditorHistory";
|
import { useEditorHistory } from "@/hooks/editor/useEditorHistory";
|
||||||
import type { CinematicDefinition } from "@/types/cinematics/cinematics";
|
import type { CinematicDefinition } from "@/types/cinematics/cinematics";
|
||||||
import { useEditorSceneData } from "@/hooks/editor/useEditorSceneData";
|
import { useEditorSceneData } from "@/hooks/editor/useEditorSceneData";
|
||||||
import type { MapNode, TransformMode } from "@/types/editor/editor";
|
import type {
|
||||||
|
EditorCinematicPreviewRequest,
|
||||||
|
MapNode,
|
||||||
|
TransformMode,
|
||||||
|
} from "@/types/editor/editor";
|
||||||
import type { SceneLoadingState } from "@/types/world/sceneLoading";
|
import type { SceneLoadingState } from "@/types/world/sceneLoading";
|
||||||
import { logger } from "@/utils/core/Logger";
|
import { logger } from "@/utils/core/Logger";
|
||||||
import {
|
import {
|
||||||
@@ -82,7 +85,7 @@ export function EditorPage(): React.JSX.Element {
|
|||||||
);
|
);
|
||||||
const editorLoadingState: SceneLoadingState = isMapLoading
|
const editorLoadingState: SceneLoadingState = isMapLoading
|
||||||
? {
|
? {
|
||||||
currentStep: "Récupération blocking",
|
currentStep: "Chargement de la carte",
|
||||||
progress: 0.08,
|
progress: 0.08,
|
||||||
status: "loading" as const,
|
status: "loading" as const,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { CinematicDefinition } from "@/types/cinematics/cinematics";
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
HierarchicalMapNode,
|
HierarchicalMapNode,
|
||||||
MapNode,
|
MapNode,
|
||||||
@@ -5,3 +7,8 @@ export type {
|
|||||||
} from "@/types/map/mapScene";
|
} from "@/types/map/mapScene";
|
||||||
|
|
||||||
export type TransformMode = "translate" | "rotate" | "scale";
|
export type TransformMode = "translate" | "rotate" | "scale";
|
||||||
|
|
||||||
|
export interface EditorCinematicPreviewRequest {
|
||||||
|
id: string;
|
||||||
|
cinematic: CinematicDefinition;
|
||||||
|
}
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
import type { Vector3Tuple } from "@/types/three/three";
|
import type { Vector3Tuple } from "@/types/three/three";
|
||||||
|
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
||||||
|
|
||||||
export type GameStep =
|
export type GameStep =
|
||||||
| "intro"
|
| "intro"
|
||||||
@@ -12,7 +13,7 @@ export type GameStep =
|
|||||||
| "manipulation"
|
| "manipulation"
|
||||||
| "outOfFabrik";
|
| "outOfFabrik";
|
||||||
|
|
||||||
export type MainGameState = "intro" | "ebike" | "pylon" | "farm" | "outro";
|
export type MainGameState = "intro" | RepairMissionId | "outro";
|
||||||
|
|
||||||
export interface Zone {
|
export interface Zone {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import type {
|
|||||||
Vector3Tuple,
|
Vector3Tuple,
|
||||||
} from "@/types/three/three";
|
} from "@/types/three/three";
|
||||||
|
|
||||||
export type RepairMissionId = "ebike" | "pylon" | "farm";
|
export const REPAIR_MISSION_IDS = ["ebike", "pylon", "farm"] as const;
|
||||||
|
|
||||||
|
export type RepairMissionId = (typeof REPAIR_MISSION_IDS)[number];
|
||||||
|
|
||||||
export interface RepairMissionTriggerConfig {
|
export interface RepairMissionTriggerConfig {
|
||||||
mission: RepairMissionId;
|
mission: RepairMissionId;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { logger } from "@/utils/core/Logger";
|
||||||
|
|
||||||
const DEBUG_GAME_STATE_COOKIE_NAME = "la-fabrik-debug-game-state";
|
const DEBUG_GAME_STATE_COOKIE_NAME = "la-fabrik-debug-game-state";
|
||||||
const DEBUG_GAME_STATE_COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
|
const DEBUG_GAME_STATE_COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
|
||||||
|
|
||||||
@@ -18,7 +20,11 @@ export function readDebugGameStateCookie(): unknown {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
return JSON.parse(decodeURIComponent(value));
|
return JSON.parse(decodeURIComponent(value));
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
logger.warn("DebugGameState", "Invalid debug game state cookie cleared", {
|
||||||
|
error: error instanceof Error ? error : String(error),
|
||||||
|
});
|
||||||
|
clearDebugGameStateCookie();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,8 @@
|
|||||||
import type { MapNode } from "@/types/map/mapScene";
|
import type { MapNode } from "@/types/map/mapScene";
|
||||||
|
import { VEGETATION_MAP_NODE_NAMES } from "@/data/world/vegetationConfig";
|
||||||
import { isInstancedMapNodeName } from "@/utils/map/isInstancedMapNodeName";
|
import { isInstancedMapNodeName } from "@/utils/map/isInstancedMapNodeName";
|
||||||
|
|
||||||
const MAP_STRUCTURE_NODE_NAMES = new Set(["Scene", "blocking", "terrain"]);
|
const MAP_STRUCTURE_NODE_NAMES = new Set(["Scene", "blocking", "terrain"]);
|
||||||
const RUNTIME_VEGETATION_NODE_NAMES = new Set([
|
|
||||||
"arbre",
|
|
||||||
"buisson",
|
|
||||||
"champdeble",
|
|
||||||
"champdesoja",
|
|
||||||
"champsdetournesol",
|
|
||||||
"potager",
|
|
||||||
"sapin",
|
|
||||||
]);
|
|
||||||
|
|
||||||
function isRuntimeStructureMapNode(name: string): boolean {
|
function isRuntimeStructureMapNode(name: string): boolean {
|
||||||
return MAP_STRUCTURE_NODE_NAMES.has(name);
|
return MAP_STRUCTURE_NODE_NAMES.has(name);
|
||||||
@@ -26,7 +18,7 @@ export function isRuntimeSingleMapNode(node: MapNode): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!RUNTIME_VEGETATION_NODE_NAMES.has(node.name) &&
|
!VEGETATION_MAP_NODE_NAMES.has(node.name) &&
|
||||||
!isInstancedMapNodeName(node.name)
|
!isInstancedMapNodeName(node.name)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ export function GameMap({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onLoadingStateChange?.({
|
onLoadingStateChange?.({
|
||||||
currentStep: "Récupération blocking",
|
currentStep: "Chargement de la carte",
|
||||||
progress: 0.05,
|
progress: 0.05,
|
||||||
status: "loading",
|
status: "loading",
|
||||||
});
|
});
|
||||||
@@ -163,7 +163,7 @@ export function GameMap({
|
|||||||
}
|
}
|
||||||
|
|
||||||
onLoadingStateChange?.({
|
onLoadingStateChange?.({
|
||||||
currentStep: "Importation des models",
|
currentStep: "Importation des modèles",
|
||||||
progress: 0.18,
|
progress: 0.18,
|
||||||
status: "loading",
|
status: "loading",
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user