diff --git a/src/hooks/world/useMapInstancingData.ts b/src/hooks/world/useMapInstancingData.ts index 42c7632..e8166f2 100644 --- a/src/hooks/world/useMapInstancingData.ts +++ b/src/hooks/world/useMapInstancingData.ts @@ -4,16 +4,11 @@ import { MAP_INSTANCING_ASSET_TYPES, type MapInstancingAssetType, } from "@/data/world/mapInstancingConfig"; -import type { MapNode } from "@/types/map/mapScene"; -import { - type MapNodeInstanceTransform, - mapNodeToInstanceTransform, -} from "@/utils/map/mapInstanceTransform"; +import type { MapAssetInstance, MapNode } from "@/types/map/mapScene"; +import { mapNodeToInstanceTransform } from "@/utils/map/mapInstanceTransform"; import { logger } from "@/utils/core/Logger"; import { getMapNodes, loadMapSceneData } from "@/utils/map/loadMapSceneData"; -export type MapAssetInstance = MapNodeInstanceTransform; - export type MapInstancingData = Map; function extractMapInstancingData(mapNodes: MapNode[]): MapInstancingData { @@ -65,6 +60,11 @@ export function useMapInstancingData(): { logger.error("MapInstancing", "Failed to load map instancing data", { error: error instanceof Error ? error : String(error), }); + if (!cancelled) { + setData(null); + setIsLoading(false); + } + return; } const nodes = getMapNodes(); diff --git a/src/world/map-instancing/InstancedMapAsset.tsx b/src/world/map-instancing/InstancedMapAsset.tsx index a5848a5..bf6a68f 100644 --- a/src/world/map-instancing/InstancedMapAsset.tsx +++ b/src/world/map-instancing/InstancedMapAsset.tsx @@ -7,12 +7,13 @@ import { normalizeMapScale, useTerrainHeightSampler, } from "@/hooks/three/useTerrainHeight"; -import type { MapAssetInstance } from "@/hooks/world/useMapInstancingData"; +import type { MapAssetInstance } from "@/types/map/mapScene"; import { optimizeGLTFSceneTextures } from "@/utils/three/optimizeGLTFScene"; interface InstancedMapAssetProps { modelPath: string; instances: MapAssetInstance[]; + scaleMultiplier: number; castShadow: boolean; receiveShadow: boolean; } @@ -133,6 +134,7 @@ function extractMeshes(scene: THREE.Group): MeshData[] { function setInstanceMatrices( instancedMesh: THREE.InstancedMesh, instances: MapAssetInstance[], + scaleMultiplier: number, geometryBottomY: number, ): void { const position = new THREE.Vector3(); @@ -148,7 +150,11 @@ function setInstanceMatrices( position.set(...instance.position); rotation.set(...instance.rotation); quaternion.setFromEuler(rotation); - scale.set(...instance.scale); + scale.set( + instance.scale[0] * scaleMultiplier, + instance.scale[1] * scaleMultiplier, + instance.scale[2] * scaleMultiplier, + ); position.y += -geometryBottomY * scale.y; matrix.compose(position, quaternion, scale); instancedMesh.setMatrixAt(i, matrix); @@ -174,6 +180,7 @@ function getMeshBottomY(meshDataList: MeshData[]): number { export function InstancedMapAsset({ modelPath, instances, + scaleMultiplier, castShadow, receiveShadow, }: InstancedMapAssetProps): React.JSX.Element | null { @@ -219,7 +226,12 @@ export function InstancedMapAsset({ groundedInstances.length, ); - setInstanceMatrices(instancedMesh, groundedInstances, geometryBottomY); + setInstanceMatrices( + instancedMesh, + groundedInstances, + scaleMultiplier, + geometryBottomY, + ); instancedMesh.castShadow = castShadow; instancedMesh.receiveShadow = receiveShadow; instancedMesh.name = `instanced-map-asset-${index}`; @@ -239,7 +251,13 @@ export function InstancedMapAsset({ disposeInstancedMapMesh(mesh); } }; - }, [castShadow, groundedInstances, meshDataList, receiveShadow]); + }, [ + castShadow, + groundedInstances, + meshDataList, + receiveShadow, + scaleMultiplier, + ]); if (instances.length === 0) { return null; diff --git a/src/world/map-instancing/MapInstancingSystem.tsx b/src/world/map-instancing/MapInstancingSystem.tsx index 4e0c107..61d6f1e 100644 --- a/src/world/map-instancing/MapInstancingSystem.tsx +++ b/src/world/map-instancing/MapInstancingSystem.tsx @@ -14,10 +14,13 @@ import { type MapInstancingAssetConfig, type MapInstancingAssetType, } from "@/data/world/mapInstancingConfig"; -import { - type MapAssetInstance, - useMapInstancingData, -} from "@/hooks/world/useMapInstancingData"; +import { useMapInstancingData } from "@/hooks/world/useMapInstancingData"; +import type { MapAssetInstance } from "@/types/map/mapScene"; + +interface MapInstancingSystemProps { + onlyModelName?: string | null; + streaming?: boolean; +} interface MapAssetChunk { key: string; @@ -72,14 +75,20 @@ function createMapAssetChunks( }); } -export function MapInstancingSystem(): React.JSX.Element | null { +export function MapInstancingSystem({ + onlyModelName = null, + streaming = true, +}: MapInstancingSystemProps): React.JSX.Element | null { const cameraMode = useCameraMode(); const sceneMode = useSceneMode(); const groups = useMapPerformanceStore((state) => state.groups); const models = useMapPerformanceStore((state) => state.models); const { data, isLoading } = useMapInstancingData(); const streamingEnabled = - CHUNK_CONFIG.enabled && sceneMode === "game" && cameraMode === "player"; + streaming && + CHUNK_CONFIG.enabled && + sceneMode === "game" && + cameraMode === "player"; const chunks = useMemo(() => { if (!data) return []; @@ -87,6 +96,8 @@ export function MapInstancingSystem(): React.JSX.Element | null { return MAP_INSTANCING_ASSET_TYPES.flatMap((type) => { const config = MAP_INSTANCING_ASSETS[type]; + if (onlyModelName && config.mapName !== onlyModelName) return []; + if ( !config.enabled || !isMapModelVisible(config.mapName, { groups, models }) @@ -99,7 +110,7 @@ export function MapInstancingSystem(): React.JSX.Element | null { return createMapAssetChunks(type, config, instances); }); - }, [data, groups, models]); + }, [data, groups, models, onlyModelName]); const visibleChunks = useVisibleWorldChunks(chunks, streamingEnabled); @@ -114,6 +125,7 @@ export function MapInstancingSystem(): React.JSX.Element | null {