Repository navigation
Duplicate Model Rendering for Different Models #121
Description
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:

Activity
m6hdix commentedon Aug 2, 2024
help sir
m6hdix commentedon Aug 8, 2024
I would appreciate it if you could respond, I'm even willing to donate
RodrigoHamuy commentedon Aug 8, 2024
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 commentedon Aug 8, 2024
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:
2024-08-08.16-04-42.mp4
RodrigoHamuy commentedon Aug 8, 2024
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.