feat(debug): add map performance visibility controls
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
|
||||
import {
|
||||
MAP_PERFORMANCE_GROUP_NAMES,
|
||||
MAP_PERFORMANCE_MODEL_NAMES,
|
||||
useMapPerformanceStore,
|
||||
} from "@/managers/stores/useMapPerformanceStore";
|
||||
|
||||
function toLabel(value: string): string {
|
||||
return value
|
||||
.split(/[-_\s]+/)
|
||||
.filter(Boolean)
|
||||
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
export function useMapPerformanceDebug(): void {
|
||||
useDebugFolder("Performance / Map", (folder) => {
|
||||
const {
|
||||
groups,
|
||||
models,
|
||||
setGroupVisible,
|
||||
setModelVisible,
|
||||
resetVisibility,
|
||||
} = useMapPerformanceStore.getState();
|
||||
const controls = {
|
||||
...groups,
|
||||
...models,
|
||||
reset: () => {
|
||||
resetVisibility();
|
||||
for (const key of [
|
||||
...MAP_PERFORMANCE_GROUP_NAMES,
|
||||
...MAP_PERFORMANCE_MODEL_NAMES,
|
||||
]) {
|
||||
controls[key] = true;
|
||||
}
|
||||
folder.controllersRecursive().forEach((controller) => {
|
||||
controller.updateDisplay();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
for (const group of MAP_PERFORMANCE_GROUP_NAMES) {
|
||||
folder
|
||||
.add(controls, group)
|
||||
.name(toLabel(group))
|
||||
.onChange((visible: boolean) => setGroupVisible(group, visible));
|
||||
}
|
||||
|
||||
for (const model of MAP_PERFORMANCE_MODEL_NAMES) {
|
||||
folder
|
||||
.add(controls, model)
|
||||
.name(toLabel(model))
|
||||
.onChange((visible: boolean) => setModelVisible(model, visible));
|
||||
}
|
||||
|
||||
folder.add(controls, "reset").name("Reset visibility");
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
export type MapPerformanceGroupName =
|
||||
| "vegetation"
|
||||
| "crops"
|
||||
| "trees"
|
||||
| "buildings"
|
||||
| "landmarks"
|
||||
| "props"
|
||||
| "terrain"
|
||||
| "sky";
|
||||
|
||||
export type MapPerformanceModelName =
|
||||
| "buisson"
|
||||
| "arbre"
|
||||
| "sapin"
|
||||
| "champdeble"
|
||||
| "champdesoja"
|
||||
| "champsdetournesol"
|
||||
| "ecole"
|
||||
| "generateur"
|
||||
| "fermeverticale"
|
||||
| "lafabrik"
|
||||
| "immeuble1"
|
||||
| "eolienne"
|
||||
| "pylone"
|
||||
| "boiteauxlettres"
|
||||
| "maison1"
|
||||
| "parcebike"
|
||||
| "terrain"
|
||||
| "sky";
|
||||
|
||||
export interface MapPerformanceVisibility {
|
||||
groups: Record<MapPerformanceGroupName, boolean>;
|
||||
models: Record<MapPerformanceModelName, boolean>;
|
||||
}
|
||||
|
||||
interface MapPerformanceActions {
|
||||
setGroupVisible: (group: MapPerformanceGroupName, visible: boolean) => void;
|
||||
setModelVisible: (model: MapPerformanceModelName, visible: boolean) => void;
|
||||
resetVisibility: () => void;
|
||||
}
|
||||
|
||||
type MapPerformanceStore = MapPerformanceVisibility & MapPerformanceActions;
|
||||
|
||||
export const MAP_PERFORMANCE_GROUP_NAMES: readonly MapPerformanceGroupName[] = [
|
||||
"vegetation",
|
||||
"crops",
|
||||
"trees",
|
||||
"buildings",
|
||||
"landmarks",
|
||||
"props",
|
||||
"terrain",
|
||||
"sky",
|
||||
];
|
||||
|
||||
export const MAP_PERFORMANCE_MODEL_NAMES: readonly MapPerformanceModelName[] = [
|
||||
"buisson",
|
||||
"arbre",
|
||||
"sapin",
|
||||
"champdeble",
|
||||
"champdesoja",
|
||||
"champsdetournesol",
|
||||
"ecole",
|
||||
"generateur",
|
||||
"fermeverticale",
|
||||
"lafabrik",
|
||||
"immeuble1",
|
||||
"eolienne",
|
||||
"pylone",
|
||||
"boiteauxlettres",
|
||||
"maison1",
|
||||
"parcebike",
|
||||
"terrain",
|
||||
"sky",
|
||||
];
|
||||
|
||||
const MODEL_GROUPS: Record<
|
||||
MapPerformanceModelName,
|
||||
readonly MapPerformanceGroupName[]
|
||||
> = {
|
||||
buisson: ["vegetation"],
|
||||
arbre: ["vegetation", "trees"],
|
||||
sapin: ["vegetation", "trees"],
|
||||
champdeble: ["vegetation", "crops"],
|
||||
champdesoja: ["vegetation", "crops"],
|
||||
champsdetournesol: ["vegetation", "crops"],
|
||||
ecole: ["buildings", "landmarks"],
|
||||
generateur: ["landmarks"],
|
||||
fermeverticale: ["buildings", "landmarks"],
|
||||
lafabrik: ["buildings", "landmarks"],
|
||||
immeuble1: ["buildings"],
|
||||
eolienne: ["props"],
|
||||
pylone: ["props"],
|
||||
boiteauxlettres: ["props"],
|
||||
maison1: ["buildings"],
|
||||
parcebike: ["props"],
|
||||
terrain: ["terrain"],
|
||||
sky: ["sky"],
|
||||
};
|
||||
|
||||
function createVisibleRecord<T extends string>(
|
||||
keys: readonly T[],
|
||||
): Record<T, boolean> {
|
||||
return Object.fromEntries(keys.map((key) => [key, true])) as Record<
|
||||
T,
|
||||
boolean
|
||||
>;
|
||||
}
|
||||
|
||||
function createDefaultVisibility(): MapPerformanceVisibility {
|
||||
return {
|
||||
groups: createVisibleRecord(MAP_PERFORMANCE_GROUP_NAMES),
|
||||
models: createVisibleRecord(MAP_PERFORMANCE_MODEL_NAMES),
|
||||
};
|
||||
}
|
||||
|
||||
export function isMapPerformanceModelName(
|
||||
name: string,
|
||||
): name is MapPerformanceModelName {
|
||||
return MAP_PERFORMANCE_MODEL_NAMES.includes(name as MapPerformanceModelName);
|
||||
}
|
||||
|
||||
export function isMapModelVisible(
|
||||
name: string,
|
||||
visibility: MapPerformanceVisibility,
|
||||
): boolean {
|
||||
if (!isMapPerformanceModelName(name)) return true;
|
||||
if (!visibility.models[name]) return false;
|
||||
|
||||
return MODEL_GROUPS[name].every((group) => visibility.groups[group]);
|
||||
}
|
||||
|
||||
export const useMapPerformanceStore = create<MapPerformanceStore>()((set) => ({
|
||||
...createDefaultVisibility(),
|
||||
setGroupVisible: (group, visible) =>
|
||||
set((state) => ({
|
||||
groups: { ...state.groups, [group]: visible },
|
||||
})),
|
||||
setModelVisible: (model, visible) =>
|
||||
set((state) => ({
|
||||
models: { ...state.models, [model]: visible },
|
||||
})),
|
||||
resetVisibility: () => set(createDefaultVisibility()),
|
||||
}));
|
||||
@@ -7,10 +7,17 @@ import {
|
||||
PHYSICS_SCENE_BACKGROUND_COLOR,
|
||||
} from "@/data/world/environmentConfig";
|
||||
import { useSceneMode } from "@/hooks/debug/useSceneMode";
|
||||
import {
|
||||
isMapModelVisible,
|
||||
useMapPerformanceStore,
|
||||
} from "@/managers/stores/useMapPerformanceStore";
|
||||
import { SkyModel } from "@/components/three/world/SkyModel";
|
||||
|
||||
export function Environment(): React.JSX.Element {
|
||||
const sceneMode = useSceneMode();
|
||||
const groups = useMapPerformanceStore((state) => state.groups);
|
||||
const models = useMapPerformanceStore((state) => state.models);
|
||||
const showSky = isMapModelVisible("sky", { groups, models });
|
||||
|
||||
if (sceneMode === "physics") {
|
||||
return (
|
||||
@@ -18,7 +25,7 @@ export function Environment(): React.JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
return showSky ? (
|
||||
<SkyModel
|
||||
fallbackColor={GAME_SCENE_FALLBACK_BACKGROUND_COLOR}
|
||||
fallbackModelPath={GAME_SCENE_FALLBACK_SKY_MODEL_PATH}
|
||||
@@ -26,5 +33,7 @@ export function Environment(): React.JSX.Element {
|
||||
modelPath={GAME_SCENE_SKY_MODEL_PATH}
|
||||
scale={GAME_SCENE_SKY_MODEL_SCALE}
|
||||
/>
|
||||
) : (
|
||||
<color attach="background" args={[GAME_SCENE_FALLBACK_BACKGROUND_COLOR]} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ import * as THREE from "three";
|
||||
import { useClonedObject } from "@/hooks/three/useClonedObject";
|
||||
import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF";
|
||||
import { TerrainModel } from "@/components/three/world/TerrainModel";
|
||||
import {
|
||||
isMapModelVisible,
|
||||
useMapPerformanceStore,
|
||||
} from "@/managers/stores/useMapPerformanceStore";
|
||||
import { GameMapCollision } from "@/world/GameMapCollision";
|
||||
import { GeneratedMapNodeInstance } from "@/world/map-generated/GeneratedMapNodeInstance";
|
||||
import { isGeneratedMapModelName } from "@/world/map-generated/generatedMapModelConfig";
|
||||
@@ -101,6 +105,8 @@ export function GameMap({
|
||||
onOctreeReady,
|
||||
}: GameMapProps): React.JSX.Element {
|
||||
const settledMapNodesRef = useRef(new Set<number>());
|
||||
const groups = useMapPerformanceStore((state) => state.groups);
|
||||
const models = useMapPerformanceStore((state) => state.models);
|
||||
const [mapNodes, setMapNodes] = useState<LoadedMapNode[]>([]);
|
||||
const [mapLoaded, setMapLoaded] = useState(false);
|
||||
const [settledMapNodeCount, setSettledMapNodeCount] = useState(0);
|
||||
@@ -221,17 +227,23 @@ export function GameMap({
|
||||
node={mapNode.node}
|
||||
onSettled={() => handleMapNodeSettled(index)}
|
||||
>
|
||||
{isMapModelVisible(mapNode.node.name, { groups, models }) ? (
|
||||
<MapNodeInstance
|
||||
node={mapNode.node}
|
||||
modelUrl={mapNode.modelUrl}
|
||||
onSettled={() => handleMapNodeSettled(index)}
|
||||
/>
|
||||
) : (
|
||||
<HiddenMapNode onSettled={() => handleMapNodeSettled(index)} />
|
||||
)}
|
||||
</ModelErrorBoundary>
|
||||
))}
|
||||
</group>
|
||||
<MapInstancingSystem />
|
||||
<VegetationSystem />
|
||||
{isMapModelVisible("terrain", { groups, models }) ? (
|
||||
<TerrainModel />
|
||||
) : null}
|
||||
<GameMapCollision
|
||||
buildOctree={buildOctree}
|
||||
mapReady={mapReady}
|
||||
@@ -244,6 +256,14 @@ export function GameMap({
|
||||
);
|
||||
}
|
||||
|
||||
function HiddenMapNode({ onSettled }: { onSettled: () => void }): null {
|
||||
useEffect(() => {
|
||||
onSettled();
|
||||
}, [onSettled]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary development-only map reducer.
|
||||
*
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
PLAYER_SPAWN_POSITION_PHYSICS,
|
||||
} from "@/data/player/playerConfig";
|
||||
import { useCameraMode } from "@/hooks/debug/useCameraMode";
|
||||
import { useMapPerformanceDebug } from "@/hooks/debug/useMapPerformanceDebug";
|
||||
import { useSceneMode } from "@/hooks/debug/useSceneMode";
|
||||
import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot";
|
||||
import { useWorldSceneLoading } from "@/hooks/world/useWorldSceneLoading";
|
||||
@@ -35,6 +36,8 @@ interface WorldProps {
|
||||
}
|
||||
|
||||
export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element {
|
||||
useMapPerformanceDebug();
|
||||
|
||||
const cameraMode = useCameraMode();
|
||||
const sceneMode = useSceneMode();
|
||||
const mainState = useGameStore((state) => state.mainState);
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { Suspense } from "react";
|
||||
import {
|
||||
isMapModelVisible,
|
||||
useMapPerformanceStore,
|
||||
} from "@/managers/stores/useMapPerformanceStore";
|
||||
import { InstancedMapAsset } from "@/world/map-instancing/InstancedMapAsset";
|
||||
import {
|
||||
MAP_INSTANCING_ASSETS,
|
||||
@@ -7,6 +11,8 @@ import {
|
||||
import { useMapInstancingData } from "@/world/map-instancing/useMapInstancingData";
|
||||
|
||||
export function MapInstancingSystem(): React.JSX.Element | null {
|
||||
const groups = useMapPerformanceStore((state) => state.groups);
|
||||
const models = useMapPerformanceStore((state) => state.models);
|
||||
const { data, isLoading } = useMapInstancingData();
|
||||
|
||||
if (isLoading || !data) {
|
||||
@@ -14,7 +20,8 @@ export function MapInstancingSystem(): React.JSX.Element | null {
|
||||
}
|
||||
|
||||
const enabledAssets = Object.entries(MAP_INSTANCING_ASSETS).filter(
|
||||
([, config]) => config.enabled,
|
||||
([, config]) =>
|
||||
config.enabled && isMapModelVisible(config.mapName, { groups, models }),
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { Suspense } from "react";
|
||||
import {
|
||||
isMapModelVisible,
|
||||
useMapPerformanceStore,
|
||||
} from "@/managers/stores/useMapPerformanceStore";
|
||||
import { InstancedVegetation } from "@/world/vegetation/InstancedVegetation";
|
||||
import { useVegetationData } from "@/world/vegetation/useVegetationData";
|
||||
import {
|
||||
@@ -7,6 +11,8 @@ import {
|
||||
} from "@/world/vegetation/vegetationConfig";
|
||||
|
||||
export function VegetationSystem(): React.JSX.Element | null {
|
||||
const groups = useMapPerformanceStore((state) => state.groups);
|
||||
const models = useMapPerformanceStore((state) => state.models);
|
||||
const { data, isLoading } = useVegetationData();
|
||||
|
||||
if (isLoading || !data) {
|
||||
@@ -14,7 +20,8 @@ export function VegetationSystem(): React.JSX.Element | null {
|
||||
}
|
||||
|
||||
const enabledTypes = Object.entries(VEGETATION_TYPES).filter(
|
||||
([, config]) => config.enabled,
|
||||
([, config]) =>
|
||||
config.enabled && isMapModelVisible(config.mapName, { groups, models }),
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user