Files
Dwinzo_Demo/app/src/modules/builder/asset/models/models.tsx

149 lines
5.2 KiB
TypeScript

import { useEffect, useRef, useState } from "react";
import { Group, Vector3 } from "three";
import { CameraControls } from "@react-three/drei";
import { GLTFLoader } from "three/examples/jsm/Addons";
import { useThree, useFrame } from "@react-three/fiber";
import {
useContextActionStore,
useLimitDistance,
useRenderDistance,
} from "../../../../store/builder/store";
import { useSelectedAsset } from "../../../../store/simulation/useSimulationStore";
import { useSceneContext } from "../../../scene/sceneContext";
import useZoomMesh from "../../hooks/useZoomMesh";
import useCallBackOnKey from "../../../../utils/hooks/useCallBackOnKey";
import Model from "./model/model";
const distanceWorker = new Worker(
new URL("../../../../services/builder/webWorkers/distanceWorker.js", import.meta.url)
);
function Models({ loader }: { readonly loader: GLTFLoader }) {
const { controls, camera } = useThree();
const assetGroupRef = useRef<Group>(null);
const { assetStore, layout } = useSceneContext();
const { assets, selectedAssets, getSelectedAssetUuids, clearSelectedAssets } = assetStore();
const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
const { contextAction, setContextAction } = useContextActionStore();
const { limitDistance } = useLimitDistance();
const { renderDistance } = useRenderDistance();
const [renderMap, setRenderMap] = useState<Record<string, boolean>>({});
const { zoomMeshes } = useZoomMesh();
const cameraPos = useRef(new Vector3());
useEffect(() => {
// console.log(assets);
}, [assets]);
useEffect(() => {
const initialRenderMap: Record<string, boolean> = {};
assets.forEach((asset) => {
initialRenderMap[asset.modelUuid] = true;
});
setRenderMap(initialRenderMap);
}, [assets.length]);
useEffect(() => {
if (contextAction === "focusAsset") {
zoomMeshes(getSelectedAssetUuids());
setContextAction(null);
}
}, [contextAction]);
useCallBackOnKey(
() => {
if (selectedAssets.length > 0) {
zoomMeshes(getSelectedAssetUuids());
}
},
".",
{ dependencies: [selectedAssets.length], noRepeat: true }
);
useEffect(() => {
distanceWorker.onmessage = (e) => {
const { shouldRender, modelUuid } = e.data;
setRenderMap((prev) => {
if (prev[modelUuid] === shouldRender) return prev;
return { ...prev, [modelUuid]: shouldRender };
});
};
return () => {
distanceWorker.terminate();
};
}, [distanceWorker, layout]);
useFrame(() => {
camera.getWorldPosition(cameraPos.current);
for (const asset of assets) {
const isRendered = renderMap[asset.modelUuid] ?? false;
// distanceWorker.postMessage({
// modelUuid: asset.modelUuid,
// assetPosition: { x: asset.position[0], y: asset.position[1], z: asset.position[2] },
// cameraPosition: cameraPos.current,
// limitDistance,
// renderDistance,
// isRendered,
// });
const assetVec = new Vector3(...asset.position);
const cameraVec = new Vector3(
cameraPos.current.x,
cameraPos.current.y,
cameraPos.current.z
);
const distance = assetVec.distanceTo(cameraVec);
if (limitDistance) {
if (!isRendered && distance <= renderDistance) {
setRenderMap((prev) => {
if (prev[asset.modelUuid] === true) return prev;
return { ...prev, [asset.modelUuid]: true };
});
} else if (isRendered && distance > renderDistance) {
setRenderMap((prev) => {
if (prev[asset.modelUuid] === false) return prev;
return { ...prev, [asset.modelUuid]: false };
});
}
} else if (!isRendered) {
setRenderMap((prev) => {
if (prev[asset.modelUuid] === true) return prev;
return { ...prev, [asset.modelUuid]: true };
});
}
}
});
return (
<group
name="Asset Group"
ref={assetGroupRef}
onPointerMissed={(e) => {
e.stopPropagation();
if (selectedAssets.length > 0) {
const target = (controls as CameraControls).getTarget(new Vector3());
(controls as CameraControls).setTarget(target.x, 0, target.z, true);
clearSelectedAssets();
}
if (selectedAsset) {
clearSelectedAsset();
}
}}
>
{assets.map((asset) => (
<Model
key={asset.modelUuid}
asset={asset}
isRendered={renderMap[asset.modelUuid] ?? false}
loader={loader}
/>
))}
</group>
);
}
export default Models;