Merge remote-tracking branch 'origin/main-dev' into main-demo
This commit is contained in:
@@ -16,12 +16,7 @@ import { getUserData } from "../../../functions/getUserData";
|
||||
import { useSceneContext } from "../../scene/sceneContext";
|
||||
import { useVersionContext } from "../version/versionContext";
|
||||
|
||||
const gltfLoaderWorker = new Worker(
|
||||
new URL(
|
||||
"../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js",
|
||||
import.meta.url
|
||||
)
|
||||
);
|
||||
const gltfLoaderWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js", import.meta.url));
|
||||
|
||||
function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
|
||||
const { activeModule } = useModuleStore();
|
||||
|
||||
@@ -1,47 +1,67 @@
|
||||
import { Line } from "@react-three/drei";
|
||||
import { Box3, Vector3 } from "three";
|
||||
import { Box3, Vector3, Quaternion } from "three";
|
||||
import { useMemo } from "react";
|
||||
import { Cylinder } from "@react-three/drei";
|
||||
|
||||
export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth }: { name: string; boundingBox: Box3 | null; color: string; lineWidth: number; }) => {
|
||||
const { points, size, center } = useMemo(() => {
|
||||
if (!boundingBox) { return { points: [], center: new Vector3(), size: new Vector3(), }; }
|
||||
export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { name: string; boundingBox: Box3 | null; color: string; lineWidth: number; }) => {
|
||||
const { edgeCylinders, center, size } = useMemo(() => {
|
||||
if (!boundingBox) return { edgeCylinders: [], center: new Vector3(), size: new Vector3() };
|
||||
|
||||
const min = boundingBox.min;
|
||||
const max = boundingBox.max;
|
||||
const center = boundingBox.getCenter(new Vector3());
|
||||
const size = boundingBox.getSize(new Vector3());
|
||||
|
||||
const edges: Array<[number, number, number]> = [
|
||||
[min.x, min.y, min.z], [max.x, min.y, min.z],
|
||||
[max.x, min.y, min.z], [max.x, max.y, min.z],
|
||||
[max.x, max.y, min.z], [min.x, max.y, min.z],
|
||||
[min.x, max.y, min.z], [min.x, min.y, min.z],
|
||||
|
||||
[min.x, min.y, max.z], [max.x, min.y, max.z],
|
||||
[max.x, min.y, max.z], [max.x, max.y, max.z],
|
||||
[max.x, max.y, max.z], [min.x, max.y, max.z],
|
||||
[min.x, max.y, max.z], [min.x, min.y, max.z],
|
||||
|
||||
[min.x, min.y, min.z], [min.x, min.y, max.z],
|
||||
[max.x, min.y, min.z], [max.x, min.y, max.z],
|
||||
[max.x, max.y, min.z], [max.x, max.y, max.z],
|
||||
[min.x, max.y, min.z], [min.x, max.y, max.z],
|
||||
const corners = [
|
||||
new Vector3(min.x, min.y, min.z),
|
||||
new Vector3(max.x, min.y, min.z),
|
||||
new Vector3(max.x, max.y, min.z),
|
||||
new Vector3(min.x, max.y, min.z),
|
||||
new Vector3(min.x, min.y, max.z),
|
||||
new Vector3(max.x, min.y, max.z),
|
||||
new Vector3(max.x, max.y, max.z),
|
||||
new Vector3(min.x, max.y, max.z),
|
||||
];
|
||||
|
||||
return { points: edges, center, size };
|
||||
}, [boundingBox]);
|
||||
const edgeIndices: [number, number][] = [
|
||||
[0, 1], [1, 2], [2, 3], [3, 0],
|
||||
[4, 5], [5, 6], [6, 7], [7, 4],
|
||||
[0, 4], [1, 5], [2, 6], [3, 7],
|
||||
];
|
||||
|
||||
const radius = 0.005 * lineWidth;
|
||||
|
||||
const edgeCylinders = edgeIndices.map(([startIdx, endIdx], i) => {
|
||||
const start = corners[startIdx];
|
||||
const end = corners[endIdx];
|
||||
const direction = new Vector3().subVectors(end, start);
|
||||
const length = direction.length();
|
||||
const midPoint = new Vector3().addVectors(start, end).multiplyScalar(0.5);
|
||||
const quaternion = new Quaternion().setFromUnitVectors(
|
||||
new Vector3(0, 1, 0),
|
||||
direction.clone().normalize()
|
||||
);
|
||||
|
||||
return {
|
||||
key: `edge-cylinder-${i}`,
|
||||
position: midPoint,
|
||||
rotation: quaternion,
|
||||
length,
|
||||
radius,
|
||||
};
|
||||
});
|
||||
|
||||
return { edgeCylinders, center, size };
|
||||
}, [boundingBox, lineWidth]);
|
||||
|
||||
if (!boundingBox) return null;
|
||||
|
||||
return (
|
||||
<group name={name}>
|
||||
<Line
|
||||
segments
|
||||
depthWrite={false}
|
||||
points={points}
|
||||
color={color}
|
||||
lineWidth={lineWidth}
|
||||
/>
|
||||
{edgeCylinders.map(({ key, position, rotation, length, radius }) => (
|
||||
<Cylinder key={key} args={[radius, radius, length, 6]} position={position} quaternion={rotation} >
|
||||
<meshBasicMaterial color={color} depthWrite={false} />
|
||||
</Cylinder>
|
||||
))}
|
||||
|
||||
<mesh visible={false} position={center}>
|
||||
<boxGeometry args={[size.x, size.y, size.z]} />
|
||||
|
||||
@@ -3,8 +3,8 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
|
||||
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||
import { ThreeEvent, useFrame, useThree } from '@react-three/fiber';
|
||||
import { useActiveTool, useDeletableFloorItem, useLimitDistance, useRenderDistance, useSelectedAssets, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store';
|
||||
import { ThreeEvent, useThree } from '@react-three/fiber';
|
||||
import { useActiveTool, useDeletableFloorItem, useSelectedAssets, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store';
|
||||
import { AssetBoundingBox } from '../../functions/assetBoundingBox';
|
||||
import { CameraControls } from '@react-three/drei';
|
||||
import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore';
|
||||
@@ -16,23 +16,23 @@ import { getUserData } from '../../../../../functions/getUserData';
|
||||
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||
import { useVersionContext } from '../../../version/versionContext';
|
||||
import { SkeletonUtils } from 'three-stdlib';
|
||||
import { useAnimationPlaySpeed, usePauseButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||
|
||||
import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';
|
||||
import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs';
|
||||
import { ModelAnimator } from './animator/modelAnimator';
|
||||
|
||||
function Model({ asset }: { readonly asset: Asset }) {
|
||||
|
||||
function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boolean }) {
|
||||
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
const savedTheme: string = localStorage.getItem("theme") || "light";
|
||||
const { camera, controls, gl } = useThree();
|
||||
const { controls, gl } = useThree();
|
||||
const { activeTool } = useActiveTool();
|
||||
const { toolMode } = useToolMode();
|
||||
const { toggleView } = useToggleView();
|
||||
const { subModule } = useSubModuleStore();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { speed } = useAnimationPlaySpeed();
|
||||
const { isPaused } = usePauseButtonStore();
|
||||
const { assetStore, eventStore, productStore } = useSceneContext();
|
||||
const { removeAsset, setAnimations, resetAnimation, setAnimationComplete } = assetStore();
|
||||
const { removeAsset, resetAnimation } = assetStore();
|
||||
const { setTop } = useTopData();
|
||||
const { setLeft } = useLeftData();
|
||||
const { getIsEventInProduct, addPoint } = productStore();
|
||||
@@ -43,23 +43,15 @@ function Model({ asset }: { readonly asset: Asset }) {
|
||||
const { socket } = useSocketStore();
|
||||
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
|
||||
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
|
||||
const { limitDistance } = useLimitDistance();
|
||||
const { renderDistance } = useRenderDistance();
|
||||
const leftDrag = useRef(false);
|
||||
const isLeftMouseDown = useRef(false);
|
||||
const rightDrag = useRef(false);
|
||||
const isRightMouseDown = useRef(false);
|
||||
const [isRendered, setIsRendered] = useState(false);
|
||||
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 mixerRef = useRef<THREE.AnimationMixer>();
|
||||
const actions = useRef<{ [name: string]: THREE.AnimationAction }>({});
|
||||
const [previousAnimation, setPreviousAnimation] = useState<string | null>(null);
|
||||
const [ikData, setIkData] = useState<any>();
|
||||
const blendFactor = useRef(0);
|
||||
const blendDuration = 0.5;
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
const { userId, organization } = getUserData();
|
||||
@@ -133,16 +125,6 @@ function Model({ asset }: { readonly asset: Asset }) {
|
||||
clone.animations = cachedModel.animations || [];
|
||||
setGltfScene(clone);
|
||||
calculateBoundingBox(clone);
|
||||
if (cachedModel.animations && clone.animations.length > 0) {
|
||||
const animationName = clone.animations.map((clip: any) => clip.name);
|
||||
setAnimations(asset.modelUuid, animationName)
|
||||
mixerRef.current = new THREE.AnimationMixer(clone);
|
||||
|
||||
clone.animations.forEach((animation: any) => {
|
||||
const action = mixerRef.current!.clipAction(animation);
|
||||
actions.current[animation.name] = action;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -202,21 +184,6 @@ function Model({ asset }: { readonly asset: Asset }) {
|
||||
|
||||
}, []);
|
||||
|
||||
useFrame(() => {
|
||||
const assetPosition = new THREE.Vector3(...asset.position);
|
||||
if (limitDistance) {
|
||||
if (!isRendered && assetPosition.distanceTo(camera.position) <= renderDistance) {
|
||||
setIsRendered(true);
|
||||
} else if (isRendered && assetPosition.distanceTo(camera.position) > renderDistance) {
|
||||
setIsRendered(false);
|
||||
}
|
||||
} else {
|
||||
if (!isRendered) {
|
||||
setIsRendered(true);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const handleDblClick = (asset: Asset) => {
|
||||
if (asset) {
|
||||
if (activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') {
|
||||
@@ -382,50 +349,6 @@ function Model({ asset }: { readonly asset: Asset }) {
|
||||
}
|
||||
}
|
||||
|
||||
const handleAnimationComplete = useCallback(() => {
|
||||
if (asset.animationState) {
|
||||
setAnimationComplete(asset.modelUuid, true);
|
||||
}
|
||||
}, [asset.animationState]);
|
||||
|
||||
useFrame((_, delta) => {
|
||||
if (mixerRef.current) {
|
||||
mixerRef.current.update(delta * (activeModule === 'simulation' ? speed : 1));
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!asset.animationState || !mixerRef.current) return;
|
||||
|
||||
const { current, loopAnimation, isPlaying } = asset.animationState;
|
||||
const currentAction = actions.current[current];
|
||||
const previousAction = previousAnimation ? actions.current[previousAnimation] : null;
|
||||
|
||||
if (isPlaying && currentAction && activeModule === 'simulation' && !isPaused) {
|
||||
blendFactor.current = 0;
|
||||
|
||||
currentAction.reset();
|
||||
currentAction.setLoop(loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce, loopAnimation ? Infinity : 1);
|
||||
currentAction.clampWhenFinished = true;
|
||||
|
||||
if (previousAction && previousAction !== currentAction) {
|
||||
previousAction.crossFadeTo(currentAction, blendDuration, false);
|
||||
}
|
||||
|
||||
currentAction.play();
|
||||
mixerRef.current.addEventListener('finished', handleAnimationComplete);
|
||||
setPreviousAnimation(current);
|
||||
} else {
|
||||
Object.values(actions.current).forEach((action) => action.stop());
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (mixerRef.current) {
|
||||
mixerRef.current.removeEventListener('finished', handleAnimationComplete);
|
||||
}
|
||||
};
|
||||
}, [asset.animationState?.current, asset.animationState?.isCompleted, asset.animationState?.isPlaying, isPaused, activeModule]);
|
||||
|
||||
useEffect(() => {
|
||||
const canvasElement = gl.domElement;
|
||||
|
||||
@@ -526,9 +449,15 @@ function Model({ asset }: { readonly asset: Asset }) {
|
||||
{gltfScene && (
|
||||
<>
|
||||
{isRendered ? (
|
||||
<primitive object={gltfScene} />
|
||||
<>
|
||||
|
||||
<primitive object={gltfScene} />
|
||||
|
||||
<ModelAnimator asset={asset} gltfScene={gltfScene} />
|
||||
|
||||
</>
|
||||
) : (
|
||||
<AssetBoundingBox name='Asset Fallback' boundingBox={boundingBox} color='gray' lineWidth={1} />
|
||||
<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} />
|
||||
|
||||
@@ -1,17 +1,45 @@
|
||||
import Model from './model/model';
|
||||
import { useThree } from '@react-three/fiber';
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useThree, useFrame } from "@react-three/fiber";
|
||||
import { Vector3 } from "three";
|
||||
import { CameraControls } from '@react-three/drei';
|
||||
import { Vector3 } from 'three';
|
||||
import { useSelectedFloorItem } from '../../../../store/builder/store';
|
||||
import { useLimitDistance, useRenderDistance, useSelectedFloorItem } from '../../../../store/builder/store';
|
||||
import { useSelectedAsset } from '../../../../store/simulation/useSimulationStore';
|
||||
import { useSceneContext } from '../../../scene/sceneContext';
|
||||
|
||||
import Model from './model/model';
|
||||
|
||||
const distanceWorker = new Worker(new URL("../../../../services/factoryBuilder/webWorkers/distanceWorker.js", import.meta.url));
|
||||
|
||||
function Models() {
|
||||
const { controls } = useThree();
|
||||
const { controls, camera } = useThree();
|
||||
const { assetStore } = useSceneContext();
|
||||
const { assets } = assetStore();
|
||||
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
|
||||
const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
|
||||
const { limitDistance } = useLimitDistance();
|
||||
const { renderDistance } = useRenderDistance();
|
||||
|
||||
const [renderMap, setRenderMap] = useState<Record<string, boolean>>({});
|
||||
|
||||
const cameraPos = useRef(new Vector3());
|
||||
|
||||
useEffect(() => {
|
||||
distanceWorker.onmessage = (e) => {
|
||||
const { shouldRender, modelUuid } = e.data;
|
||||
setRenderMap((prev) => {
|
||||
if (prev[modelUuid] === shouldRender) return prev;
|
||||
return { ...prev, [modelUuid]: shouldRender };
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
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, });
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<group
|
||||
@@ -28,11 +56,11 @@ function Models() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{assets.map((asset) =>
|
||||
<Model key={asset.modelUuid} asset={asset} />
|
||||
)}
|
||||
{assets.map((asset) => (
|
||||
<Model key={asset.modelUuid} asset={asset} isRendered={renderMap[asset.modelUuid] ?? false} />
|
||||
))}
|
||||
</group>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default Models
|
||||
export default Models;
|
||||
Reference in New Issue
Block a user