RodrigoHamuy / react-three-map

Use React and Three.js inside Mapbox and Maplibre
https://rodrigohamuy.github.io/react-three-map/
MIT License
201 stars 28 forks source link

Duplicate Model Rendering for Different Models #121

Closed m6hdix closed 3 months ago

m6hdix commented 3 months ago

I am experiencing a bug when rendering multiple 3D models using react-three-map and react-three-fiber. Despite having different URLs for the models, the same model is rendered across all canvases. This issue occurs when trying to load multiple models that are close to each other on the map.

Use the provided MapPolygons component. Ensure there are at least three models with distinct URLs that are close to each other geographically. Observe that the same model is rendered in all canvases, even though their URLs are different. Expected Behavior: Each canvas should render the 3D model specified by its unique URL.

Actual Behavior: All canvases render the same model, ignoring the distinct URLs.

Relevant Code:

import { Layer, Source, useMap, Marker } from "react-map-gl";
import { useLoader } from "@react-three/fiber";
import { Canvas } from "react-three-map/maplibre";
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
import { HemisphereLight } from "three";
import { BORDER_COLORS } from "../../Services/Constants/BorderColors";
import { POLYGON_COLORS } from "../../Services/Constants/PolygonColors";
import useRequest from "../../Services/Hooks/useRequest";
import { ClipLoader } from "react-spinners";

const FBXModel = memo(({ url, rotation, setLoading, uniqueKey }) => {
  const fbx = useLoader(FBXLoader, url, (loader) => {
    loader.manager.onStart = () => setLoading(true);
    loader.manager.onLoad = () => setLoading(false);
    loader.manager.onError = () => setLoading(false);
  });

  const fbxRef = useRef();
  return (
    <group ref={fbxRef} rotation={rotation} scale={0.0097} key={uniqueKey}>
      <hemisphereLight
        args={["#ffffff", "#60666C"]}
        intensity={12}
        key={`${uniqueKey}-light`}
      />
      <primitive object={fbx} key={`${uniqueKey}-primitive`} />
    </group>
  );
});

const MapPolygons = () => {
  const map = useMap();
  const bounds = map.current.getBounds();
  const [features, setFeatures] = useState([]);
  const [buildingModels, setBuildingModels] = useState([]);
  const [zoom, setZoom] = useState(map.current.getZoom());
  const [isLoading, setIsLoading] = useState(false);

  const { Request } = useRequest();

  useEffect(() => {
    const handleViewportChange = () => {
      setZoom(map.current.getZoom());
    };

    map.current.on("zoomend", handleViewportChange);

    return () => {
      map.current.off("zoomend", handleViewportChange);
    };
  }, [map]);

  useEffect(() => {
    window.Echo.channel("feature-status").listen(
      ".feature-status-changed",
      (e) => {
        const data = [];
        for (const feature of features) {
          if (parseInt(feature.id) === parseInt(e.data.id)) {
            feature.rgb = e.data.rgb;
          }
          data.push(feature);
        }

        setFeatures(data);
      }
    );
  }, [features]);

  useEffect(() => {
    if (bounds.getSouthWest().lng && zoom >= 14) {
      const loadBuildings = zoom >= 15 ? "&load_buildings=1" : "";
      Request(
        `features?points[]=${bounds.getSouthWest().lng},${
          bounds.getSouthWest().lat
        }&points[]=${bounds.getSouthEast().lng},${
          bounds.getSouthEast().lat
        }&points[]=${bounds.getNorthWest().lng},${
          bounds.getNorthWest().lat
        }&points[]=${bounds.getNorthEast().lng},${
          bounds.getNorthEast().lat
        }${loadBuildings}`
      ).then((response) => {
        const newFeatures = [];
        const newBuildingModels = [];
        for (const feature of response?.data?.data) {
          newFeatures.push({
            id: feature?.geometry?.feature_id,
            rgb: feature?.properties?.rgb,
            coordinates: feature?.geometry?.coordinates.map((coordinate) => [
              parseFloat(coordinate.x),
              parseFloat(coordinate.y),
            ]),
          });

          if (feature.building_models) {
            for (const model of feature.building_models) {
              newBuildingModels.push(model);
            }
          }
        }

        setFeatures((prevFeatures) => [...prevFeatures, ...newFeatures]);
        setBuildingModels((prevModels) => [
          ...prevModels,
          ...newBuildingModels,
        ]);
      });
    }
  }, [bounds.getSouthWest().lng, zoom]);

  return (
    <>
      {zoom >= 14 && (
        <Source
          id="polygons"
          type="geojson"
          data={{
            type: "FeatureCollection",
            features: features.map((polygon) => ({
              type: "Feature",
              properties: {
                id: polygon.id,
                fill: POLYGON_COLORS[polygon.rgb],
                border: BORDER_COLORS[polygon.rgb],
              },
              geometry: {
                type: "Polygon",
                coordinates: [polygon.coordinates],
              },
            })),
          }}
        >
          <Layer
            id="polygon-fill-layer"
            type="fill"
            paint={{
              "fill-color": ["get", "fill"],
            }}
          />
          <Layer
            id="polygon-outline-layer"
            type="line"
            paint={{
              "line-color": ["get", "border"],
              "line-width": 3,
            }}
          />
        </Source>
      )}
      {zoom >= 15 &&
        buildingModels.length > 0 &&
        buildingModels.map((model) => {
          return (
            <>
              {isLoading && <ClipLoader color="#36d7b7" />}
              <Canvas
                latitude={parseFloat(model.building.position.split(", ")[0])}
                longitude={parseFloat(model.building.position.split(", ")[1])}
                key={`${model.id}-canvas`}
              >
                <FBXModel
                  url={model.file.url}
                  rotation={[0, 0, 0]}
                  setLoading={setIsLoading}
                  uniqueKey={`${model.id}-model`}
                />
              </Canvas>
            </>
          );
        })}
    </>
  );
};

export default MapPolygons;

# The models have different URLs and unique keys. This issue is impacting the accurate visualization of 3D models on the map. Environment:

image

m6hdix commented 3 months ago

help sir

m6hdix commented 3 months ago

I would appreciate it if you could respond, I'm even willing to donate

RodrigoHamuy commented 3 months ago

Can you put this on a sandbox to be able to replicate? Here is a starting one you can fork https://codesandbox.io/p/sandbox/react-three-map-gettings-started-dhw34w

m6hdix commented 3 months ago

Hello RodrigoHamuy,

Thank you for reviewing the issue I raised. I have created a sandbox environment to demonstrate the problem. Please note that this is just for testing purposes and not intended for production.

Here is the link to the sandbox: https://codesandbox.io/p/live/176d120c-fd63-4726-b612-7f48d5e13b30

Please make sure to install and enable the CORS error extension, and for the environments to load correctly, zoom in to 15x.

I appreciate your time and look forward to your feedback.

Best regards example video:

https://github.com/user-attachments/assets/40836d13-3be6-46b9-b274-40b3b36972ef

RodrigoHamuy commented 3 months ago

Do you have a minimum example without having to install custom extensions?

That sandbox is still a bit complicated for debugging.

But I can see you are creating a new <Canvas> per model. Instead, I would recommend to use <NearCoordinates> instead.