chore: address code quality audit findings
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
This commit is contained in:
@@ -35,7 +35,7 @@ import {
|
||||
isRuntimeSingleMapNode,
|
||||
} from "@/utils/map/mapRuntimeClassification";
|
||||
import { logModelLoadError } from "@/utils/three/modelLoadLogger";
|
||||
import type { MapNode } from "@/types/editor/editor";
|
||||
import type { MapNode } from "@/types/map/mapScene";
|
||||
import type { OctreeReadyHandler } from "@/types/three/three";
|
||||
|
||||
interface LoadedMapNode {
|
||||
|
||||
@@ -18,12 +18,12 @@ import {
|
||||
useTerrainHeightSampler,
|
||||
} from "@/hooks/three/useTerrainHeight";
|
||||
import { WorldBoundsCollision } from "@/world/collision/WorldBoundsCollision";
|
||||
import type { MapNode } from "@/types/editor/editor";
|
||||
import type { MapNode } from "@/types/map/mapScene";
|
||||
import type { OctreeReadyHandler } from "@/types/three/three";
|
||||
import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading";
|
||||
import { logModelLoadError } from "@/utils/three/modelLoadLogger";
|
||||
|
||||
export interface GameMapCollisionNode {
|
||||
interface GameMapCollisionNode {
|
||||
node: MapNode;
|
||||
modelUrl: string | null;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { InteractableObject } from "@/components/three/interaction/InteractableObject";
|
||||
import { RepairGame } from "@/components/three/gameplay/RepairGame";
|
||||
import { EBIKE_REPAIR_POSITION } from "@/data/gameplay/repairMissionAnchors";
|
||||
import {
|
||||
BIKE_REPAIR_POSITION,
|
||||
REPAIR_MISSION_POSITION_ENTRIES,
|
||||
} from "@/data/gameplay/repairMissionAnchors";
|
||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
||||
import type { Vector3Tuple } from "@/types/three/three";
|
||||
|
||||
interface StageAnchorProps {
|
||||
@@ -11,26 +13,6 @@ interface StageAnchorProps {
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
interface GameRepairZone {
|
||||
mission: RepairMissionId;
|
||||
position: Vector3Tuple;
|
||||
}
|
||||
|
||||
const GAME_REPAIR_ZONES = [
|
||||
{
|
||||
mission: "bike",
|
||||
position: EBIKE_REPAIR_POSITION,
|
||||
},
|
||||
{
|
||||
mission: "pylone",
|
||||
position: [64, 0, -66],
|
||||
},
|
||||
{
|
||||
mission: "ferme",
|
||||
position: [-24, 0, 42],
|
||||
},
|
||||
] as const satisfies readonly GameRepairZone[];
|
||||
|
||||
function StageAnchor({
|
||||
color,
|
||||
position,
|
||||
@@ -58,11 +40,11 @@ function EbikeMissionTrigger(): React.JSX.Element | null {
|
||||
if (mainState !== "bike" || bikeStep !== "locked") return null;
|
||||
|
||||
return (
|
||||
<group position={EBIKE_REPAIR_POSITION}>
|
||||
<group position={BIKE_REPAIR_POSITION}>
|
||||
<InteractableObject
|
||||
kind="trigger"
|
||||
label="Réparer l'e-bike"
|
||||
position={EBIKE_REPAIR_POSITION}
|
||||
position={BIKE_REPAIR_POSITION}
|
||||
radius={4}
|
||||
onPress={() => setMissionStep("bike", "waiting")}
|
||||
>
|
||||
@@ -83,12 +65,8 @@ export function GameStageContent(): React.JSX.Element {
|
||||
{mainState === "intro" ? (
|
||||
<StageAnchor color="#7dd3fc" position={[0, 4, 0]} />
|
||||
) : null}
|
||||
{GAME_REPAIR_ZONES.map((zone) => (
|
||||
<RepairGame
|
||||
key={zone.mission}
|
||||
mission={zone.mission}
|
||||
position={zone.position}
|
||||
/>
|
||||
{REPAIR_MISSION_POSITION_ENTRIES.map(({ mission, position }) => (
|
||||
<RepairGame key={mission} mission={mission} position={position} />
|
||||
))}
|
||||
<EbikeMissionTrigger />
|
||||
{mainState === "outro" ? (
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as THREE from "three";
|
||||
import { TERRAIN_MODEL_PATH } from "@/data/world/terrainConfig";
|
||||
import type { TerrainSurfaceBounds } from "@/types/world/terrainSurface";
|
||||
import type { Vector3Tuple } from "@/types/three/three";
|
||||
import { logger } from "@/utils/core/Logger";
|
||||
import { getMapNodesByName } from "@/utils/map/loadMapSceneData";
|
||||
import { GRASS_CONFIG } from "@/world/grass/grassConfig";
|
||||
|
||||
@@ -13,8 +14,9 @@ const DOWN = new THREE.Vector3(0, -1, 0);
|
||||
const DEFAULT_TERRAIN_POSITION: Vector3Tuple = [0, 0, 0];
|
||||
const DEFAULT_TERRAIN_ROTATION: Vector3Tuple = [0, 0, 0];
|
||||
const DEFAULT_TERRAIN_SCALE: Vector3Tuple = [1, 1, 1];
|
||||
let hasWarnedFallbackBounds = false;
|
||||
|
||||
export interface TerrainGrassSample {
|
||||
interface TerrainGrassSample {
|
||||
normal: THREE.Vector3;
|
||||
position: THREE.Vector3;
|
||||
}
|
||||
@@ -27,7 +29,12 @@ export interface TerrainGrassSampler {
|
||||
sample: (x: number, z: number) => TerrainGrassSample | null;
|
||||
}
|
||||
|
||||
function createFallbackBounds(): TerrainSurfaceBounds {
|
||||
function createFallbackTerrainBounds(): TerrainSurfaceBounds {
|
||||
if (!hasWarnedFallbackBounds) {
|
||||
hasWarnedFallbackBounds = true;
|
||||
logger.warn("Grass", "Terrain bounds missing, using fallback grass bounds");
|
||||
}
|
||||
|
||||
return {
|
||||
minX: -120,
|
||||
maxX: 120,
|
||||
@@ -78,7 +85,7 @@ function createTerrainGrassSampler(
|
||||
}
|
||||
|
||||
const bounds = terrainBounds.isEmpty()
|
||||
? createFallbackBounds()
|
||||
? createFallbackTerrainBounds()
|
||||
: {
|
||||
minX: terrainBounds.min.x,
|
||||
maxX: terrainBounds.max.x,
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
normalizeMapScale,
|
||||
useTerrainSnappedPosition,
|
||||
} from "@/hooks/three/useTerrainHeight";
|
||||
import type { MapNode } from "@/types/editor/editor";
|
||||
import type { MapNode } from "@/types/map/mapScene";
|
||||
|
||||
interface GeneratedMapNodeInstanceProps {
|
||||
node: MapNode;
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
normalizeMapScale,
|
||||
useTerrainHeightSampler,
|
||||
} from "@/hooks/three/useTerrainHeight";
|
||||
import type { MapAssetInstance } from "@/hooks/world/useMapInstancingData";
|
||||
import { optimizeGLTFSceneTextures } from "@/utils/three/optimizeGLTFScene";
|
||||
import type { MapAssetInstance } from "@/world/map-instancing/useMapInstancingData";
|
||||
|
||||
interface InstancedMapAssetProps {
|
||||
modelPath: string;
|
||||
@@ -102,8 +102,11 @@ function extractMeshes(scene: THREE.Group): MeshData[] {
|
||||
return [...groups.values()]
|
||||
.map((group) => {
|
||||
if (group.geometries.length === 1) {
|
||||
const [geometry] = group.geometries;
|
||||
if (!geometry) return null;
|
||||
|
||||
return {
|
||||
geometry: group.geometries[0] as THREE.BufferGeometry,
|
||||
geometry,
|
||||
material: group.material,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Suspense, useCallback, useMemo, useRef, useState } from "react";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { CHUNK_CONFIG } from "@/data/world/fogConfig";
|
||||
import { Suspense, useMemo } from "react";
|
||||
import { CHUNK_CONFIG } from "@/data/world/chunkStreamingConfig";
|
||||
import { useCameraMode } from "@/hooks/debug/useCameraMode";
|
||||
import { useSceneMode } from "@/hooks/debug/useSceneMode";
|
||||
import { useVisibleWorldChunks } from "@/hooks/world/useVisibleWorldChunks";
|
||||
import {
|
||||
isMapModelVisible,
|
||||
useMapPerformanceStore,
|
||||
@@ -10,13 +10,14 @@ import {
|
||||
import { InstancedMapAsset } from "@/world/map-instancing/InstancedMapAsset";
|
||||
import {
|
||||
MAP_INSTANCING_ASSETS,
|
||||
MAP_INSTANCING_ASSET_TYPES,
|
||||
type MapInstancingAssetConfig,
|
||||
type MapInstancingAssetType,
|
||||
} from "@/world/map-instancing/mapInstancingConfig";
|
||||
} from "@/data/world/mapInstancingConfig";
|
||||
import {
|
||||
type MapAssetInstance,
|
||||
useMapInstancingData,
|
||||
} from "@/world/map-instancing/useMapInstancingData";
|
||||
} from "@/hooks/world/useMapInstancingData";
|
||||
|
||||
interface MapAssetChunk {
|
||||
key: string;
|
||||
@@ -72,23 +73,20 @@ function createMapAssetChunks(
|
||||
}
|
||||
|
||||
export function MapInstancingSystem(): React.JSX.Element | null {
|
||||
const camera = useThree((state) => state.camera);
|
||||
const cameraMode = useCameraMode();
|
||||
const sceneMode = useSceneMode();
|
||||
const groups = useMapPerformanceStore((state) => state.groups);
|
||||
const models = useMapPerformanceStore((state) => state.models);
|
||||
const { data, isLoading } = useMapInstancingData();
|
||||
const lastUpdateRef = useRef(-CHUNK_CONFIG.updateInterval);
|
||||
const [activeChunkKeys, setActiveChunkKeys] = useState<Set<string>>(
|
||||
() => new Set(),
|
||||
);
|
||||
const streamingEnabled =
|
||||
CHUNK_CONFIG.enabled && sceneMode === "game" && cameraMode === "player";
|
||||
|
||||
const chunks = useMemo(() => {
|
||||
if (!data) return [];
|
||||
|
||||
return Object.entries(MAP_INSTANCING_ASSETS).flatMap(([type, config]) => {
|
||||
return MAP_INSTANCING_ASSET_TYPES.flatMap((type) => {
|
||||
const config = MAP_INSTANCING_ASSETS[type];
|
||||
|
||||
if (
|
||||
!config.enabled ||
|
||||
!isMapModelVisible(config.mapName, { groups, models })
|
||||
@@ -96,71 +94,14 @@ export function MapInstancingSystem(): React.JSX.Element | null {
|
||||
return [];
|
||||
}
|
||||
|
||||
const instances = data.get(type as MapInstancingAssetType);
|
||||
const instances = data.get(type);
|
||||
if (!instances || instances.length === 0) return [];
|
||||
|
||||
return createMapAssetChunks(
|
||||
type as MapInstancingAssetType,
|
||||
config,
|
||||
instances,
|
||||
);
|
||||
return createMapAssetChunks(type, config, instances);
|
||||
});
|
||||
}, [data, groups, models]);
|
||||
|
||||
const visibleChunks = streamingEnabled
|
||||
? chunks.filter((chunk) => {
|
||||
if (activeChunkKeys.size > 0) {
|
||||
return activeChunkKeys.has(chunk.key);
|
||||
}
|
||||
|
||||
return (
|
||||
Math.hypot(
|
||||
chunk.centerX - camera.position.x,
|
||||
chunk.centerZ - camera.position.z,
|
||||
) <= CHUNK_CONFIG.loadRadius
|
||||
);
|
||||
})
|
||||
: chunks;
|
||||
|
||||
const updateActiveChunks = useCallback(() => {
|
||||
const nextKeys = new Set<string>();
|
||||
const cameraX = camera.position.x;
|
||||
const cameraZ = camera.position.z;
|
||||
|
||||
for (const chunk of chunks) {
|
||||
const distance = Math.hypot(
|
||||
chunk.centerX - cameraX,
|
||||
chunk.centerZ - cameraZ,
|
||||
);
|
||||
const wasActive = activeChunkKeys.has(chunk.key);
|
||||
const radius = wasActive
|
||||
? CHUNK_CONFIG.unloadRadius
|
||||
: CHUNK_CONFIG.loadRadius;
|
||||
|
||||
if (distance <= radius) {
|
||||
nextKeys.add(chunk.key);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
nextKeys.size === activeChunkKeys.size &&
|
||||
[...nextKeys].every((key) => activeChunkKeys.has(key))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveChunkKeys(nextKeys);
|
||||
}, [activeChunkKeys, camera, chunks]);
|
||||
|
||||
useFrame(({ clock }) => {
|
||||
if (!streamingEnabled) return;
|
||||
|
||||
const now = clock.elapsedTime * 1000;
|
||||
if (now - lastUpdateRef.current < CHUNK_CONFIG.updateInterval) return;
|
||||
lastUpdateRef.current = now;
|
||||
|
||||
updateActiveChunks();
|
||||
});
|
||||
const visibleChunks = useVisibleWorldChunks(chunks, streamingEnabled);
|
||||
|
||||
if (isLoading || !data) {
|
||||
return null;
|
||||
|
||||
@@ -4,9 +4,9 @@ import { useGLTF } from "@react-three/drei";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { mergeGeometries } from "three/addons/utils/BufferGeometryUtils.js";
|
||||
import { useTerrainHeightSampler } from "@/hooks/three/useTerrainHeight";
|
||||
import type { VegetationInstance } from "@/hooks/world/useVegetationData";
|
||||
import { useWind } from "@/hooks/world/useWind";
|
||||
import { optimizeGLTFSceneTextures } from "@/utils/three/optimizeGLTFScene";
|
||||
import type { VegetationInstance } from "@/world/vegetation/useVegetationData";
|
||||
|
||||
interface InstancedVegetationProps {
|
||||
modelPath: string;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Suspense, useCallback, useMemo, useRef, useState } from "react";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { CHUNK_CONFIG } from "@/data/world/fogConfig";
|
||||
import { Suspense, useMemo } from "react";
|
||||
import { CHUNK_CONFIG } from "@/data/world/chunkStreamingConfig";
|
||||
import { useCameraMode } from "@/hooks/debug/useCameraMode";
|
||||
import { useSceneMode } from "@/hooks/debug/useSceneMode";
|
||||
import { useVisibleWorldChunks } from "@/hooks/world/useVisibleWorldChunks";
|
||||
import {
|
||||
isMapModelVisible,
|
||||
useMapPerformanceStore,
|
||||
@@ -11,8 +11,9 @@ import { InstancedVegetation } from "@/world/vegetation/InstancedVegetation";
|
||||
import {
|
||||
type VegetationInstance,
|
||||
useVegetationData,
|
||||
} from "@/world/vegetation/useVegetationData";
|
||||
} from "@/hooks/world/useVegetationData";
|
||||
import {
|
||||
VEGETATION_TYPE_KEYS,
|
||||
VEGETATION_TYPES,
|
||||
type VegetationType,
|
||||
} from "@/world/vegetation/vegetationConfig";
|
||||
@@ -80,87 +81,31 @@ function createVegetationChunks(
|
||||
}
|
||||
|
||||
export function VegetationSystem(): React.JSX.Element | null {
|
||||
const camera = useThree((state) => state.camera);
|
||||
const cameraMode = useCameraMode();
|
||||
const sceneMode = useSceneMode();
|
||||
const groups = useMapPerformanceStore((state) => state.groups);
|
||||
const models = useMapPerformanceStore((state) => state.models);
|
||||
const { data, isLoading } = useVegetationData();
|
||||
const lastUpdateRef = useRef(-CHUNK_CONFIG.updateInterval);
|
||||
const [activeChunkKeys, setActiveChunkKeys] = useState<Set<string>>(
|
||||
() => new Set(),
|
||||
);
|
||||
const streamingEnabled =
|
||||
CHUNK_CONFIG.enabled && sceneMode === "game" && cameraMode === "player";
|
||||
|
||||
const chunks = useMemo(() => {
|
||||
if (!data) return [];
|
||||
|
||||
return Object.entries(VEGETATION_TYPES).flatMap(([type, config]) => {
|
||||
return VEGETATION_TYPE_KEYS.flatMap((type) => {
|
||||
const config = VEGETATION_TYPES[type];
|
||||
|
||||
if (!config.enabled) return [];
|
||||
if (!isMapModelVisible(config.mapName, { groups, models })) return [];
|
||||
|
||||
const entry = data.get(config.mapName);
|
||||
if (!entry || entry.instances.length === 0) return [];
|
||||
|
||||
return createVegetationChunks(type as VegetationType, entry.instances);
|
||||
return createVegetationChunks(type, entry.instances);
|
||||
});
|
||||
}, [data, groups, models]);
|
||||
|
||||
const visibleChunks = streamingEnabled
|
||||
? chunks.filter((chunk) => {
|
||||
if (activeChunkKeys.size > 0) {
|
||||
return activeChunkKeys.has(chunk.key);
|
||||
}
|
||||
|
||||
return (
|
||||
Math.hypot(
|
||||
chunk.centerX - camera.position.x,
|
||||
chunk.centerZ - camera.position.z,
|
||||
) <= CHUNK_CONFIG.loadRadius
|
||||
);
|
||||
})
|
||||
: chunks;
|
||||
|
||||
const updateActiveChunks = useCallback(() => {
|
||||
const nextKeys = new Set<string>();
|
||||
const cameraX = camera.position.x;
|
||||
const cameraZ = camera.position.z;
|
||||
|
||||
for (const chunk of chunks) {
|
||||
const distance = Math.hypot(
|
||||
chunk.centerX - cameraX,
|
||||
chunk.centerZ - cameraZ,
|
||||
);
|
||||
const wasActive = activeChunkKeys.has(chunk.key);
|
||||
const radius = wasActive
|
||||
? CHUNK_CONFIG.unloadRadius
|
||||
: CHUNK_CONFIG.loadRadius;
|
||||
|
||||
if (distance <= radius) {
|
||||
nextKeys.add(chunk.key);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
nextKeys.size === activeChunkKeys.size &&
|
||||
[...nextKeys].every((key) => activeChunkKeys.has(key))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveChunkKeys(nextKeys);
|
||||
}, [activeChunkKeys, camera, chunks]);
|
||||
|
||||
useFrame(({ clock }) => {
|
||||
if (!streamingEnabled) return;
|
||||
|
||||
const now = clock.elapsedTime * 1000;
|
||||
if (now - lastUpdateRef.current < CHUNK_CONFIG.updateInterval) return;
|
||||
lastUpdateRef.current = now;
|
||||
|
||||
updateActiveChunks();
|
||||
});
|
||||
const visibleChunks = useVisibleWorldChunks(chunks, streamingEnabled);
|
||||
|
||||
if (isLoading || !data) {
|
||||
return null;
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
export const VEGETATION_LOD = {
|
||||
windAnimationRadius: 70,
|
||||
windFadeStart: 50,
|
||||
windFadeEnd: 70,
|
||||
};
|
||||
|
||||
export const VEGETATION_TYPES = {
|
||||
buissons: {
|
||||
mapName: "buisson",
|
||||
@@ -61,18 +55,19 @@ export const VEGETATION_TYPES = {
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type VegetationType = keyof typeof VEGETATION_TYPES;
|
||||
export const VEGETATION_TYPE_KEYS = [
|
||||
"buissons",
|
||||
"sapin",
|
||||
"arbre",
|
||||
"champdeble",
|
||||
"champdesoja",
|
||||
"champsdetournesol",
|
||||
] as const satisfies readonly (keyof typeof VEGETATION_TYPES)[];
|
||||
|
||||
export type VegetationType = (typeof VEGETATION_TYPE_KEYS)[number];
|
||||
|
||||
export const INSTANCED_MAP_EXCEPTIONS = new Set([
|
||||
"Scene",
|
||||
"blocking",
|
||||
"terrain",
|
||||
]);
|
||||
|
||||
export function getVegetationScaleMultiplier(name: string): number | null {
|
||||
const config = Object.values(VEGETATION_TYPES).find(
|
||||
(vegetationConfig) => vegetationConfig.mapName === name,
|
||||
);
|
||||
|
||||
return config?.scaleMultiplier ?? null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user