218 lines
8.2 KiB
TypeScript
218 lines
8.2 KiB
TypeScript
import * as THREE from 'three';
|
|
import { useEffect, useRef, useState } from 'react';
|
|
import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
|
|
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
|
import { useDeletableFloorItem, useSelectedAssets, useSelectedFloorItem, useToggleView, useToolMode } from '../../../../../store/builder/store';
|
|
import { AssetBoundingBox } from '../../functions/assetBoundingBox';
|
|
import useModuleStore from '../../../../../store/useModuleStore';
|
|
import { useSceneContext } from '../../../../scene/sceneContext';
|
|
import { SkeletonUtils } from 'three-stdlib';
|
|
|
|
import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs';
|
|
import { ModelAnimator } from './animator/modelAnimator';
|
|
import { useModelEventHandlers } from './eventHandlers/useEventHandlers';
|
|
|
|
function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendered: boolean, loader: GLTFLoader }) {
|
|
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
|
const savedTheme: string = localStorage.getItem("theme") || "light";
|
|
const { toolMode } = useToolMode();
|
|
const { toggleView } = useToggleView();
|
|
const { activeModule } = useModuleStore();
|
|
const { assetStore } = useSceneContext();
|
|
const { resetAnimation } = assetStore();
|
|
const { setDeletableFloorItem } = useDeletableFloorItem();
|
|
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
|
|
const [gltfScene, setGltfScene] = useState<GLTF["scene"] | null>(null);
|
|
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
|
|
const [isSelected, setIsSelected] = useState(false);
|
|
const groupRef = useRef<THREE.Group>(null);
|
|
const [ikData, setIkData] = useState<any>();
|
|
const { selectedAssets } = useSelectedAssets();
|
|
|
|
useEffect(() => {
|
|
if (!ikData && asset.eventData && asset.eventData.type === 'ArmBot') {
|
|
getAssetIksApi(asset.assetId).then((data) => {
|
|
if (data.iks) {
|
|
const iks: IK[] = data.iks;
|
|
setIkData(iks);
|
|
}
|
|
})
|
|
}
|
|
}, [asset.modelUuid, ikData])
|
|
|
|
useEffect(() => {
|
|
setDeletableFloorItem(null);
|
|
if (selectedFloorItem === null || selectedFloorItem.userData.modelUuid !== asset.modelUuid) {
|
|
resetAnimation(asset.modelUuid);
|
|
}
|
|
}, [activeModule, toolMode, selectedFloorItem])
|
|
|
|
useEffect(() => {
|
|
if (selectedFloorItem && selectedFloorItem.userData.modelUuid === asset.modelUuid) {
|
|
setSelectedFloorItem(groupRef.current);
|
|
}
|
|
}, [isRendered, selectedFloorItem])
|
|
|
|
useEffect(() => {
|
|
if (selectedAssets.length > 0) {
|
|
if (selectedAssets.some((selectedAsset: THREE.Object3D) => selectedAsset.userData.modelUuid === asset.modelUuid)) {
|
|
setIsSelected(true);
|
|
} else {
|
|
setIsSelected(false);
|
|
}
|
|
} else {
|
|
setIsSelected(false);
|
|
}
|
|
}, [selectedAssets])
|
|
|
|
useEffect(() => {
|
|
if (gltfScene) {
|
|
gltfScene.traverse((child: any) => {
|
|
if (child.isMesh) {
|
|
child.castShadow = true;
|
|
child.receiveShadow = true;
|
|
}
|
|
})
|
|
}
|
|
}, [gltfScene]);
|
|
|
|
useEffect(() => {
|
|
// Calculate Bounding Box
|
|
const calculateBoundingBox = (scene: THREE.Object3D) => {
|
|
const box = new THREE.Box3().setFromObject(scene);
|
|
setBoundingBox(box);
|
|
};
|
|
|
|
// Check Cache
|
|
const assetId = asset.assetId;
|
|
const cachedModel = THREE.Cache.get(assetId);
|
|
if (cachedModel) {
|
|
const clone: any = SkeletonUtils.clone(cachedModel.scene);
|
|
clone.animations = cachedModel.animations || [];
|
|
setGltfScene(clone);
|
|
calculateBoundingBox(clone);
|
|
return;
|
|
}
|
|
|
|
// Check IndexedDB
|
|
retrieveGLTF(assetId).then((indexedDBModel) => {
|
|
if (indexedDBModel) {
|
|
const blobUrl = URL.createObjectURL(indexedDBModel);
|
|
loader.load(
|
|
blobUrl,
|
|
(gltf) => {
|
|
URL.revokeObjectURL(blobUrl);
|
|
THREE.Cache.remove(blobUrl);
|
|
THREE.Cache.add(assetId, gltf);
|
|
setGltfScene(gltf.scene.clone());
|
|
calculateBoundingBox(gltf.scene);
|
|
},
|
|
undefined,
|
|
(error) => {
|
|
echo.error(`[IndexedDB] Error loading ${asset.modelName}:`);
|
|
URL.revokeObjectURL(blobUrl);
|
|
}
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Fetch from Backend
|
|
const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${assetId}`;
|
|
loader.load(
|
|
modelUrl,
|
|
(gltf: GLTF) => {
|
|
fetch(modelUrl)
|
|
.then((response) => response.blob())
|
|
.then((modelBlob) => storeGLTF(assetId, modelBlob))
|
|
.then(() => {
|
|
THREE.Cache.add(assetId, gltf);
|
|
setGltfScene(gltf.scene.clone());
|
|
calculateBoundingBox(gltf.scene);
|
|
})
|
|
.catch((error) => {
|
|
console.error(
|
|
`[Backend] Error storing/loading ${asset.modelName}:`,
|
|
error
|
|
);
|
|
});
|
|
},
|
|
undefined,
|
|
(error) => {
|
|
echo.error(`[Backend] Error loading ${asset.modelName}:`);
|
|
}
|
|
);
|
|
}).catch((err) => {
|
|
console.error("Failed to load model:", asset.assetId, err);
|
|
});
|
|
}, []);
|
|
|
|
const { handleDblClick, handleClick, handlePointerOver, handlePointerOut, handleContextMenu } = useModelEventHandlers({ boundingBox, groupRef });
|
|
|
|
return (
|
|
<group
|
|
key={asset.modelUuid}
|
|
name='Asset Model'
|
|
ref={groupRef}
|
|
uuid={asset.modelUuid}
|
|
position={asset.position}
|
|
rotation={asset.rotation}
|
|
visible={asset.isVisible}
|
|
userData={{ ...asset, iks: ikData }}
|
|
castShadow
|
|
receiveShadow
|
|
onDoubleClick={(e) => {
|
|
if (!toggleView) {
|
|
e.stopPropagation();
|
|
handleDblClick(asset);
|
|
}
|
|
}}
|
|
onClick={(e) => {
|
|
if (!toggleView) {
|
|
e.stopPropagation();
|
|
handleClick(e, asset);
|
|
}
|
|
}}
|
|
onPointerOver={(e) => {
|
|
if (!toggleView) {
|
|
e.stopPropagation();
|
|
handlePointerOver(asset);
|
|
}
|
|
}}
|
|
onPointerLeave={(e) => {
|
|
if (!toggleView) {
|
|
e.stopPropagation();
|
|
handlePointerOut(e, asset);
|
|
}
|
|
}}
|
|
onContextMenu={(e) => {
|
|
e.stopPropagation();
|
|
handleContextMenu(asset, e);
|
|
}}
|
|
>
|
|
{gltfScene && (
|
|
<>
|
|
{isRendered ? (
|
|
<>
|
|
|
|
<primitive object={gltfScene} />
|
|
|
|
<ModelAnimator asset={asset} gltfScene={gltfScene} />
|
|
|
|
</>
|
|
) : (
|
|
<>
|
|
{!isSelected &&
|
|
<AssetBoundingBox name='Asset Fallback' boundingBox={boundingBox} color='gray' lineWidth={2.5} />
|
|
}
|
|
</>
|
|
)}
|
|
{isSelected &&
|
|
<AssetBoundingBox name='Asset BBox' boundingBox={boundingBox} color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"} lineWidth={2.7} />
|
|
}
|
|
</>
|
|
)}
|
|
</group>
|
|
);
|
|
}
|
|
|
|
export default Model; |