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