add initial components and utility functions for simulation and builder modules
This commit is contained in:
186
app/src/modules/builder/geomentries/assets/addAssetModel.ts
Normal file
186
app/src/modules/builder/geomentries/assets/addAssetModel.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
import * as THREE from 'three';
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import gsap from 'gsap';
|
||||
import { toast } from 'react-toastify';
|
||||
import TempLoader from './tempLoader';
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
import { retrieveGLTF, storeGLTF } from '../../../../utils/indexDB/idbUtils';
|
||||
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
|
||||
import { Socket } from 'socket.io-client';
|
||||
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||
|
||||
async function addAssetModel(
|
||||
raycaster: THREE.Raycaster,
|
||||
camera: THREE.Camera,
|
||||
pointer: THREE.Vector2,
|
||||
floorGroup: Types.RefGroup,
|
||||
setFloorItems: Types.setFloorItemSetState,
|
||||
itemsGroup: Types.RefGroup,
|
||||
isTempLoader: Types.RefBoolean,
|
||||
tempLoader: Types.RefMesh,
|
||||
socket: Socket<any>,
|
||||
selectedItem: any,
|
||||
setSelectedItem: any,
|
||||
plane: Types.RefMesh,
|
||||
): Promise<void> {
|
||||
|
||||
////////// Load Floor GLtf's and set the positions, rotation, type etc. in state and store in localstorage //////////
|
||||
|
||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
|
||||
try {
|
||||
isTempLoader.current = true;
|
||||
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);
|
||||
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const floorIntersections = raycaster.intersectObjects(floorGroup.current.children, true);
|
||||
const intersectedFloor = floorIntersections.find(intersect => intersect.object.name.includes("Floor"));
|
||||
|
||||
const planeIntersections = raycaster.intersectObject(plane.current!, true);
|
||||
const intersectedPlane = planeIntersections[0];
|
||||
|
||||
let intersectPoint: THREE.Vector3 | null = null;
|
||||
|
||||
if (intersectedFloor && intersectedPlane) {
|
||||
intersectPoint = intersectedFloor.distance < intersectedPlane.distance ? (new THREE.Vector3(intersectedFloor.point.x, Math.round(intersectedFloor.point.y), intersectedFloor.point.z)) : (new THREE.Vector3(intersectedPlane.point.x, 0, intersectedPlane.point.z));
|
||||
} else if (intersectedFloor) {
|
||||
intersectPoint = new THREE.Vector3(intersectedFloor.point.x, Math.round(intersectedFloor.point.y), intersectedFloor.point.z);
|
||||
} else if (intersectedPlane) {
|
||||
intersectPoint = new THREE.Vector3(intersectedPlane.point.x, 0, intersectedPlane.point.z);
|
||||
}
|
||||
|
||||
if (intersectPoint) {
|
||||
if (intersectPoint.y < 0) {
|
||||
intersectPoint = new THREE.Vector3(intersectPoint.x, 0, intersectPoint.z);
|
||||
}
|
||||
const cachedModel = THREE.Cache.get(selectedItem.id);
|
||||
if (cachedModel) {
|
||||
// console.log(`[Cache] Fetching ${selectedItem.name}`);
|
||||
handleModelLoad(cachedModel, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
|
||||
return;
|
||||
} else {
|
||||
const cachedModelBlob = await retrieveGLTF(selectedItem.id);
|
||||
|
||||
if (cachedModelBlob) {
|
||||
// console.log(`Added ${selectedItem.name} from indexDB`);
|
||||
|
||||
const blobUrl = URL.createObjectURL(cachedModelBlob);
|
||||
loader.load(blobUrl, (gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(selectedItem.id, gltf);
|
||||
handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
|
||||
},
|
||||
() => {
|
||||
TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
|
||||
});
|
||||
} else {
|
||||
// console.log(`Added ${selectedItem.name} from Backend`);
|
||||
|
||||
loader.load(`${url_Backend_dwinzo}/api/v1/AssetFile/${selectedItem.id}`, async (gltf) => {
|
||||
const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v1/AssetFile/${selectedItem.id}`).then((res) => res.blob());
|
||||
await storeGLTF(selectedItem.id, modelBlob);
|
||||
THREE.Cache.add(selectedItem.id, gltf);
|
||||
await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
|
||||
},
|
||||
() => {
|
||||
TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching asset model:', error);
|
||||
} finally {
|
||||
setSelectedItem({});
|
||||
}
|
||||
}
|
||||
|
||||
async function handleModelLoad(
|
||||
gltf: any,
|
||||
intersectPoint: THREE.Vector3,
|
||||
selectedItem: any,
|
||||
itemsGroup: Types.RefGroup,
|
||||
tempLoader: Types.RefMesh,
|
||||
isTempLoader: Types.RefBoolean,
|
||||
setFloorItems: Types.setFloorItemSetState,
|
||||
socket: Socket<any>
|
||||
) {
|
||||
const model = gltf.scene.clone();
|
||||
model.userData = { name: selectedItem.name, modelId: selectedItem.id };
|
||||
model.position.set(intersectPoint!.x, 3 + intersectPoint!.y, intersectPoint!.z);
|
||||
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
|
||||
|
||||
model.traverse((child: any) => {
|
||||
if (child) {
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
}
|
||||
});
|
||||
|
||||
itemsGroup.current.add(model);
|
||||
if (tempLoader.current) {
|
||||
(<any>tempLoader.current.material).dispose();
|
||||
(<any>tempLoader.current.geometry).dispose();
|
||||
itemsGroup.current.remove(tempLoader.current);
|
||||
tempLoader.current = undefined;
|
||||
}
|
||||
|
||||
const newFloorItem: Types.FloorItemType = {
|
||||
modeluuid: model.uuid,
|
||||
modelname: selectedItem.name,
|
||||
modelfileID: selectedItem.id,
|
||||
position: [intersectPoint!.x, intersectPoint!.y, intersectPoint!.z],
|
||||
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, },
|
||||
isLocked: false,
|
||||
isVisible: true
|
||||
};
|
||||
|
||||
setFloorItems((prevItems) => {
|
||||
const updatedItems = [...(prevItems || []), newFloorItem];
|
||||
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
|
||||
return updatedItems;
|
||||
});
|
||||
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email ? email.split("@")[1].split(".")[0] : "default";
|
||||
|
||||
//REST
|
||||
|
||||
// await setFloorItemApi(
|
||||
// organization,
|
||||
// newFloorItem.modeluuid,
|
||||
// newFloorItem.modelname,
|
||||
// newFloorItem.position,
|
||||
// { "x": model.rotation.x, "y": model.rotation.y, "z": model.rotation.z },
|
||||
// newFloorItem.modelfileID!,
|
||||
// false,
|
||||
// true,
|
||||
// );
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization,
|
||||
modeluuid: newFloorItem.modeluuid,
|
||||
modelname: newFloorItem.modelname,
|
||||
modelfileID: newFloorItem.modelfileID,
|
||||
position: newFloorItem.position,
|
||||
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
socketId: socket.id,
|
||||
};
|
||||
|
||||
socket.emit("v1:FloorItems:set", data);
|
||||
|
||||
gsap.to(model.position, { y: newFloorItem.position[1], duration: 1.5, ease: "power2.out" });
|
||||
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: "power2.out", onComplete: () => { toast.success("Model Added!"); } });
|
||||
}
|
||||
|
||||
export default addAssetModel;
|
||||
153
app/src/modules/builder/geomentries/assets/assetManager.ts
Normal file
153
app/src/modules/builder/geomentries/assets/assetManager.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import * as THREE from "three";
|
||||
import gsap from "gsap";
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
import { initializeDB, retrieveGLTF, storeGLTF } from "../../../../utils/indexDB/idbUtils";
|
||||
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
let currentTaskId = 0; // Track the active task
|
||||
let activePromises = new Map<number, boolean>(); // Map to track task progress
|
||||
|
||||
export default async function assetManager(
|
||||
data: any,
|
||||
itemsGroup: Types.RefGroup,
|
||||
loader: GLTFLoader,
|
||||
) {
|
||||
const taskId = ++currentTaskId; // Increment taskId for each call
|
||||
activePromises.set(taskId, true); // Mark task as active
|
||||
|
||||
// console.log("Received message from worker:", data);
|
||||
|
||||
if (data.toRemove.length > 0) {
|
||||
data.toRemove.forEach((uuid: string) => {
|
||||
const item = itemsGroup.current.getObjectByProperty("uuid", uuid);
|
||||
if (item) {
|
||||
// Traverse and dispose of resources
|
||||
// item.traverse((child: THREE.Object3D) => {
|
||||
// if (child instanceof THREE.Mesh) {
|
||||
// if (child.geometry) child.geometry.dispose();
|
||||
// if (Array.isArray(child.material)) {
|
||||
// child.material.forEach((material) => {
|
||||
// if (material.map) material.map.dispose();
|
||||
// material.dispose();
|
||||
// });
|
||||
// } else if (child.material) {
|
||||
// if (child.material.map) child.material.map.dispose();
|
||||
// child.material.dispose();
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// Remove the object from the scene
|
||||
itemsGroup.current.remove(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (data.toAdd.length > 0) {
|
||||
await initializeDB();
|
||||
|
||||
for (const item of data.toAdd) {
|
||||
if (!activePromises.get(taskId)) return; // Stop processing if task is canceled
|
||||
|
||||
await new Promise<void>(async (resolve) => {
|
||||
const modelUrl = `${url_Backend_dwinzo}/api/v1/AssetFile/${item.modelfileID!}`;
|
||||
|
||||
// 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, 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); // Add to cache
|
||||
processLoadedModel(gltf.scene.clone(), item, itemsGroup, resolve);
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
toast.error(`[IndexedDB] Error loading ${item.modelname}:`);
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch from Backend
|
||||
// console.log(`[Backend] Fetching ${item.modelname}`);
|
||||
loader.load(
|
||||
modelUrl,
|
||||
async (gltf) => {
|
||||
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
||||
await storeGLTF(item.modelfileID!, modelBlob); // Store in IndexedDB
|
||||
THREE.Cache.add(item.modelfileID!, gltf); // Add to cache
|
||||
processLoadedModel(gltf.scene.clone(), item, itemsGroup, resolve);
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
toast.error(`[Backend] Error loading ${item.modelname}:`);
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function processLoadedModel(
|
||||
gltf: any,
|
||||
item: Types.FloorItemType,
|
||||
itemsGroup: Types.RefGroup,
|
||||
resolve: () => void
|
||||
) {
|
||||
if (!activePromises.get(taskId)) return; // Stop processing if task is canceled
|
||||
|
||||
const existingModel = itemsGroup.current.getObjectByProperty("uuid", item.modeluuid);
|
||||
if (existingModel) {
|
||||
// console.log(`Model ${item.modelname} already exists in the scene.`);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const model = gltf;
|
||||
model.uuid = item.modeluuid;
|
||||
model.userData = { name: item.modelname, modelId: item.modelfileID };
|
||||
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
|
||||
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);
|
||||
|
||||
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: 0.5, ease: "power2.out", onStart: resolve, });
|
||||
}
|
||||
}
|
||||
|
||||
activePromises.delete(taskId); // Mark task as complete
|
||||
}
|
||||
|
||||
// Cancel ongoing task when new call arrives
|
||||
export function cancelOngoingTasks() {
|
||||
activePromises.clear(); // Clear all ongoing tasks
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
|
||||
let lastUpdateTime = 0;
|
||||
|
||||
export default function assetVisibility(
|
||||
itemsGroup: Types.RefGroup,
|
||||
cameraPosition: Types.Vector3,
|
||||
renderDistance: Types.Number,
|
||||
throttleTime = 100
|
||||
): void {
|
||||
const now = performance.now();
|
||||
if (now - lastUpdateTime < throttleTime) return;
|
||||
lastUpdateTime = now;
|
||||
|
||||
if (!itemsGroup?.current || !cameraPosition) return;
|
||||
|
||||
itemsGroup.current.children.forEach((child) => {
|
||||
const Distance = cameraPosition.distanceTo(child.position);
|
||||
if (Distance <= renderDistance) {
|
||||
child.visible = true;
|
||||
} else {
|
||||
child.visible = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
|
||||
function DeletableHoveredFloorItems(
|
||||
state: Types.ThreeState,
|
||||
itemsGroup: Types.RefGroup,
|
||||
hoveredDeletableFloorItem: Types.RefMesh,
|
||||
setDeletableFloorItem: any
|
||||
): void {
|
||||
|
||||
////////// Altering the color of the hovered GLTF item during the Deletion time //////////
|
||||
|
||||
state.raycaster.setFromCamera(state.pointer, state.camera);
|
||||
const intersects = state.raycaster.intersectObjects(itemsGroup.current.children, true);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
if (intersects[0].object.name === "Pole") {
|
||||
return;
|
||||
}
|
||||
if (hoveredDeletableFloorItem.current) {
|
||||
hoveredDeletableFloorItem.current = undefined;
|
||||
setDeletableFloorItem(null);
|
||||
}
|
||||
let currentObject = intersects[0].object;
|
||||
|
||||
while (currentObject) {
|
||||
if (currentObject.name === "Scene") {
|
||||
hoveredDeletableFloorItem.current = currentObject as THREE.Mesh;
|
||||
setDeletableFloorItem(currentObject);
|
||||
break;
|
||||
}
|
||||
currentObject = currentObject.parent as THREE.Object3D;
|
||||
}
|
||||
} else {
|
||||
if (hoveredDeletableFloorItem.current) {
|
||||
hoveredDeletableFloorItem.current = undefined;
|
||||
setDeletableFloorItem(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DeletableHoveredFloorItems;
|
||||
@@ -0,0 +1,82 @@
|
||||
import { toast } from 'react-toastify';
|
||||
import * as THREE from 'three';
|
||||
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
import { getFloorItems } from '../../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
|
||||
// import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi';
|
||||
import { Socket } from 'socket.io-client';
|
||||
|
||||
async function DeleteFloorItems(
|
||||
itemsGroup: Types.RefGroup,
|
||||
hoveredDeletableFloorItem: Types.RefMesh,
|
||||
setFloorItems: Types.setFloorItemSetState,
|
||||
socket: Socket<any>
|
||||
): Promise<void> {
|
||||
|
||||
////////// Deleting the hovered Floor GLTF from the scene (itemsGroup.current) and from the floorItems and also update it in the localstorage //////////
|
||||
|
||||
if (hoveredDeletableFloorItem.current) {
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
const items = await getFloorItems(organization);
|
||||
const removedItem = items.find(
|
||||
(item: { modeluuid: string }) => item.modeluuid === hoveredDeletableFloorItem.current?.uuid
|
||||
);
|
||||
|
||||
if (!removedItem) {
|
||||
return
|
||||
}
|
||||
|
||||
//REST
|
||||
|
||||
// const response = await deleteFloorItem(organization, removedItem.modeluuid, removedItem.modelname);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization: organization,
|
||||
modeluuid: removedItem.modeluuid,
|
||||
modelname: removedItem.modelname,
|
||||
socketId: socket.id
|
||||
}
|
||||
|
||||
const response = socket.emit('v1:FloorItems:delete', data)
|
||||
|
||||
if (response) {
|
||||
const updatedItems = items.filter(
|
||||
(item: { modeluuid: string }) => item.modeluuid !== hoveredDeletableFloorItem.current?.uuid
|
||||
);
|
||||
|
||||
const storedItems = JSON.parse(localStorage.getItem("FloorItems") || '[]');
|
||||
const updatedStoredItems = storedItems.filter((item: { modeluuid: string }) => item.modeluuid !== hoveredDeletableFloorItem.current?.uuid);
|
||||
localStorage.setItem("FloorItems", JSON.stringify(updatedStoredItems));
|
||||
|
||||
if (hoveredDeletableFloorItem.current) {
|
||||
// Traverse and dispose of resources
|
||||
hoveredDeletableFloorItem.current.traverse((child: THREE.Object3D) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
if (child.geometry) child.geometry.dispose();
|
||||
if (Array.isArray(child.material)) {
|
||||
child.material.forEach((material) => {
|
||||
if (material.map) material.map.dispose();
|
||||
material.dispose();
|
||||
});
|
||||
} else if (child.material) {
|
||||
if (child.material.map) child.material.map.dispose();
|
||||
child.material.dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Remove the object from the scene
|
||||
itemsGroup.current.remove(hoveredDeletableFloorItem.current);
|
||||
}
|
||||
setFloorItems(updatedItems);
|
||||
toast.success("Model Removed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DeleteFloorItems;
|
||||
29
app/src/modules/builder/geomentries/assets/tempLoader.ts
Normal file
29
app/src/modules/builder/geomentries/assets/tempLoader.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
|
||||
function TempLoader(
|
||||
intersectPoint: Types.Vector3,
|
||||
isTempLoader: Types.RefBoolean,
|
||||
tempLoader: Types.RefMesh,
|
||||
itemsGroup: Types.RefGroup
|
||||
): void {
|
||||
|
||||
////////// Temporary Loader that indicates the gltf is being loaded //////////
|
||||
|
||||
////////// Bug: Can't Load More than one TempLoader if done, it won't leave the scene //////////
|
||||
|
||||
if (tempLoader.current) {
|
||||
itemsGroup.current.remove(tempLoader.current);
|
||||
}
|
||||
if (isTempLoader.current) {
|
||||
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
|
||||
const cubeMaterial = new THREE.MeshBasicMaterial({ color: "white" });
|
||||
tempLoader.current = new THREE.Mesh(cubeGeometry, cubeMaterial);
|
||||
tempLoader.current.position.set(intersectPoint.x, 0.5 + intersectPoint.y, intersectPoint.z);
|
||||
itemsGroup.current.add(tempLoader.current);
|
||||
isTempLoader.current = false;
|
||||
}
|
||||
}
|
||||
|
||||
export default TempLoader;
|
||||
Reference in New Issue
Block a user