HI I HAVE WRITTEN A CODE FOR LOADING LARGE GB FILES,FOR THAT I ADDED OCTREE,RAYCASTING AND ALSO ADDED LOD IN THE CODE .BUT THE PROBLEM IS AFTER IMPLEMENTING LOD MY OUTPUT IS LAGGING .CAN ANYONE HELP ME TO IMPLEMENT LOD CORRECTLY
CODE BELOW
type or past
// LOD
import React, { useEffect, useRef, useState } from "react";
import * as THREE from "three";
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { SimplifyModifier } from "three/examples/jsm/modifiers/SimplifyModifier";
import { openDB } from "idb";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faEyeSlash, faEye, faSearch } from "@fortawesome/free-solid-svg-icons";
import Stats from "stats.js"; // Import stats.js
import "./App.css";
function FBXViewer() {
const mountRef = useRef(null);
const sceneRef = useRef(new THREE.Scene());
const cameraRef = useRef(
new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
)
);
const rendererRef = useRef(new THREE.WebGLRenderer({ antialias: true }));
const controlsRef = useRef(null);
const cumulativeBoundingBox = useRef(
new THREE.Box3(
new THREE.Vector3(Infinity, Infinity, Infinity),
new THREE.Vector3(-Infinity, -Infinity, -Infinity)
)
);
const [isVisible, setIsVisible] = useState(true);
const [db, setDb] = useState(null);
const [boundingBoxes, setBoundingBoxes] = useState([]); // State to store bounding boxes
const raycasterRef = useRef(new THREE.Raycaster());
const mouseRef = useRef(new THREE.Vector2());
const highlightedBoundingBoxHelper = useRef(null);
const statsRef = useRef(new Stats()); // Ref for stats.js
useEffect(() => {
const initDB = async () => {
const database = await openDB("fbx-files-db", 1, {
upgrade(db) {
if (!db.objectStoreNames.contains("files")) {
db.createObjectStore("files", {
keyPath: "id",
autoIncrement: true,
});
}
},
});
await clearDatabase(database);
setDb(database);
await loadModelsFromDB(database);
};
initDB();
rendererRef.current.setSize(window.innerWidth, window.innerHeight);
rendererRef.current.setClearColor(0xd3d3d3);
rendererRef.current.gammaOutput = true;
rendererRef.current.gammaFactor = 2.2;
mountRef.current.appendChild(rendererRef.current.domElement);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
sceneRef.current.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight.position.set(0, 1, 0);
sceneRef.current.add(directionalLight);
controlsRef.current = new OrbitControls(
cameraRef.current,
rendererRef.current.domElement
);
controlsRef.current.enableDamping = true;
controlsRef.current.dampingFactor = 0.1;
sceneRef.Near;
// Add stats.js panel to the DOM
statsRef.current.showPanel(0); // 0: fps, 1: ms, 2: memory
document.body.appendChild(statsRef.current.dom);
animate();
return () => {
mountRef.current.removeChild(rendererRef.current.domElement);
controlsRef.current.dispose();
document.body.removeChild(statsRef.current.dom); // Clean up stats.js panel
};
}, []);
useEffect(() => {
// Log the boundingBoxes state whenever it changes
console.log("Bounding Boxes:", boundingBoxes);
identifyMeshesInCumulativeBoundingBox();
}, [boundingBoxes]);
const clearDatabase = async (database) => {
const tx = database.transaction("files", "readwrite");
const store = tx.objectStore("files");
await store.clear();
await tx.done;
};
const loadModels = async (files) => {
const loader = new FBXLoader();
const objects = [];
Array.from(files).forEach((file) => {
const reader = new FileReader();
reader.onload = async (event) => {
const result = event.target.result;
const arrayBuffer = result;
if (db) {
await db.put("files", { id: file.name, data: arrayBuffer });
console.log(`Stored file: ${file.name}`);
loadModelsFromDB(db);
}
};
reader.readAsArrayBuffer(file);
});
};
const loadModelsFromDB = async (database) => {
const loader = new FBXLoader();
const tx = database.transaction("files", "readonly");
const store = tx.objectStore("files");
const allFiles = await store.getAll();
const objects = [];
for (const file of allFiles) {
const arrayBuffer = file.data;
loader.load(
URL.createObjectURL(new Blob([arrayBuffer])),
(object) => {
object.traverse((child) => {
if (child.isMesh && child.material) {
child.material = new THREE.MeshStandardMaterial({
color: child.material.color,
map: child.material.map,
});
}
});
const lod = createLOD(object); // Create LOD for the object
const boundingBox = new THREE.Box3().setFromObject(lod);
cumulativeBoundingBox.current.union(boundingBox);
sceneRef.current.add(lod);
setBoundingBoxes((prev) => {
const updatedBoundingBoxes = [...prev, boundingBox];
createBoundingBoxCubes(updatedBoundingBoxes); // Call function to create cubes
return updatedBoundingBoxes;
});
adjustCamera();
},
undefined,
(error) => {
console.error("Error loading model:", error);
}
);
}
};
const createLOD = (object) => {
const lod = new THREE.LOD();
const modifier = new SimplifyModifier();
// Clone the object for different levels of detail
const highDetailObject = object.clone();
const mediumDetailObject = object.clone();
const lowDetailObject = object.clone();
// Simplify the geometry for medium and low detail levels
mediumDetailObject.traverse((child) => {
if (child.isMesh) {
const count = Math.floor(child.geometry.attributes.position.count * 0.5); // Reduce to 50% vertices
child.geometry = modifier.modify(child.geometry, count);
child.material = new THREE.MeshStandardMaterial({ color: 0x00ff00 }); // Green for medium detail
}
});
lowDetailObject.traverse((child) => {
if (child.isMesh) {
const count = Math.floor(child.geometry.attributes.position.count * 0.1); // Reduce to 10% vertices
child.geometry = modifier.modify(child.geometry, count);
child.material = new THREE.MeshStandardMaterial({ color: 0x0000ff }); // Blue for low detail
}
});
lod.addLevel(highDetailObject, 0);
lod.addLevel(mediumDetailObject, 50);
lod.addLevel(lowDetailObject, 200);
return lod;
};
const createBoundingBoxCubes = (updatedBoundingBoxes) => {
updatedBoundingBoxes.forEach((boundingBox) => {
const helper = new THREE.Box3Helper(boundingBox, 0x90ee90); // Light Green color for individual bounding boxes
sceneRef.current.add(helper);
});
// Create a cube that encompasses the cumulative bounding box
const size = cumulativeBoundingBox.current.getSize(new THREE.Vector3());
const center = cumulativeBoundingBox.current.getCenter(new THREE.Vector3());
const encompassingCube = new THREE.Mesh(
new THREE.BoxGeometry(size.x, size.y, size.z),
new THREE.MeshStandardMaterial({
color: 0x90ee90, // Light Green color for cumulative bounding box
wireframe: true,
})
);
encompassingCube.position.copy(center);
sceneRef.current.add(encompassingCube);
// Subdivide the cumulative bounding box
const subBoxes = subdivideBoundingBox(cumulativeBoundingBox.current, 4);
subBoxes.forEach((box) => {
const helper = new THREE.Box3Helper(box, 0x90ee90); //Light Green color for subdivision bounding boxes
sceneRef.current.add(helper);
});
};
const subdivideBoundingBox = (box, divisions) => {
const subBoxes = [];
const size = box.getSize(new THREE.Vector3());
const step = size.divideScalar(divisions);
for (let i = 0; i < divisions; i++) {
for (let j = 0; j < divisions; j++) {
for (let k = 0; k < divisions; k++) {
const min = new THREE.Vector3(
box.min.x + i * step.x,
box.min.y + j * step.y,
box.min.z + k * step.z
);
const max = new THREE.Vector3(
box.min.x + (i + 1) * step.x,
box.min.y + (j + 1) * step.y,
box.min.z + (k + 1) * step.z
);
subBoxes.push(new THREE.Box3(min, max));
}
}
}
return subBoxes;
};
const identifyMeshesInCumulativeBoundingBox = () => {
const meshesInBoundingBox = [];
sceneRef.current.traverse((object) => {
if (object.isMesh) {
const boundingBox = new THREE.Box3().setFromObject(object);
if (cumulativeBoundingBox.current.intersectsBox(boundingBox)) {
meshesInBoundingBox.push(object);
}
}
});
// console.log("Meshes inside cumulative bounding box:", meshesInBoundingBox);
};
const adjustCamera = () => {
const center = new THREE.Vector3();
cumulativeBoundingBox.current.getCenter(center);
const size = cumulativeBoundingBox.current.getSize(new THREE.Vector3());
const distance = size.length();
const fov = cameraRef.current.fov * (Math.PI / 180);
let cameraZ = distance / (2 * Math.tan(fov / 2));
cameraZ *= 2.5;
cameraRef.current.position.set(center.x, center.y, center.z + cameraZ);
cameraRef.current.lookAt(center);
controlsRef.current.target.copy(center);
controlsRef.current.update();
};
const onFileChange = (event) => {
cumulativeBoundingBox.current = new THREE.Box3(
new THREE.Vector3(Infinity, Infinity, Infinity),
new THREE.Vector3(-Infinity, -Infinity, -Infinity)
);
setBoundingBoxes([]); // Reset bounding boxes state
loadModels(event.target.files);
};
const animate = () => {
requestAnimationFrame(animate);
if (isVisible) {
statsRef.current.begin(); // Start measuring
controlsRef.current.update();
rendererRef.current.render(sceneRef.current, cameraRef.current);
performRaycasting(); // Call the raycasting function in the animation loop
statsRef.current.end(); // End measuring
}
};
const toggleVisibility = (visible) => {
setIsVisible(visible);
sceneRef.current.traverse(function (object) {
if (object instanceof THREE.Mesh) {
object.visible = visible;
}
});
};
const resetCameraView = () => {
const center = new THREE.Vector3();
cumulativeBoundingBox.current.getCenter(center);
const size = cumulativeBoundingBox.current.getSize(new THREE.Vector3());
const distance = size.length();
const fov = cameraRef.current.fov * (Math.PI / 180);
let cameraZ = distance / (2 * Math.tan(fov / 2));
cameraZ *= 2.5;
cameraRef.current.position.set(center.x, center.y, center.z + cameraZ);
cameraRef.current.lookAt(center);
controlsRef.current.target.copy(center);
controlsRef.current.update();
};
const performRaycasting = () => {
raycasterRef.current.setFromCamera(mouseRef.current, cameraRef.current);
const intersects = raycasterRef.current.intersectObjects(
sceneRef.current.children,
true
);
if (intersects.length > 0) {
const intersect = intersects[0];
const hitMesh = intersect.object;
const boundingBox = new THREE.Box3().setFromObject(hitMesh);
console.log("Hit mesh details:", hitMesh);
console.log("Bounding box:", boundingBox);
// Highlight the bounding box helper in red
highlightBoundingBoxHelper(boundingBox);
}
};
const highlightBoundingBoxHelper = (boundingBox) => {
if (highlightedBoundingBoxHelper.current) {
// Reset the color of the previously highlighted bounding box
highlightedBoundingBoxHelper.current.material.color.set(0x90ee90);
}
// Find the bounding box helper in the scene
const helper = sceneRef.current.children.find(
(child) => child.isBox3Helper && child.box.equals(boundingBox)
);
if (helper) {
helper.material.color.set(0xff0000); // Set color to red
highlightedBoundingBoxHelper.current = helper;
}
};
return (
<div className="main">
<div className="canvas-container">
<input
className="button"
type="file"
multiple
onChange={onFileChange}
accept=".fbx"
/>
<div ref={mountRef} style={{ width: "99%", height: "100vh" }}></div>
</div>
<div className="button-container">
<button
className="custom-button hide-show"
onClick={() => toggleVisibility(true)}
>
<FontAwesomeIcon icon={faEye} />
</button>
<button
className="custom-button"
onClick={() => toggleVisibility(false)}
>
<FontAwesomeIcon icon={faEyeSlash} />
</button>
<button className="custom-button fit-view" onClick={resetCameraView}>
<FontAwesomeIcon icon={faSearch} />
</button>
<div className="bounding-boxes">
<h3>Bounding Boxes:</h3>
{boundingBoxes.map((box, index) => (
<div key={index}>
<p>
Box {index + 1} - Min:{" "}
{`(${box.min.x.toFixed(2)}, ${box.min.y.toFixed(
2
)}, ${box.min.z.toFixed(2)})`}
Max:{" "}
{`(${box.max.x.toFixed(2)}, ${box.max.y.toFixed(
2
)}, ${box.max.z.toFixed(2)})`}
</p>
</div>
))}
<h3>Cumulative Bounding Box:</h3>
<p>
Min:{" "}
{`(${cumulativeBoundingBox.current.min.x.toFixed(
2
)}, ${cumulativeBoundingBox.current.min.y.toFixed(
2
)}, ${cumulativeBoundingBox.current.min.z.toFixed(2)})`}
Max:{" "}
{`(${cumulativeBoundingBox.current.max.x.toFixed(
2
)}, ${cumulativeBoundingBox.current.max.y.toFixed(
2
)}, ${cumulativeBoundingBox.current.max.z.toFixed(2)})`}
</p>
</div>
</div>
</div>
);
}
export default FBXViewer;
e code here