import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'; import gsap from 'gsap'; import * as THREE from 'three'; import * as CONSTANTS from '../../../types/world/worldConstants'; import { toast } from 'react-toastify'; import * as Types from "../../../types/world/worldTypes"; import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils'; import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi'; import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi'; import PointsCalculator from '../../simulation/events/points/functions/pointsCalculator'; async function loadInitialFloorItems( itemsGroup: Types.RefGroup, setFloorItems: Types.setFloorItemSetState, addEvent: (event: EventsSchema) => void, renderDistance: number ): Promise { if (!itemsGroup.current) return; let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; const email = localStorage.getItem('email'); const organization = (email!.split("@")[1]).split(".")[0]; const items = await getFloorAssets(organization); localStorage.setItem("FloorItems", JSON.stringify(items)); await initializeDB(); if (items.message === "floorItems not found") return; if (items) { const storedFloorItems: Types.FloorItems = items; const loader = new GLTFLoader(); const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/'); loader.setDRACOLoader(dracoLoader); let modelsLoaded = 0; const modelsToLoad = storedFloorItems.length; const camData = await getCamera(organization, localStorage.getItem('userId')!); let storedPosition; if (camData && camData.position) { storedPosition = camData?.position; } else { storedPosition = new THREE.Vector3(0, 40, 30); } if (!storedPosition) return; const cameraPosition = new THREE.Vector3(storedPosition.x, storedPosition.y, storedPosition.z); storedFloorItems.sort((a, b) => { const aPosition = new THREE.Vector3(a.position[0], a.position[1], a.position[2]); const bPosition = new THREE.Vector3(b.position[0], b.position[1], b.position[2]); return cameraPosition.distanceTo(aPosition) - cameraPosition.distanceTo(bPosition); }); for (const item of storedFloorItems) { if (!item.modelfileID) return; const itemPosition = new THREE.Vector3(item.position[0], item.position[1], item.position[2]); let storedPosition; if (localStorage.getItem("cameraPosition")) { storedPosition = JSON.parse(localStorage.getItem("cameraPosition")!); } else { storedPosition = new THREE.Vector3(0, 40, 30); } const cameraPosition = new THREE.Vector3(storedPosition.x, storedPosition.y, storedPosition.z); if (cameraPosition.distanceTo(itemPosition) < renderDistance) { await new Promise(async (resolve) => { // Check Three.js Cache const cachedModel = THREE.Cache.get(item.modelfileID!); if (cachedModel) { // console.log(`[Cache] Fetching ${item.modelName}`); processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, addEvent); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); return; } // Check IndexedDB const indexedDBModel = await retrieveGLTF(item.modelfileID!); if (indexedDBModel) { // console.log(`[IndexedDB] Fetching ${item.modelName}`); const blobUrl = URL.createObjectURL(indexedDBModel); loader.load(blobUrl, (gltf) => { URL.revokeObjectURL(blobUrl); THREE.Cache.remove(blobUrl); THREE.Cache.add(item.modelfileID!, gltf); processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, addEvent); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); }, undefined, (error) => { toast.error(`[IndexedDB] Error loading ${item.modelName}:`); URL.revokeObjectURL(blobUrl); resolve(); } ); return; } // Fetch from Backend // console.log(`[Backend] Fetching ${item.modelName}`); const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${item.modelfileID!}`; loader.load(modelUrl, async (gltf) => { const modelBlob = await fetch(modelUrl).then((res) => res.blob()); await storeGLTF(item.modelfileID!, modelBlob); THREE.Cache.add(item.modelfileID!, gltf); processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, addEvent); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); }, undefined, (error) => { toast.error(`[Backend] Error loading ${item.modelName}:`); resolve(); } ); }); } else { // console.log(`Item ${item.modelName} is not near`); setFloorItems((prevItems) => [ ...(prevItems || []), { modelUuid: item.modelUuid, modelName: item.modelName, position: item.position, rotation: item.rotation, modelfileID: item.modelfileID, isLocked: item.isLocked, isVisible: item.isVisible, }, ]); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, () => { }); } } // Dispose loader after all models dracoLoader.dispose(); } } function processLoadedModel( gltf: any, item: Types.FloorItemType, itemsGroup: Types.RefGroup, setFloorItems: Types.setFloorItemSetState, addEvent: (event: EventsSchema) => void, ) { const model = gltf.clone(); model.uuid = item.modelUuid; model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap); model.userData = { name: item.modelName, modelId: item.modelfileID, modelUuid: item.modelUuid, eventData: item.eventData }; model.position.set(...item.position); model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z); model.traverse((child: any) => { if (child.isMesh) { // Clone the material to ensure changes are independent // child.material = child.material.clone(); child.castShadow = true; child.receiveShadow = true; } }); itemsGroup?.current?.add(model); if (item.eventData) { setFloorItems((prevItems) => [ ...(prevItems || []), { modelUuid: item.modelUuid, modelName: item.modelName, position: item.position, rotation: item.rotation, modelfileID: item.modelfileID, isLocked: item.isLocked, isVisible: item.isVisible, eventData: item.eventData, }, ]); if (item.eventData.type === "Vehicle") { const vehicleEvent: VehicleEventSchema = { modelUuid: item.modelUuid, modelName: item.modelName, position: item.position, rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "vehicle", speed: 1, point: { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0], rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0], action: { actionUuid: THREE.MathUtils.generateUUID(), actionName: "Action 1", actionType: "travel", unLoadDuration: 5, loadCapacity: 10, steeringAngle: 0, pickUpPoint: null, unLoadPoint: null, triggers: [] } } }; addEvent(vehicleEvent); } else if (item.eventData.type === "Conveyor") { const ConveyorEvent: ConveyorEventSchema = { modelUuid: item.modelUuid, modelName: item.modelName, position: item.position, rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "transfer", speed: 1, points: item.eventData.points?.map((point: any, index: number) => ({ uuid: point.uuid || THREE.MathUtils.generateUUID(), position: [point.position[0], point.position[1], point.position[2]], rotation: [point.rotation[0], point.rotation[1], point.rotation[2]], action: { actionUuid: THREE.MathUtils.generateUUID(), actionName: `Action ${index + 1}`, actionType: 'default', material: 'Default material', delay: 0, spawnInterval: 5, spawnCount: 1, triggers: [] } })) || [], }; addEvent(ConveyorEvent); } else if (item.eventData.type === "StaticMachine") { const machineEvent: MachineEventSchema = { modelUuid: item.modelUuid, modelName: item.modelName, position: item.position, rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "machine", point: { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0], rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0], action: { actionUuid: THREE.MathUtils.generateUUID(), actionName: "Action 1", actionType: "process", processTime: 10, swapMaterial: "material-id", triggers: [] } } }; addEvent(machineEvent); } else if (item.eventData.type === "ArmBot") { const roboticArmEvent: RoboticArmEventSchema = { modelUuid: item.modelUuid, modelName: item.modelName, position: item.position, rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "roboticArm", speed: 1, point: { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0], rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0], actions: [ { actionUuid: THREE.MathUtils.generateUUID(), actionName: "Action 1", actionType: "pickAndPlace", process: { startPoint: [0, 0, 0], endPoint: [0, 0, 0] }, triggers: [] } ] } }; addEvent(roboticArmEvent); } } else { setFloorItems((prevItems) => [ ...(prevItems || []), { modelUuid: item.modelUuid, modelName: item.modelName, position: item.position, rotation: item.rotation, modelfileID: item.modelfileID, isLocked: item.isLocked, isVisible: item.isVisible, }, ]); } gsap.to(model.position, { y: item.position[1], duration: 1.5, ease: 'power2.out' }); gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: 'power2.out' }); } function checkLoadingCompletion( modelsLoaded: number, modelsToLoad: number, dracoLoader: DRACOLoader, resolve: () => void ) { if (modelsLoaded === modelsToLoad) { toast.success("Models Loaded!"); dracoLoader.dispose(); } resolve(); } export default loadInitialFloorItems;