Level of detail(lod)

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

Any reason not to use react-three-fiber ? With it, you could just use drei Detailed.

1 Like

here’s the sandbox for drei/detailed https://codesandbox.io/p/sandbox/re-using-geometry-and-level-of-detail-12nmp?

i could point out a hundred issues with the code you posted, but time is short. don’t use react like that. you don’t mix imperative and declarative. trash all this code, use react-three-fiber. it’s a small renderer, just like react-dom. because of that you won’t have a mix of two conflicting worlds any longer, same tree, same state model, full integration with react (suspense, context, etc), the whole of reacts eco system is available to you and fiber has its own eco system for threejs. that lod component is a good example of that, everything you could possibly want is already there.

2 Likes

Iam a begginer to 3js and actually iam so confused right now, still learning 3js using the docs,chatgpt…
so you are saying i cant implement lod in my code?
if yes can anyone pls implement that in my code .it would be a great help