Merge branch 'main-dev' into dev-physics
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 390 KiB |
|
After Width: | Height: | Size: 239 KiB |
|
After Width: | Height: | Size: 127 KiB |
BIN
app/src/assets/textures/floor/tex2/MI_FloorMats01_Normal.png
Normal file
|
After Width: | Height: | Size: 6.6 MiB |
BIN
app/src/assets/textures/floor/tex2/MI_FloorMats01_baseColor.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 3.1 MiB |
BIN
app/src/assets/textures/floor/tex3/metal_plate_diff_1k.jpg
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
app/src/assets/textures/floor/tex3/metal_plate_metal_1k.png
Normal file
|
After Width: | Height: | Size: 347 KiB |
BIN
app/src/assets/textures/floor/tex3/metal_plate_nor_gl_1k.png
Normal file
|
After Width: | Height: | Size: 580 KiB |
BIN
app/src/assets/textures/floor/tex3/metal_plate_rough_1k.png
Normal file
|
After Width: | Height: | Size: 324 KiB |
BIN
app/src/assets/textures/hdr/autoshop_01_1k.hdr
Normal file
BIN
app/src/assets/textures/hdr/empty_warehouse_01_1k.hdr
Normal file
BIN
app/src/assets/textures/hdr/industrial_pipe_and_valve_01_1k.hdr
Normal file
BIN
app/src/assets/textures/hdr/machine_shop_02_1k.hdr
Normal file
@@ -4,6 +4,9 @@ import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
|
||||
|
||||
import defaultTexture from '../../../../assets/textures/floor/white.png';
|
||||
import flootTexture1 from '../../../../assets/textures/floor/factory wall texture.jpg';
|
||||
import flootTexture2 from '../../../../assets/textures/floor/tex1/MI_FactoryConcreteFloor01_BaseColor.001.jpg';
|
||||
import flootTexture3 from '../../../../assets/textures/floor/tex2/MI_FloorMats01_baseColor.png';
|
||||
import flootTexture4 from '../../../../assets/textures/floor/tex3/metal_plate_diff_1k.jpg';
|
||||
|
||||
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
|
||||
|
||||
@@ -13,9 +16,12 @@ type Material = {
|
||||
textureName: string;
|
||||
};
|
||||
|
||||
const materials = [
|
||||
export const materials = [
|
||||
{ texture: defaultTexture, textureId: "Default Material", textureName: "Default Material" },
|
||||
{ texture: flootTexture1, textureId: "Material 1", textureName: "Grunge Concrete Wall" }
|
||||
{ texture: flootTexture1, textureId: "Material 1", textureName: "Grunge Concrete Wall" },
|
||||
{ texture: flootTexture2, textureId: "Material 2", textureName: "Tiled Floor" },
|
||||
{ texture: flootTexture3, textureId: "Material 3", textureName: "Metal Floor" },
|
||||
{ texture: flootTexture4, textureId: "Material 4", textureName: "Metal Floor 2" },
|
||||
];
|
||||
|
||||
const FloorProperties = () => {
|
||||
@@ -30,7 +36,7 @@ const FloorProperties = () => {
|
||||
top: materials.find((mat) => mat.textureId === topMaterial) || null,
|
||||
side: materials.find((mat) => mat.textureId === sideMaterial) || null,
|
||||
});
|
||||
}, []);
|
||||
}, [sideMaterial, topMaterial]);
|
||||
|
||||
const handleDepthChange = (val: string) => {
|
||||
setFloorDepth(parseFloat(val));
|
||||
|
||||
@@ -2,15 +2,13 @@ import { useEffect, useState } from "react";
|
||||
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
|
||||
import InputToggle from "../../../ui/inputs/InputToggle";
|
||||
|
||||
import defaultTexture from '../../../../assets/textures/floor/white.png';
|
||||
import floorTexture1 from '../../../../assets/textures/floor/factory wall texture.jpg';
|
||||
|
||||
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
|
||||
import { useSceneContext } from "../../../../modules/scene/sceneContext";
|
||||
import { useVersionContext } from "../../../../modules/builder/version/versionContext";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { getUserData } from "../../../../functions/getUserData";
|
||||
import { useSocketStore } from "../../../../store/builder/store";
|
||||
import { materials } from "./FloorProperties";
|
||||
|
||||
// import { upsertFloorApi } from "../../../../services/factoryBuilder/floor/upsertFloorApi";
|
||||
|
||||
@@ -29,11 +27,6 @@ const SelectedFloorProperties = () => {
|
||||
|
||||
const [activeSurface, setActiveSurface] = useState<"top" | "side">("top");
|
||||
|
||||
const materials = [
|
||||
{ texture: defaultTexture, textureId: "Default Material", textureName: "Default Material" },
|
||||
{ texture: floorTexture1, textureId: "Material 1", textureName: "Grunge Concrete" }
|
||||
];
|
||||
|
||||
const floor = selectedFloor ? getFloorById(selectedFloor.userData.floorUuid) : null;
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
// import { GLTF, GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
// import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
|
||||
// import * as THREE from "three";
|
||||
// import * as Types from "../../../types/world/worldTypes";
|
||||
// import { getWallItems } from "../../../services/factoryBuilder/asset/wallAsset/getWallItemsApi";
|
||||
// import { retrieveGLTF, storeGLTF } from "../../../utils/indexDB/idbUtils";
|
||||
// import { getUserData } from "../../../functions/getUserData";
|
||||
|
||||
// async function loadInitialWallItems(
|
||||
// setWallItems: Types.setWallItemSetState,
|
||||
// projectId?: string,
|
||||
// versionId?: string
|
||||
// ): Promise<void> {
|
||||
// if (!projectId || !versionId) return;
|
||||
// try {
|
||||
// const { organization, email } = getUserData();
|
||||
|
||||
// if (!email) {
|
||||
// console.error("No email found in localStorage");
|
||||
// }
|
||||
|
||||
// const items = await getWallItems(organization, projectId, versionId);
|
||||
|
||||
// let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
|
||||
// if (!items || items.length === 0) {
|
||||
// localStorage.removeItem("WallItems");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// localStorage.setItem("WallItems", JSON.stringify(items));
|
||||
|
||||
// const loader = new GLTFLoader();
|
||||
// const dracoLoader = new DRACOLoader();
|
||||
// dracoLoader.setDecoderPath(
|
||||
// "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/"
|
||||
// );
|
||||
// loader.setDRACOLoader(dracoLoader);
|
||||
|
||||
// const loadedWallItems = await Promise.all(
|
||||
// items.map(async (item: Types.WallItem) => {
|
||||
// // Check THREE.js cache first
|
||||
// const cachedModel = THREE.Cache.get(item.assetId!);
|
||||
// if (cachedModel) {
|
||||
// return processModel(cachedModel, item);
|
||||
// }
|
||||
|
||||
// // Check IndexedDB cache
|
||||
// const cachedModelBlob = await retrieveGLTF(item.assetId!);
|
||||
// if (cachedModelBlob) {
|
||||
// const blobUrl = URL.createObjectURL(cachedModelBlob);
|
||||
// return new Promise<Types.WallItem>((resolve) => {
|
||||
// loader.load(blobUrl, (gltf) => {
|
||||
// URL.revokeObjectURL(blobUrl);
|
||||
// THREE.Cache.add(item.assetId!, gltf);
|
||||
// resolve(processModel(gltf, item));
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
// // Load from original URL if not cached
|
||||
// const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${item.assetId!}`;
|
||||
// return new Promise<Types.WallItem>((resolve) => {
|
||||
// loader.load(modelUrl, async (gltf) => {
|
||||
// try {
|
||||
// // Cache the model
|
||||
// const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
||||
// await storeGLTF(item.assetId!, modelBlob);
|
||||
// THREE.Cache.add(item.assetId!, gltf);
|
||||
// resolve(processModel(gltf, item));
|
||||
// } catch (error) {
|
||||
// console.error("Failed to cache model:", error);
|
||||
// resolve(processModel(gltf, item));
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// })
|
||||
// );
|
||||
|
||||
// setWallItems(loadedWallItems);
|
||||
// } catch (error) {
|
||||
// console.error("Failed to load wall items:", error);
|
||||
// }
|
||||
// }
|
||||
|
||||
// function processModel(gltf: GLTF, item: Types.WallItem): Types.WallItem {
|
||||
// const model = gltf.scene.clone();
|
||||
// model.uuid = item.modelUuid!;
|
||||
|
||||
// model.children[0]?.children?.forEach((child: THREE.Object3D) => {
|
||||
// if (child.name !== "CSG_REF") {
|
||||
// child.castShadow = true;
|
||||
// child.receiveShadow = true;
|
||||
// }
|
||||
// });
|
||||
|
||||
// return {
|
||||
// type: item.type,
|
||||
// model: model,
|
||||
// modelName: item.modelName,
|
||||
// assetId: item.assetId,
|
||||
// scale: item.scale,
|
||||
// csgscale: item.csgscale,
|
||||
// csgposition: item.csgposition,
|
||||
// position: item.position,
|
||||
// quaternion: item.quaternion,
|
||||
// };
|
||||
// }
|
||||
|
||||
// export default loadInitialWallItems;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useToggleView } from '../../../../store/builder/store';
|
||||
import AisleInstance from './instance/aisleInstance';
|
||||
import Point from '../../point/point';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as THREE from 'three';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { Extrude } from '@react-three/drei';
|
||||
import { Instances, Instance } from '@react-three/drei';
|
||||
import * as Constants from '../../../../../../types/world/worldConstants';
|
||||
import { useToolMode } from '../../../../../../store/builder/store';
|
||||
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
|
||||
@@ -10,8 +10,16 @@ function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const { toolMode } = useToolMode();
|
||||
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
|
||||
|
||||
const arrows = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrows-aisle') return [];
|
||||
const { arrowGeometry, arrowInstances } = useMemo(() => {
|
||||
const result = {
|
||||
arrowGeometry: null as THREE.ExtrudeGeometry | null,
|
||||
arrowInstances: [] as {
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
}[],
|
||||
};
|
||||
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrows-aisle') return result;
|
||||
|
||||
const start = new THREE.Vector3(...aisle.points[0].position);
|
||||
const end = new THREE.Vector3(...aisle.points[1].position);
|
||||
@@ -24,69 +32,67 @@ function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
direction.normalize();
|
||||
|
||||
const count = Math.floor((length + spacing) / (arrowLength + spacing));
|
||||
const angle = Math.atan2(direction.x, direction.z) + Math.PI;
|
||||
|
||||
const arrowShapes: { shape: THREE.Shape; position: THREE.Vector3; rotationY: number }[] = [];
|
||||
const shape = new THREE.Shape();
|
||||
const w = width * 0.8;
|
||||
const h = arrowLength;
|
||||
|
||||
shape.moveTo(0, 0);
|
||||
shape.lineTo(w, h * 0.6);
|
||||
shape.lineTo(w * 0.4, h * 0.6);
|
||||
shape.lineTo(w * 0.4, h);
|
||||
shape.lineTo(-w * 0.4, h);
|
||||
shape.lineTo(-w * 0.4, h * 0.6);
|
||||
shape.lineTo(-w, h * 0.6);
|
||||
shape.lineTo(0, 0);
|
||||
|
||||
result.arrowGeometry = new THREE.ExtrudeGeometry(shape, {
|
||||
depth: 0.01,
|
||||
bevelEnabled: false,
|
||||
});
|
||||
|
||||
result.arrowGeometry.rotateX(Math.PI / 2);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const initialOffset = arrowLength;
|
||||
const center = new THREE.Vector3().copy(start).addScaledVector(direction, initialOffset + i * (arrowLength + spacing));
|
||||
const offset = arrowLength + i * (arrowLength + spacing);
|
||||
const center = new THREE.Vector3().copy(start).addScaledVector(direction, offset);
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
const w = width * 0.8;
|
||||
const h = arrowLength;
|
||||
|
||||
shape.moveTo(0, 0);
|
||||
shape.lineTo(w, h * 0.6);
|
||||
shape.lineTo(w * 0.4, h * 0.6);
|
||||
shape.lineTo(w * 0.4, h);
|
||||
shape.lineTo(-w * 0.4, h);
|
||||
shape.lineTo(-w * 0.4, h * 0.6);
|
||||
shape.lineTo(-w, h * 0.6);
|
||||
shape.lineTo(0, 0);
|
||||
|
||||
const angle = Math.atan2(direction.x, direction.z) + Math.PI;
|
||||
|
||||
arrowShapes.push({ shape, position: center, rotationY: angle });
|
||||
result.arrowInstances.push({
|
||||
position: [center.x, 0, center.z],
|
||||
rotation: [0, angle, 0],
|
||||
});
|
||||
}
|
||||
|
||||
return arrowShapes;
|
||||
return result;
|
||||
}, [aisle]);
|
||||
|
||||
const handleClick = () => {
|
||||
if (toolMode === 'move' && !hoveredPoint) {
|
||||
setSelectedAisle(aisleRef.current);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (arrows.length === 0) return null;
|
||||
if (!arrowGeometry || arrowInstances.length === 0) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
name='Arrows-Aisle'
|
||||
name="Arrows-Aisle"
|
||||
uuid={aisle.aisleUuid}
|
||||
ref={aisleRef}
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
userData={aisle}
|
||||
onDoubleClick={handleClick}
|
||||
onPointerMissed={() => {
|
||||
setSelectedAisle(null);
|
||||
}}
|
||||
>
|
||||
{arrows.map(({ shape, position, rotationY }, index) => (
|
||||
<group key={index} position={[position.x, position.z, 0]} rotation={[0, 0, -rotationY]}>
|
||||
<Extrude
|
||||
args={[shape, { depth: 0.01, bevelEnabled: false }]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
</group>
|
||||
))}
|
||||
<Instances geometry={arrowGeometry} frustumCulled={false}>
|
||||
<meshStandardMaterial color={aisle.type.aisleColor || '#ffffff'} side={THREE.DoubleSide} />
|
||||
{arrowInstances.map(({ position, rotation }, i) => (
|
||||
<Instance key={i} position={position} rotation={rotation} />
|
||||
))}
|
||||
</Instances>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as THREE from 'three';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { Extrude } from '@react-three/drei';
|
||||
import { Instances, Instance } from '@react-three/drei';
|
||||
import * as Constants from '../../../../../../types/world/worldConstants';
|
||||
import { useToolMode } from '../../../../../../store/builder/store';
|
||||
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
|
||||
@@ -10,82 +10,71 @@ function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const { toolMode } = useToolMode();
|
||||
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
|
||||
|
||||
const shapes = useMemo(() => {
|
||||
const dashInstances = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dashed-aisle') return [];
|
||||
|
||||
const start = new THREE.Vector3(...aisle.points[0].position);
|
||||
const end = new THREE.Vector3(...aisle.points[1].position);
|
||||
|
||||
const width = aisle.type.aisleWidth || 0.1;
|
||||
const dashLength = aisle.type.dashLength || 0.5;
|
||||
const gapLength = aisle.type.gapLength || 0.3;
|
||||
|
||||
const direction = new THREE.Vector3().subVectors(end, start).normalize();
|
||||
const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize();
|
||||
|
||||
const totalLength = new THREE.Vector3().subVectors(end, start).length();
|
||||
const totalVec = new THREE.Vector3().subVectors(end, start);
|
||||
const totalLength = totalVec.length();
|
||||
const direction = totalVec.clone().normalize();
|
||||
const segmentCount = Math.floor((totalLength + gapLength) / (dashLength + gapLength));
|
||||
|
||||
const shapes = [];
|
||||
const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize();
|
||||
const instances = [];
|
||||
|
||||
for (let i = 0; i < segmentCount; i++) {
|
||||
const segmentStart = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * (dashLength + gapLength));
|
||||
const segmentEnd = new THREE.Vector3().copy(segmentStart).addScaledVector(directionNormalized, dashLength);
|
||||
const center = start.clone().addScaledVector(direction, i * (dashLength + gapLength) + dashLength / 2);
|
||||
|
||||
const leftStart = new THREE.Vector3().copy(segmentStart).addScaledVector(perp, width / 2);
|
||||
const rightStart = new THREE.Vector3().copy(segmentStart).addScaledVector(perp, -width / 2);
|
||||
const leftEnd = new THREE.Vector3().copy(segmentEnd).addScaledVector(perp, width / 2);
|
||||
const rightEnd = new THREE.Vector3().copy(segmentEnd).addScaledVector(perp, -width / 2);
|
||||
const rotationY = Math.atan2(direction.x, direction.z);
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
shape.moveTo(leftStart.x, leftStart.z);
|
||||
shape.lineTo(leftEnd.x, leftEnd.z);
|
||||
shape.lineTo(rightEnd.x, rightEnd.z);
|
||||
shape.lineTo(rightStart.x, rightStart.z);
|
||||
shape.closePath();
|
||||
|
||||
shapes.push(shape);
|
||||
instances.push({
|
||||
position: [center.x, 0, center.z] as [number, number, number],
|
||||
scale: [width, 0.001, dashLength] as [number, number, number],
|
||||
rotation: [0, rotationY, 0] as [number, number, number],
|
||||
});
|
||||
}
|
||||
|
||||
return shapes;
|
||||
return instances;
|
||||
}, [aisle]);
|
||||
|
||||
const handleClick = () => {
|
||||
if (toolMode === 'move' && !hoveredPoint) {
|
||||
setSelectedAisle(aisleRef.current);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (shapes.length === 0) return null;
|
||||
if (dashInstances.length === 0) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
name='Dashed-Aisle'
|
||||
name="Dashed-Aisle"
|
||||
uuid={aisle.aisleUuid}
|
||||
ref={aisleRef}
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
userData={aisle}
|
||||
onDoubleClick={handleClick}
|
||||
onPointerMissed={() => {
|
||||
setSelectedAisle(null);
|
||||
}}
|
||||
>
|
||||
{shapes.map((shape, index) => (
|
||||
<Extrude
|
||||
key={index}
|
||||
args={[shape, { depth: 0.01, bevelEnabled: false }]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
<Instances frustumCulled={false}>
|
||||
<boxGeometry args={[1, 1, 1]} />
|
||||
<meshStandardMaterial color={aisle.type.aisleColor || '#ffffff'} />
|
||||
{dashInstances.map((inst, i) => (
|
||||
<Instance
|
||||
key={i}
|
||||
position={inst.position}
|
||||
scale={inst.scale}
|
||||
rotation={inst.rotation}
|
||||
/>
|
||||
</Extrude>
|
||||
))}
|
||||
))}
|
||||
</Instances>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashedAisle;
|
||||
export default DashedAisle;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as THREE from 'three';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { Extrude } from '@react-three/drei';
|
||||
import { Instance, Instances } from '@react-three/drei';
|
||||
import * as Constants from '../../../../../../types/world/worldConstants';
|
||||
import { useToolMode } from '../../../../../../store/builder/store';
|
||||
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
|
||||
@@ -10,31 +10,20 @@ function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const { toolMode } = useToolMode();
|
||||
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
|
||||
|
||||
const shapes = useMemo(() => {
|
||||
const dotPositions = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dotted-aisle') return [];
|
||||
|
||||
const start = new THREE.Vector3(...aisle.points[0].position);
|
||||
const end = new THREE.Vector3(...aisle.points[1].position);
|
||||
const width = aisle.type.dotRadius || 0.1;
|
||||
const dotSpacing = aisle.type.gapLength || 0.5;
|
||||
const dotRadius = width * 0.6;
|
||||
|
||||
const totalLength = new THREE.Vector3().subVectors(end, start).length();
|
||||
const dotCount = Math.floor((totalLength + (dotSpacing / 2)) / dotSpacing);
|
||||
|
||||
const shapes = [];
|
||||
const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize();
|
||||
|
||||
for (let i = 0; i < dotCount; i++) {
|
||||
const dotCenter = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * dotSpacing + dotSpacing / 2);
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
shape.absarc(dotCenter.x, dotCenter.z, dotRadius, 0, Math.PI * 2, false);
|
||||
|
||||
shapes.push(shape);
|
||||
}
|
||||
|
||||
return shapes;
|
||||
return Array.from({ length: dotCount }, (_, i) => {
|
||||
return new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * dotSpacing + dotSpacing / 2);
|
||||
});
|
||||
}, [aisle]);
|
||||
|
||||
const handleClick = () => {
|
||||
@@ -43,7 +32,14 @@ function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
}
|
||||
}
|
||||
|
||||
if (shapes.length === 0) return null;
|
||||
const { dotRadius, color } = useMemo(() => {
|
||||
return {
|
||||
dotRadius: aisle.type.aisleType === 'dotted-aisle' ? ((aisle.type as any).dotRadius || 0.1) * 0.6 : 0.06,
|
||||
color: aisle.type.aisleColor || '#ffffff'
|
||||
};
|
||||
}, [aisle]);
|
||||
|
||||
if (dotPositions.length === 0) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
@@ -51,26 +47,23 @@ function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
uuid={aisle.aisleUuid}
|
||||
ref={aisleRef}
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
userData={aisle}
|
||||
onDoubleClick={handleClick}
|
||||
onPointerMissed={() => {
|
||||
setSelectedAisle(null);
|
||||
}}
|
||||
>
|
||||
{shapes.map((shape, index) => (
|
||||
<Extrude
|
||||
key={index}
|
||||
args={[shape, { depth: 0.01, bevelEnabled: false }]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
<Instances frustumCulled={false}>
|
||||
<circleGeometry args={[dotRadius, 32]} />
|
||||
<meshStandardMaterial color={color} side={THREE.DoubleSide} />
|
||||
{dotPositions.map((position, index) => (
|
||||
<Instance
|
||||
key={index}
|
||||
position={[position.x, 0, position.z]}
|
||||
rotation={[-Math.PI / 2, 0, 0]}
|
||||
/>
|
||||
</Extrude>
|
||||
))}
|
||||
))}
|
||||
</Instances>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,8 +19,9 @@ function AisleCreator() {
|
||||
const { toolMode } = useToolMode();
|
||||
const { activeLayer } = useActiveLayer();
|
||||
const { socket } = useSocketStore();
|
||||
const { aisleStore } = useSceneContext();
|
||||
const { aisleStore, undoRedo2DStore } = useSceneContext();
|
||||
const { addAisle, getAislePointById } = aisleStore();
|
||||
const { push2D } = undoRedo2DStore();
|
||||
const drag = useRef(false);
|
||||
const isLeftMouseDown = useRef(false);
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
@@ -107,7 +108,23 @@ function AisleCreator() {
|
||||
aisleWidth: aisleWidth
|
||||
}
|
||||
};
|
||||
|
||||
addAisle(aisle);
|
||||
|
||||
push2D({
|
||||
type: 'Draw',
|
||||
actions: [
|
||||
{
|
||||
actionType: 'Line-Create',
|
||||
point: {
|
||||
type: 'Aisle',
|
||||
lineData: aisle,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
if (projectId) {
|
||||
|
||||
// API
|
||||
@@ -145,7 +162,23 @@ function AisleCreator() {
|
||||
gapLength: gapLength
|
||||
}
|
||||
};
|
||||
|
||||
addAisle(aisle);
|
||||
|
||||
push2D({
|
||||
type: 'Draw',
|
||||
actions: [
|
||||
{
|
||||
actionType: 'Line-Create',
|
||||
point: {
|
||||
type: 'Aisle',
|
||||
lineData: aisle,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
if (projectId) {
|
||||
|
||||
// API
|
||||
@@ -182,7 +215,23 @@ function AisleCreator() {
|
||||
gapLength: gapLength
|
||||
}
|
||||
};
|
||||
|
||||
addAisle(aisle);
|
||||
|
||||
push2D({
|
||||
type: 'Draw',
|
||||
actions: [
|
||||
{
|
||||
actionType: 'Line-Create',
|
||||
point: {
|
||||
type: 'Aisle',
|
||||
lineData: aisle,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
if (projectId) {
|
||||
|
||||
// API
|
||||
@@ -218,7 +267,23 @@ function AisleCreator() {
|
||||
aisleWidth: aisleWidth
|
||||
}
|
||||
};
|
||||
|
||||
addAisle(aisle);
|
||||
|
||||
push2D({
|
||||
type: 'Draw',
|
||||
actions: [
|
||||
{
|
||||
actionType: 'Line-Create',
|
||||
point: {
|
||||
type: 'Aisle',
|
||||
lineData: aisle,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
if (projectId) {
|
||||
|
||||
// API
|
||||
@@ -256,7 +321,23 @@ function AisleCreator() {
|
||||
gapLength: gapLength
|
||||
}
|
||||
};
|
||||
|
||||
addAisle(aisle);
|
||||
|
||||
push2D({
|
||||
type: 'Draw',
|
||||
actions: [
|
||||
{
|
||||
actionType: 'Line-Create',
|
||||
point: {
|
||||
type: 'Aisle',
|
||||
lineData: aisle,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
if (projectId) {
|
||||
|
||||
// API
|
||||
@@ -293,7 +374,23 @@ function AisleCreator() {
|
||||
isFlipped: isFlipped
|
||||
}
|
||||
};
|
||||
|
||||
addAisle(aisle);
|
||||
|
||||
push2D({
|
||||
type: 'Draw',
|
||||
actions: [
|
||||
{
|
||||
actionType: 'Line-Create',
|
||||
point: {
|
||||
type: 'Aisle',
|
||||
lineData: aisle,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
if (projectId) {
|
||||
|
||||
// API
|
||||
@@ -329,7 +426,23 @@ function AisleCreator() {
|
||||
aisleWidth: aisleWidth
|
||||
}
|
||||
};
|
||||
|
||||
addAisle(aisle);
|
||||
|
||||
push2D({
|
||||
type: 'Draw',
|
||||
actions: [
|
||||
{
|
||||
actionType: 'Line-Create',
|
||||
point: {
|
||||
type: 'Aisle',
|
||||
lineData: aisle,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
if (projectId) {
|
||||
|
||||
// API
|
||||
@@ -366,7 +479,23 @@ function AisleCreator() {
|
||||
isFlipped: isFlipped
|
||||
}
|
||||
};
|
||||
|
||||
addAisle(aisle);
|
||||
|
||||
push2D({
|
||||
type: 'Draw',
|
||||
actions: [
|
||||
{
|
||||
actionType: 'Line-Create',
|
||||
point: {
|
||||
type: 'Aisle',
|
||||
lineData: aisle,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
if (projectId) {
|
||||
|
||||
// API
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as THREE from 'three';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import { useActiveLayer, useToolMode, useToggleView } from '../../../../store/builder/store';
|
||||
import * as Constants from '../../../../types/world/worldConstants';
|
||||
import { Extrude, Html } from '@react-three/drei';
|
||||
import { Extrude, Html, Instance, Instances } from '@react-three/drei';
|
||||
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
|
||||
import { useDirectionalSnapping } from '../../point/helpers/useDirectionalSnapping';
|
||||
import { usePointSnapping } from '../../point/helpers/usePointSnapping';
|
||||
@@ -289,125 +289,119 @@ function SolidAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
}
|
||||
|
||||
function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const shapes = useMemo(() => {
|
||||
|
||||
const dashInstances = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dashed-aisle') return [];
|
||||
|
||||
const start = new THREE.Vector3(...aisle.points[0].position);
|
||||
const end = new THREE.Vector3(...aisle.points[1].position);
|
||||
|
||||
const width = aisle.type.aisleWidth || 0.1;
|
||||
const dashLength = aisle.type.dashLength || 0.5;
|
||||
const gapLength = aisle.type.gapLength || 0.3;
|
||||
|
||||
const direction = new THREE.Vector3().subVectors(end, start).normalize();
|
||||
const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize();
|
||||
|
||||
const totalLength = new THREE.Vector3().subVectors(end, start).length();
|
||||
const totalVec = new THREE.Vector3().subVectors(end, start);
|
||||
const totalLength = totalVec.length();
|
||||
const direction = totalVec.clone().normalize();
|
||||
const segmentCount = Math.floor((totalLength + gapLength) / (dashLength + gapLength));
|
||||
|
||||
const shapes = [];
|
||||
const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize();
|
||||
const instances = [];
|
||||
|
||||
for (let i = 0; i < segmentCount; i++) {
|
||||
const segmentStart = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * (dashLength + gapLength));
|
||||
const segmentEnd = new THREE.Vector3().copy(segmentStart).addScaledVector(directionNormalized, dashLength);
|
||||
const center = start.clone().addScaledVector(direction, i * (dashLength + gapLength) + dashLength / 2);
|
||||
|
||||
const leftStart = new THREE.Vector3().copy(segmentStart).addScaledVector(perp, width / 2);
|
||||
const rightStart = new THREE.Vector3().copy(segmentStart).addScaledVector(perp, -width / 2);
|
||||
const leftEnd = new THREE.Vector3().copy(segmentEnd).addScaledVector(perp, width / 2);
|
||||
const rightEnd = new THREE.Vector3().copy(segmentEnd).addScaledVector(perp, -width / 2);
|
||||
const rotationY = Math.atan2(direction.x, direction.z);
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
shape.moveTo(leftStart.x, leftStart.z);
|
||||
shape.lineTo(leftEnd.x, leftEnd.z);
|
||||
shape.lineTo(rightEnd.x, rightEnd.z);
|
||||
shape.lineTo(rightStart.x, rightStart.z);
|
||||
shape.closePath();
|
||||
|
||||
shapes.push(shape);
|
||||
instances.push({
|
||||
position: [center.x, 0, center.z] as [number, number, number],
|
||||
scale: [width, 0.001, dashLength] as [number, number, number],
|
||||
rotation: [0, rotationY, 0] as [number, number, number],
|
||||
});
|
||||
}
|
||||
|
||||
return shapes;
|
||||
return instances;
|
||||
}, [aisle]);
|
||||
|
||||
if (shapes.length === 0) return null;
|
||||
if (dashInstances.length === 0) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
>
|
||||
{shapes.map((shape, index) => (
|
||||
<Extrude
|
||||
key={index}
|
||||
args={[shape, { depth: 0.01, bevelEnabled: false }]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
<Instances frustumCulled={false}>
|
||||
<boxGeometry args={[1, 1, 1]} />
|
||||
<meshStandardMaterial color={aisle.type.aisleColor || '#ffffff'} />
|
||||
{dashInstances.map((inst, i) => (
|
||||
<Instance
|
||||
key={i}
|
||||
position={inst.position}
|
||||
scale={inst.scale}
|
||||
rotation={inst.rotation}
|
||||
/>
|
||||
</Extrude>
|
||||
))}
|
||||
))}
|
||||
</Instances>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const shapes = useMemo(() => {
|
||||
|
||||
const dotPositions = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dotted-aisle') return [];
|
||||
|
||||
const start = new THREE.Vector3(...aisle.points[0].position);
|
||||
const end = new THREE.Vector3(...aisle.points[1].position);
|
||||
const width = aisle.type.dotRadius || 0.1;
|
||||
const dotSpacing = aisle.type.gapLength || 0.5;
|
||||
const dotRadius = width * 0.6;
|
||||
|
||||
const totalLength = new THREE.Vector3().subVectors(end, start).length();
|
||||
const dotCount = Math.floor((totalLength + (dotSpacing / 2)) / dotSpacing);
|
||||
|
||||
const shapes = [];
|
||||
const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize();
|
||||
|
||||
for (let i = 0; i < dotCount; i++) {
|
||||
const dotCenter = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * dotSpacing + dotSpacing / 2);
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
shape.absarc(dotCenter.x, dotCenter.z, dotRadius, 0, Math.PI * 2, false);
|
||||
|
||||
shapes.push(shape);
|
||||
}
|
||||
|
||||
return shapes;
|
||||
return Array.from({ length: dotCount }, (_, i) => {
|
||||
return new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * dotSpacing + dotSpacing / 2);
|
||||
});
|
||||
}, [aisle]);
|
||||
|
||||
if (shapes.length === 0) return null;
|
||||
const { dotRadius, color } = useMemo(() => {
|
||||
return {
|
||||
dotRadius: aisle.type.aisleType === 'dotted-aisle' ? ((aisle.type as any).dotRadius || 0.1) * 0.6 : 0.06,
|
||||
color: aisle.type.aisleColor || '#ffffff'
|
||||
};
|
||||
}, [aisle]);
|
||||
|
||||
if (dotPositions.length === 0) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
>
|
||||
{shapes.map((shape, index) => (
|
||||
<Extrude
|
||||
key={index}
|
||||
args={[shape, { depth: 0.01, bevelEnabled: false }]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
<Instances frustumCulled={false}>
|
||||
<circleGeometry args={[dotRadius, 32]} />
|
||||
<meshStandardMaterial color={color} side={THREE.DoubleSide} />
|
||||
{dotPositions.map((position, index) => (
|
||||
<Instance
|
||||
key={index}
|
||||
position={[position.x, 0, position.z]}
|
||||
rotation={[-Math.PI / 2, 0, 0]}
|
||||
/>
|
||||
</Extrude>
|
||||
))}
|
||||
))}
|
||||
</Instances>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const arrows = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrows-aisle') return [];
|
||||
|
||||
const { arrowGeometry, arrowInstances } = useMemo(() => {
|
||||
const result = {
|
||||
arrowGeometry: null as THREE.ExtrudeGeometry | null,
|
||||
arrowInstances: [] as {
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
}[],
|
||||
};
|
||||
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrows-aisle') return result;
|
||||
|
||||
const start = new THREE.Vector3(...aisle.points[0].position);
|
||||
const end = new THREE.Vector3(...aisle.points[1].position);
|
||||
@@ -420,55 +414,53 @@ function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
direction.normalize();
|
||||
|
||||
const count = Math.floor((length + spacing) / (arrowLength + spacing));
|
||||
const angle = Math.atan2(direction.x, direction.z) + Math.PI;
|
||||
|
||||
const arrowShapes: { shape: THREE.Shape; position: THREE.Vector3; rotationY: number }[] = [];
|
||||
const shape = new THREE.Shape();
|
||||
const w = width * 0.8;
|
||||
const h = arrowLength;
|
||||
|
||||
shape.moveTo(0, 0);
|
||||
shape.lineTo(w, h * 0.6);
|
||||
shape.lineTo(w * 0.4, h * 0.6);
|
||||
shape.lineTo(w * 0.4, h);
|
||||
shape.lineTo(-w * 0.4, h);
|
||||
shape.lineTo(-w * 0.4, h * 0.6);
|
||||
shape.lineTo(-w, h * 0.6);
|
||||
shape.lineTo(0, 0);
|
||||
|
||||
result.arrowGeometry = new THREE.ExtrudeGeometry(shape, {
|
||||
depth: 0.01,
|
||||
bevelEnabled: false,
|
||||
});
|
||||
|
||||
result.arrowGeometry.rotateX(Math.PI / 2);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const initialOffset = arrowLength;
|
||||
const center = new THREE.Vector3().copy(start).addScaledVector(direction, initialOffset + i * (arrowLength + spacing));
|
||||
const offset = arrowLength + i * (arrowLength + spacing);
|
||||
const center = new THREE.Vector3().copy(start).addScaledVector(direction, offset);
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
const w = width * 0.8;
|
||||
const h = arrowLength;
|
||||
|
||||
shape.moveTo(0, 0);
|
||||
shape.lineTo(w, h * 0.6);
|
||||
shape.lineTo(w * 0.4, h * 0.6);
|
||||
shape.lineTo(w * 0.4, h);
|
||||
shape.lineTo(-w * 0.4, h);
|
||||
shape.lineTo(-w * 0.4, h * 0.6);
|
||||
shape.lineTo(-w, h * 0.6);
|
||||
shape.lineTo(0, 0);
|
||||
|
||||
const angle = Math.atan2(direction.x, direction.z) + Math.PI;
|
||||
|
||||
arrowShapes.push({ shape, position: center, rotationY: angle });
|
||||
result.arrowInstances.push({
|
||||
position: [center.x, 0, center.z],
|
||||
rotation: [0, angle, 0],
|
||||
});
|
||||
}
|
||||
|
||||
return arrowShapes;
|
||||
return result;
|
||||
}, [aisle]);
|
||||
|
||||
if (arrows.length === 0) return null;
|
||||
if (!arrowGeometry || arrowInstances.length === 0) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
>
|
||||
{arrows.map(({ shape, position, rotationY }, index) => (
|
||||
<group key={index} position={[position.x, position.z, 0]} rotation={[0, 0, -rotationY]}>
|
||||
<Extrude
|
||||
args={[shape, { depth: 0.01, bevelEnabled: false }]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
</group>
|
||||
))}
|
||||
<Instances geometry={arrowGeometry} frustumCulled={false}>
|
||||
<meshStandardMaterial color={aisle.type.aisleColor || '#ffffff'} side={THREE.DoubleSide} />
|
||||
{arrowInstances.map(({ position, rotation }, i) => (
|
||||
<Instance key={i} position={position} rotation={rotation} />
|
||||
))}
|
||||
</Instances>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectId || !selectedVersion) return;
|
||||
|
||||
clearEvents();
|
||||
|
||||
let totalAssets = 0;
|
||||
@@ -304,7 +305,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
|
||||
pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
|
||||
pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
||||
|
||||
addAssetModel(scene, raycaster, camera, pointer, socket, selectedItem, setSelectedItem, addEvent, addAsset, plane, selectedVersion, projectId, userId);
|
||||
addAssetModel(scene, raycaster, camera, pointer, socket, selectedItem, setSelectedItem, addEvent, addAsset, plane, loader, selectedVersion, projectId, userId);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -345,10 +346,10 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
|
||||
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||
};
|
||||
}, [selectedItem, camera, pointer, activeModule, controls, isRenameMode]);
|
||||
}, [selectedItem, camera, activeModule, controls, isRenameMode]);
|
||||
|
||||
return (
|
||||
<Models />
|
||||
<Models loader={loader} />
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ async function addAssetModel(
|
||||
addEvent: (event: EventsSchema) => void,
|
||||
addAsset: (asset: Asset) => void,
|
||||
plane: Types.RefMesh,
|
||||
loader: GLTFLoader,
|
||||
selectedVersion?: Version | null,
|
||||
projectId?: string,
|
||||
userId?: string
|
||||
@@ -29,12 +30,6 @@ async function addAssetModel(
|
||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
|
||||
try {
|
||||
const loader = new GLTFLoader();
|
||||
const dracoLoader = new DRACOLoader();
|
||||
|
||||
dracoLoader.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/");
|
||||
loader.setDRACOLoader(dracoLoader);
|
||||
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const wallFloorsGroup = scene.getObjectByName("Walls-Floors-Group") as Types.Group | null;
|
||||
const floorsGroup = scene.getObjectByName("Floors-Group") as Types.Group | null;
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
|
||||
import { RapierRigidBody } from '@react-three/rapier';
|
||||
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||
import { ThreeEvent, useThree } from '@react-three/fiber';
|
||||
import { useActiveTool, useDeletableFloorItem, useSelectedAssets, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store';
|
||||
import { AssetBoundingBox } from '../../functions/assetBoundingBox';
|
||||
@@ -25,7 +24,7 @@ import { ModelAnimator } from './animator/modelAnimator';
|
||||
import ConveyorCollider from '../../../../scene/physics/conveyor/conveyorCollider';
|
||||
import RibbonCollider from '../../../../scene/physics/conveyor/ribbonCollider';
|
||||
|
||||
function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boolean }) {
|
||||
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 { controls, gl } = useThree();
|
||||
const savedTheme: string = localStorage.getItem("theme") || "light";
|
||||
@@ -139,11 +138,6 @@ function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boole
|
||||
}, [isRendered, selectedFloorItem])
|
||||
|
||||
useEffect(() => {
|
||||
const loader = new GLTFLoader();
|
||||
const dracoLoader = new DRACOLoader();
|
||||
|
||||
dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
|
||||
loader.setDRACOLoader(dracoLoader);
|
||||
const loadModel = async () => {
|
||||
try {
|
||||
|
||||
@@ -192,8 +186,7 @@ function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boole
|
||||
console.error(`[Backend] Error storing/loading ${asset.modelName}:`, error);
|
||||
}
|
||||
};
|
||||
loader.load(
|
||||
modelUrl,
|
||||
loader.load(modelUrl,
|
||||
handleBackendLoad,
|
||||
undefined,
|
||||
(error) => {
|
||||
@@ -448,26 +441,26 @@ function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boole
|
||||
castShadow
|
||||
receiveShadow
|
||||
onDoubleClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!toggleView) {
|
||||
e.stopPropagation();
|
||||
handleDblClick(asset);
|
||||
}
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!toggleView) {
|
||||
e.stopPropagation();
|
||||
handleClick(e, asset);
|
||||
}
|
||||
}}
|
||||
onPointerOver={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!toggleView) {
|
||||
e.stopPropagation();
|
||||
handlePointerOver(asset);
|
||||
}
|
||||
}}
|
||||
onPointerLeave={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!toggleView) {
|
||||
e.stopPropagation();
|
||||
handlePointerOut(e, asset);
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -7,10 +7,11 @@ import { useSelectedAsset } from '../../../../store/simulation/useSimulationStor
|
||||
import { useSceneContext } from '../../../scene/sceneContext';
|
||||
|
||||
import Model from './model/model';
|
||||
import { GLTFLoader } from "three/examples/jsm/Addons";
|
||||
|
||||
const distanceWorker = new Worker(new URL("../../../../services/factoryBuilder/webWorkers/distanceWorker.js", import.meta.url));
|
||||
|
||||
function Models() {
|
||||
function Models({ loader }: { loader: GLTFLoader }) {
|
||||
const { controls, camera } = useThree();
|
||||
const { assetStore } = useSceneContext();
|
||||
const { assets } = assetStore();
|
||||
@@ -57,7 +58,7 @@ function Models() {
|
||||
}}
|
||||
>
|
||||
{assets.map((asset) => (
|
||||
<Model key={asset.modelUuid} asset={asset} isRendered={renderMap[asset.modelUuid] ?? false} />
|
||||
<Model key={asset.modelUuid} asset={asset} isRendered={renderMap[asset.modelUuid] ?? false} loader={loader} />
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
|
||||
@@ -95,7 +95,7 @@ export default function Builder() {
|
||||
|
||||
<AssetsGroup plane={plane} />
|
||||
|
||||
<mesh name='Walls-And-WallAssets-Group'>
|
||||
{/* <mesh name='Walls-And-WallAssets-Group'>
|
||||
<Geometry ref={csgRef} useGroups>
|
||||
|
||||
<WallGroup />
|
||||
@@ -103,7 +103,7 @@ export default function Builder() {
|
||||
<WallAssetGroup />
|
||||
|
||||
</Geometry>
|
||||
</mesh>
|
||||
</mesh> */}
|
||||
|
||||
<AislesGroup />
|
||||
|
||||
@@ -113,9 +113,9 @@ export default function Builder() {
|
||||
|
||||
<MeasurementTool />
|
||||
|
||||
<CalculateAreaGroup />
|
||||
{/* <CalculateAreaGroup /> */}
|
||||
|
||||
<NavMesh />
|
||||
{/* <NavMesh /> */}
|
||||
|
||||
<DxfFile />
|
||||
|
||||
|
||||
@@ -1,108 +1,247 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Shape, Vector2, DoubleSide, TextureLoader, RepeatWrapping, SRGBColorSpace } from 'three';
|
||||
import { useLoader } from '@react-three/fiber';
|
||||
import { Extrude } from '@react-three/drei';
|
||||
import useModuleStore from '../../../../../store/useModuleStore';
|
||||
import { useBuilderStore } from '../../../../../store/builder/useBuilderStore';
|
||||
import { useToggleView } from '../../../../../store/builder/store';
|
||||
import * as Constants from '../../../../../types/world/worldConstants';
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
Shape,
|
||||
Vector2,
|
||||
DoubleSide,
|
||||
TextureLoader,
|
||||
RepeatWrapping,
|
||||
SRGBColorSpace,
|
||||
NoColorSpace,
|
||||
} from "three";
|
||||
import { useLoader } from "@react-three/fiber";
|
||||
import { Extrude } from "@react-three/drei";
|
||||
import useModuleStore from "../../../../../store/useModuleStore";
|
||||
import { useBuilderStore } from "../../../../../store/builder/useBuilderStore";
|
||||
import { useToggleView } from "../../../../../store/builder/store";
|
||||
import * as Constants from "../../../../../types/world/worldConstants";
|
||||
|
||||
import texturePath from "../../../../../assets/textures/floor/white.png";
|
||||
import texturePathDark from "../../../../../assets/textures/floor/black.png";
|
||||
import material1 from '../../../../../assets/textures/floor/factory wall texture.jpg';
|
||||
import material1 from "../../../../../assets/textures/floor/factory wall texture.jpg";
|
||||
|
||||
// floor-mat1
|
||||
import material2Map from "../../../../../assets/textures/floor/tex1/MI_FactoryConcreteFloor01_BaseColor.001.jpg";
|
||||
import material2NormalMap from "../../../../../assets/textures/floor/tex1/MI_FactoryConcreteFloor01_Normal.001.jpg";
|
||||
import material2MetalicRoughnessMap from "../../../../../assets/textures/floor/tex1/MI_FactoryConcreteFloor01_MetallicRoughness.001.jpg";
|
||||
|
||||
// floor-mat2
|
||||
import material3Map from "../../../../../assets/textures/floor/tex2/MI_FloorMats01_baseColor.png";
|
||||
import material3NormalMap from "../../../../../assets/textures/floor/tex2/MI_FloorMats01_Normal.png";
|
||||
import material3MetalicRoughnessMap from "../../../../../assets/textures/floor/tex2/MI_FloorMats01_occlusionRoughnessMetallic.png";
|
||||
|
||||
// floor-mat3
|
||||
import material4Map from "../../../../../assets/textures/floor/tex3/metal_plate_diff_1k.jpg";
|
||||
import material4RoughnessMap from "../../../../../assets/textures/floor/tex3/metal_plate_rough_1k.png";
|
||||
import material4MetalicMap from "../../../../../assets/textures/floor/tex3/metal_plate_metal_1k.png";
|
||||
import material4NormalMap from "../../../../../assets/textures/floor/tex3/metal_plate_nor_gl_1k.png";
|
||||
|
||||
function FloorInstance({ floor }: { floor: Floor }) {
|
||||
const { togglView } = useToggleView();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { selectedFloor, setSelectedFloor, setSelectedDecal } = useBuilderStore();
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const { togglView } = useToggleView();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { selectedFloor, setSelectedFloor, setSelectedDecal } =
|
||||
useBuilderStore();
|
||||
const savedTheme = localStorage.getItem("theme");
|
||||
|
||||
const materials: Record<string, string> = {
|
||||
"Default Material": savedTheme === "dark" ? texturePathDark : texturePath,
|
||||
"Material 1": savedTheme === "dark" ? material1 : material1,
|
||||
};
|
||||
const materials: Record<
|
||||
string,
|
||||
{
|
||||
map: string;
|
||||
roughnessMap?: string;
|
||||
metalnessMap?: string;
|
||||
normalMap?: string;
|
||||
textureTileScale?: [number, number];
|
||||
}
|
||||
> = {
|
||||
"Default Material": {
|
||||
map: savedTheme === "dark" ? texturePathDark : texturePath,
|
||||
},
|
||||
"Material 1": {
|
||||
map: material1,
|
||||
},
|
||||
"Material 2": {
|
||||
map: material2Map,
|
||||
roughnessMap: material2MetalicRoughnessMap,
|
||||
metalnessMap: material2MetalicRoughnessMap,
|
||||
normalMap: material2NormalMap,
|
||||
textureTileScale: [0.1, 0.1],
|
||||
},
|
||||
"Material 3": {
|
||||
map: material3Map,
|
||||
roughnessMap: material3MetalicRoughnessMap,
|
||||
metalnessMap: material3MetalicRoughnessMap,
|
||||
normalMap: material3NormalMap,
|
||||
textureTileScale: [0.35, 0.5],
|
||||
},
|
||||
"Material 4": {
|
||||
map: material4Map,
|
||||
roughnessMap: material4RoughnessMap,
|
||||
metalnessMap: material4MetalicMap,
|
||||
normalMap: material4NormalMap,
|
||||
},
|
||||
};
|
||||
|
||||
const shape = useMemo(() => {
|
||||
const shape = new Shape();
|
||||
const points = floor.points.map(p => new Vector2(p.position[0], p.position[2]));
|
||||
if (points.length < 3) return null;
|
||||
shape.moveTo(points[0].x, points[0].y);
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
shape.lineTo(points[i].x, points[i].y);
|
||||
}
|
||||
return shape;
|
||||
}, [floor]);
|
||||
|
||||
const textureScale = Constants.floorConfig.textureScale;
|
||||
|
||||
const [topTexture, sideTexture] = useLoader(
|
||||
TextureLoader,
|
||||
[
|
||||
materials[floor.topMaterial] || materials['Default Material'],
|
||||
materials[floor.sideMaterial] || materials['Default Material']
|
||||
]
|
||||
const shape = useMemo(() => {
|
||||
const shape = new Shape();
|
||||
const points = floor.points.map(
|
||||
(p) => new Vector2(p.position[0], p.position[2])
|
||||
);
|
||||
if (points.length < 3) return null;
|
||||
shape.moveTo(points[0].x, points[0].y);
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
shape.lineTo(points[i].x, points[i].y);
|
||||
}
|
||||
return shape;
|
||||
}, [floor]);
|
||||
|
||||
if (!materials[floor.topMaterial] || !materials[floor.sideMaterial]) return null;
|
||||
const textureScale = Constants.floorConfig.textureScale;
|
||||
|
||||
[topTexture, sideTexture].forEach(tex => {
|
||||
tex.wrapS = tex.wrapT = RepeatWrapping;
|
||||
tex.repeat.set(textureScale, textureScale);
|
||||
tex.colorSpace = SRGBColorSpace;
|
||||
// Helper function to handle texture maps and filter out null values
|
||||
function getMaterialMaps(material: any, defaultMap: any) {
|
||||
const materialMap = material.map || defaultMap;
|
||||
const normalMap = material.normalMap || null;
|
||||
const roughnessMap = material.roughnessMap || null;
|
||||
const metalnessMap = material.metalnessMap || null;
|
||||
|
||||
return [materialMap, normalMap, roughnessMap, metalnessMap].filter(
|
||||
(texture): texture is string => texture !== null
|
||||
);
|
||||
}
|
||||
|
||||
// Default material map
|
||||
const defaultMaterialMap = materials["Default Material"].map;
|
||||
|
||||
// Get top and side material maps
|
||||
const topMaterial = materials[floor.topMaterial];
|
||||
const sideMaterial = materials[floor.sideMaterial];
|
||||
|
||||
// Get the filtered lists for top and side textures
|
||||
const topTexturesList = getMaterialMaps(topMaterial, defaultMaterialMap);
|
||||
const sideTexturesList = getMaterialMaps(sideMaterial, defaultMaterialMap);
|
||||
|
||||
// Use loader to load top and side textures
|
||||
const [topTexture, topNormalTexture, topRoughnessTexture, topMetalicTexture] =
|
||||
useLoader(TextureLoader, topTexturesList);
|
||||
|
||||
const [
|
||||
sideTexture,
|
||||
sideNormalTexture,
|
||||
sideRoughnessTexture,
|
||||
sideMetalicTexture,
|
||||
] = useLoader(TextureLoader, sideTexturesList);
|
||||
|
||||
// Early exit if materials are missing
|
||||
if (!materials[floor.topMaterial] || !materials[floor.sideMaterial])
|
||||
return null;
|
||||
|
||||
// Combine and pair textures with their corresponding material
|
||||
const textureMaterialMap = [
|
||||
{
|
||||
textures: [
|
||||
topTexture,
|
||||
topNormalTexture,
|
||||
topRoughnessTexture,
|
||||
topMetalicTexture,
|
||||
],
|
||||
materialKey: floor.topMaterial,
|
||||
},
|
||||
{
|
||||
textures: [
|
||||
sideTexture,
|
||||
sideNormalTexture,
|
||||
sideRoughnessTexture,
|
||||
sideMetalicTexture,
|
||||
],
|
||||
materialKey: floor.sideMaterial,
|
||||
},
|
||||
];
|
||||
|
||||
// Apply texture settings
|
||||
textureMaterialMap.forEach(({ textures, materialKey }) => {
|
||||
const tileScale = materials[materialKey]?.textureTileScale ?? [
|
||||
textureScale,
|
||||
textureScale,
|
||||
];
|
||||
|
||||
textures.forEach((tex, idx) => {
|
||||
if (!tex) return;
|
||||
tex.wrapS = tex.wrapT = RepeatWrapping;
|
||||
tex.repeat.set(tileScale[0], tileScale[1]);
|
||||
tex.anisotropy = 16;
|
||||
// First texture is always the color map (use SRGB), others should be linear
|
||||
tex.colorSpace = idx < 1 ? SRGBColorSpace : NoColorSpace;
|
||||
});
|
||||
});
|
||||
|
||||
if (!shape) return null;
|
||||
if (!shape) return null;
|
||||
|
||||
return (
|
||||
<mesh
|
||||
castShadow
|
||||
receiveShadow
|
||||
name={`Floor-${floor.floorUuid}`}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
position={[0, !floor.isBeveled ? (floor.floorDepth - 0.1) : (floor.floorDepth - 0.2), 0]}
|
||||
userData={floor}
|
||||
onDoubleClick={(e) => {
|
||||
if (!togglView && activeModule === 'builder') {
|
||||
if (e.object.userData.floorUuid) {
|
||||
e.stopPropagation();
|
||||
setSelectedFloor(e.object);
|
||||
setSelectedDecal(null);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
if (selectedFloor && selectedFloor.userData.floorUuid === floor.floorUuid) {
|
||||
setSelectedFloor(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Extrude
|
||||
name={`Floor-${floor.floorUuid}`}
|
||||
args={[shape, {
|
||||
depth: !floor.isBeveled ? floor.floorDepth : (floor.floorDepth - 0.1),
|
||||
bevelEnabled: floor.isBeveled,
|
||||
bevelSegments: floor.bevelStrength,
|
||||
bevelOffset: -0.1,
|
||||
bevelSize: 0.1,
|
||||
bevelThickness: 0.1,
|
||||
}]}
|
||||
userData={floor}
|
||||
>
|
||||
<meshStandardMaterial
|
||||
attach="material-0"
|
||||
color={Constants.floorConfig.defaultColor}
|
||||
map={topTexture}
|
||||
side={DoubleSide}
|
||||
/>
|
||||
<meshStandardMaterial
|
||||
attach="material-1"
|
||||
color={Constants.floorConfig.defaultColor}
|
||||
map={sideTexture}
|
||||
side={DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
</mesh>
|
||||
);
|
||||
return (
|
||||
<mesh
|
||||
castShadow
|
||||
receiveShadow
|
||||
name={`Floor-${floor.floorUuid}`}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
position={[
|
||||
0,
|
||||
!floor.isBeveled ? floor.floorDepth - 0.1 : floor.floorDepth - 0.2,
|
||||
0,
|
||||
]}
|
||||
userData={floor}
|
||||
onDoubleClick={(e) => {
|
||||
if (!togglView && activeModule === "builder") {
|
||||
if (e.object.userData.floorUuid) {
|
||||
e.stopPropagation();
|
||||
setSelectedFloor(e.object);
|
||||
setSelectedDecal(null);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onPointerMissed={() => {
|
||||
if (
|
||||
selectedFloor &&
|
||||
selectedFloor.userData.floorUuid === floor.floorUuid
|
||||
) {
|
||||
setSelectedFloor(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Extrude
|
||||
name={`Floor-${floor.floorUuid}`}
|
||||
args={[
|
||||
shape,
|
||||
{
|
||||
depth: !floor.isBeveled ? floor.floorDepth : floor.floorDepth - 0.1,
|
||||
bevelEnabled: floor.isBeveled,
|
||||
bevelSegments: floor.bevelStrength,
|
||||
bevelOffset: -0.1,
|
||||
bevelSize: 0.1,
|
||||
bevelThickness: 0.1,
|
||||
},
|
||||
]}
|
||||
userData={floor}
|
||||
>
|
||||
<meshPhysicalMaterial
|
||||
attach="material-0"
|
||||
color={Constants.floorConfig.defaultColor}
|
||||
map={topTexture}
|
||||
roughnessMap={topRoughnessTexture}
|
||||
metalnessMap={topMetalicTexture}
|
||||
normalMap={topNormalTexture}
|
||||
roughness={1.5}
|
||||
metalness={1.0}
|
||||
side={DoubleSide}
|
||||
/>
|
||||
<meshStandardMaterial
|
||||
attach="material-1"
|
||||
color={Constants.floorConfig.defaultColor}
|
||||
map={sideTexture?.clone()}
|
||||
roughnessMap={sideRoughnessTexture?.clone()}
|
||||
metalnessMap={sideMetalicTexture?.clone()}
|
||||
normalMap={sideNormalTexture?.clone()}
|
||||
side={DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
</mesh>
|
||||
);
|
||||
}
|
||||
|
||||
export default FloorInstance;
|
||||
export default FloorInstance;
|
||||
|
||||
@@ -43,6 +43,13 @@ function Line({ points }: Readonly<LineProps>) {
|
||||
const { hoveredLine, setHoveredLine, hoveredPoint } = useBuilderStore();
|
||||
const { selectedPoints } = useSelectedPoints();
|
||||
|
||||
const [initialPositions, setInitialPositions] = useState<{
|
||||
aisles?: Aisle[],
|
||||
walls?: Wall[],
|
||||
floors?: Floor[],
|
||||
zones?: Zone[]
|
||||
}>({});
|
||||
|
||||
const path = useMemo(() => {
|
||||
const [start, end] = points.map(p => new THREE.Vector3(...p.position));
|
||||
return new THREE.LineCurve3(start, end);
|
||||
@@ -353,6 +360,17 @@ function Line({ points }: Readonly<LineProps>) {
|
||||
|
||||
const offset = new THREE.Vector3().subVectors(midPoint, hit);
|
||||
setDragOffset(offset);
|
||||
|
||||
if (points[0].pointType === 'Wall') {
|
||||
const walls = getWallsByPointId(points[0].pointUuid);
|
||||
setInitialPositions({ walls });
|
||||
} else if (points[0].pointType === 'Floor') {
|
||||
const floors = getFloorsByPointId(points[0].pointUuid);
|
||||
setInitialPositions({ floors });
|
||||
} else if (points[0].pointType === 'Zone') {
|
||||
const zones = getZonesByPointId(points[0].pointUuid);
|
||||
setInitialPositions({ zones });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -370,9 +388,7 @@ function Line({ points }: Readonly<LineProps>) {
|
||||
|
||||
// API
|
||||
|
||||
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall).catch((error) => {
|
||||
// console.error('Error updating wall:', error);
|
||||
// });
|
||||
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
|
||||
|
||||
// SOCKET
|
||||
|
||||
@@ -386,6 +402,23 @@ function Line({ points }: Readonly<LineProps>) {
|
||||
|
||||
socket.emit('v1:model-Wall:add', data);
|
||||
})
|
||||
|
||||
if (initialPositions.walls && initialPositions.walls.length > 0) {
|
||||
const updatedPoints = initialPositions.walls.map((wall) => ({
|
||||
type: "Wall" as const,
|
||||
lineData: wall,
|
||||
newData: updatedWalls.find(w => w.wallUuid === wall.wallUuid),
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
push2D({
|
||||
type: 'Draw',
|
||||
actions: [{
|
||||
actionType: 'Lines-Update',
|
||||
points: updatedPoints,
|
||||
}]
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (points[0].pointType === 'Floor' && points[1].pointType === 'Floor') {
|
||||
const updatedFloors1 = getFloorsByPointId(points[0].pointUuid);
|
||||
@@ -397,9 +430,7 @@ function Line({ points }: Readonly<LineProps>) {
|
||||
|
||||
// API
|
||||
|
||||
// upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor).catch((error) => {
|
||||
// console.error('Error updating floor:', error);
|
||||
// });
|
||||
// upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
|
||||
|
||||
// SOCKET
|
||||
|
||||
@@ -413,6 +444,23 @@ function Line({ points }: Readonly<LineProps>) {
|
||||
|
||||
socket.emit('v1:model-Floor:add', data);
|
||||
})
|
||||
|
||||
if (initialPositions.floors && initialPositions.floors.length > 0) {
|
||||
const updatedPoints = initialPositions.floors.map((floor) => ({
|
||||
type: "Floor" as const,
|
||||
lineData: floor,
|
||||
newData: updatedFloors.find(f => f.floorUuid === floor.floorUuid),
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
push2D({
|
||||
type: 'Draw',
|
||||
actions: [{
|
||||
actionType: 'Lines-Update',
|
||||
points: updatedPoints,
|
||||
}]
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (points[0].pointType === 'Zone' && points[1].pointType === 'Zone') {
|
||||
const updatedZones1 = getZonesByPointId(points[0].pointUuid);
|
||||
@@ -424,9 +472,7 @@ function Line({ points }: Readonly<LineProps>) {
|
||||
|
||||
// API
|
||||
|
||||
// upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone).catch((error) => {
|
||||
// console.error('Error updating zone:', error);
|
||||
// });
|
||||
// upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone);
|
||||
|
||||
// SOCKET
|
||||
|
||||
@@ -440,6 +486,23 @@ function Line({ points }: Readonly<LineProps>) {
|
||||
|
||||
socket.emit('v1:zone:add', data);
|
||||
})
|
||||
|
||||
if (initialPositions.zones && initialPositions.zones.length > 0) {
|
||||
const updatedPoints = initialPositions.zones.map((zone) => ({
|
||||
type: "Zone" as const,
|
||||
lineData: zone,
|
||||
newData: updatedZones.find(z => z.zoneUuid === zone.zoneUuid),
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
push2D({
|
||||
type: 'Draw',
|
||||
actions: [{
|
||||
actionType: 'Lines-Update',
|
||||
points: updatedPoints,
|
||||
}]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -631,7 +631,8 @@ function Point({ point }: { readonly point: Point }) {
|
||||
uuid={point.pointUuid}
|
||||
name={`${point.pointType}-Point`}
|
||||
position={[...point.position]}
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePointClick(point);
|
||||
}}
|
||||
onPointerOver={(e) => {
|
||||
@@ -707,7 +708,6 @@ function Point({ point }: { readonly point: Point }) {
|
||||
<meshBasicMaterial color="white" />
|
||||
</mesh>
|
||||
</group>
|
||||
|
||||
}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -16,8 +16,10 @@ import defaultMaterial from '../../../../../assets/textures/floor/wall-tex.png';
|
||||
import material1 from '../../../../../assets/textures/floor/factory wall texture.jpg';
|
||||
|
||||
function Wall({ wall }: { readonly wall: Wall }) {
|
||||
const { wallStore } = useSceneContext();
|
||||
const { wallStore, wallAssetStore } = useSceneContext();
|
||||
const { walls, addDecal } = wallStore();
|
||||
const { wallAssets, getAssetsByWall } = wallAssetStore();
|
||||
const assets = getAssetsByWall(wall.wallUuid);
|
||||
const { selectedWall, setSelectedWall, setSelectedDecal } = useBuilderStore();
|
||||
const { togglView } = useToggleView();
|
||||
const { activeModule } = useModuleStore();
|
||||
@@ -105,19 +107,35 @@ function Wall({ wall }: { readonly wall: Wall }) {
|
||||
key={wall.wallUuid}
|
||||
userData={wall}
|
||||
>
|
||||
<Base
|
||||
castShadow
|
||||
receiveShadow
|
||||
ref={meshRef}
|
||||
geometry={geometry}
|
||||
position={[centerX, centerY, centerZ]}
|
||||
rotation={[0, -angle, 0]}
|
||||
userData={wall}
|
||||
>
|
||||
{materials.map((material, index) => (
|
||||
<primitive key={index} visible={visible} object={material} attach={`material-${index}`} />
|
||||
))}
|
||||
</Base>
|
||||
{(assets.length > 0 || (walls[0].wallUuid === wall.wallUuid && wallAssets.length > 0)) ?
|
||||
<Base
|
||||
castShadow
|
||||
receiveShadow
|
||||
ref={meshRef}
|
||||
geometry={geometry}
|
||||
position={[centerX, centerY, centerZ]}
|
||||
rotation={[0, -angle, 0]}
|
||||
userData={wall}
|
||||
>
|
||||
{materials.map((material, index) => (
|
||||
<primitive key={index} visible={visible} object={material} attach={`material-${index}`} />
|
||||
))}
|
||||
</Base>
|
||||
:
|
||||
<mesh
|
||||
castShadow
|
||||
receiveShadow
|
||||
ref={meshRef}
|
||||
geometry={geometry}
|
||||
position={[centerX, centerY, centerZ]}
|
||||
rotation={[0, -angle, 0]}
|
||||
userData={wall}
|
||||
>
|
||||
{materials.map((material, index) => (
|
||||
<primitive key={index} visible={visible} object={material} attach={`material-${index}`} />
|
||||
))}
|
||||
</mesh>
|
||||
}
|
||||
<mesh
|
||||
castShadow
|
||||
receiveShadow
|
||||
|
||||
@@ -44,6 +44,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
|
||||
|
||||
dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
|
||||
loader.setDRACOLoader(dracoLoader);
|
||||
|
||||
const loadModel = async () => {
|
||||
try {
|
||||
// Check Cache
|
||||
@@ -114,11 +115,56 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
|
||||
useEffect(() => {
|
||||
const canvasElement = gl.domElement;
|
||||
|
||||
const onPointerUp = () => {
|
||||
const onPointerUp = (e: PointerEvent) => {
|
||||
draggingRef.current = false;
|
||||
if (controls) {
|
||||
(controls as any).enabled = true;
|
||||
}
|
||||
|
||||
if (selectedWallAsset) {
|
||||
pointer.x = (e.clientX / window.innerWidth) * 2 - 1;
|
||||
pointer.y = -(e.clientY / window.innerHeight) * 2 + 1;
|
||||
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersects = raycaster.intersectObjects(scene.children, true);
|
||||
const intersect = intersects.find((i: any) => i.object.name.includes('WallReference'));
|
||||
|
||||
if (intersect && intersect.object.userData.wallUuid && selectedWallAsset.userData.modelUuid === wallAsset.modelUuid) {
|
||||
const newPoint = closestPointOnLineSegment(
|
||||
new THREE.Vector3(intersect.point.x, 0, intersect.point.z),
|
||||
new THREE.Vector3(...intersect.object.userData.points[0].position),
|
||||
new THREE.Vector3(...intersect.object.userData.points[1].position)
|
||||
);
|
||||
|
||||
const wallRotation = intersect.object.rotation.clone();
|
||||
|
||||
const updatedWallAsset = updateWallAsset(wallAsset.modelUuid, {
|
||||
wallUuid: intersect.object.userData.wallUuid,
|
||||
position: [newPoint.x, wallAsset.wallAssetType === 'fixed-move' ? 0 : intersect.point.y, newPoint.z],
|
||||
rotation: [wallRotation.x, wallRotation.y, wallRotation.z],
|
||||
});
|
||||
|
||||
if (projectId && updatedWallAsset) {
|
||||
|
||||
// API
|
||||
|
||||
// upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset);
|
||||
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
wallAssetData: updatedWallAsset,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
userId: userId,
|
||||
organization: organization
|
||||
}
|
||||
|
||||
socket.emit('v1:wall-asset:add', data);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerMove = (e: any) => {
|
||||
@@ -142,31 +188,11 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
|
||||
|
||||
const wallRotation = intersect.object.rotation.clone();
|
||||
|
||||
const updatedWallAsset = updateWallAsset(wallAsset.modelUuid, {
|
||||
updateWallAsset(wallAsset.modelUuid, {
|
||||
wallUuid: intersect.object.userData.wallUuid,
|
||||
position: [newPoint.x, wallAsset.wallAssetType === 'fixed-move' ? 0 : intersect.point.y, newPoint.z],
|
||||
rotation: [wallRotation.x, wallRotation.y, wallRotation.z],
|
||||
});
|
||||
|
||||
if (projectId && updatedWallAsset) {
|
||||
|
||||
// API
|
||||
|
||||
// upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset);
|
||||
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
wallAssetData: updatedWallAsset,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
userId: userId,
|
||||
organization: organization
|
||||
}
|
||||
|
||||
socket.emit('v1:wall-asset:add', data);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -225,7 +251,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
|
||||
}
|
||||
}, [activeTool, deletableWallAsset]);
|
||||
|
||||
if (!gltfScene || !boundingBox || !wall) { return null }
|
||||
if (!gltfScene || !boundingBox) { return null }
|
||||
const size = new THREE.Vector3();
|
||||
boundingBox.getSize(size);
|
||||
const center = new THREE.Vector3();
|
||||
@@ -242,7 +268,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
|
||||
visible={wallAsset.isVisible}
|
||||
userData={wallAsset}
|
||||
>
|
||||
<Subtraction position={[center.x, center.y, 0]} scale={[size.x, size.y, wall.wallThickness + 0.05]}>
|
||||
<Subtraction position={[center.x, center.y, 0]} scale={[size.x, size.y, (wall?.wallThickness ?? 0) + 0.05]}>
|
||||
<Geometry>
|
||||
<Base geometry={new THREE.BoxGeometry()} />
|
||||
</Geometry>
|
||||
|
||||
@@ -17,295 +17,292 @@ import setCameraView from "../functions/setCameraView";
|
||||
import { getUserData } from "../../../functions/getUserData";
|
||||
|
||||
const CamModelsGroup = () => {
|
||||
const navigate = useNavigate();
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
const { userId, organization, email } = getUserData();
|
||||
const { setActiveUsers } = useActiveUsers();
|
||||
const { socket } = useSocketStore();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { selectedUser, setSelectedUser } = useSelectedUserStore();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const loader = new GLTFLoader();
|
||||
const dracoLoader = new DRACOLoader();
|
||||
dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/");
|
||||
loader.setDRACOLoader(dracoLoader);
|
||||
|
||||
|
||||
const { camMode } = useCamMode();
|
||||
const { camera, controls } = useThree(); // Access R3F camera and controls
|
||||
|
||||
useEffect(() => {
|
||||
if (camMode !== "FollowPerson") return;
|
||||
// If a user is selected, set the camera view to their location
|
||||
// and update the camera and controls accordingly
|
||||
if (selectedUser?.location) {
|
||||
const { position, rotation, target } = selectedUser.location;
|
||||
if (rotation && target)
|
||||
setCameraView({
|
||||
controls,
|
||||
camera,
|
||||
position,
|
||||
rotation,
|
||||
target,
|
||||
username: selectedUser.name,
|
||||
});
|
||||
}
|
||||
}, [selectedUser, camera, controls, camMode]);
|
||||
|
||||
const [cams, setCams] = useState<any[]>([]);
|
||||
const [models, setModels] = useState<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
targetPosition: THREE.Vector3;
|
||||
targetRotation: THREE.Euler;
|
||||
target: THREE.Vector3;
|
||||
}
|
||||
>
|
||||
>({});
|
||||
|
||||
const dedupeCams = (cams: any[]) => {
|
||||
const seen = new Set();
|
||||
return cams.filter((cam) => {
|
||||
if (seen.has(cam.uuid)) return false;
|
||||
seen.add(cam.uuid);
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
const dedupeUsers = (users: any[]) => {
|
||||
const seen = new Set();
|
||||
return users.filter((user) => {
|
||||
if (seen.has(user._id)) return false;
|
||||
seen.add(user._id);
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!email) navigate("/");
|
||||
|
||||
if (!socket) return;
|
||||
|
||||
socket.on("userConnectResponse", (data: any) => {
|
||||
if (!groupRef.current) return;
|
||||
if (data.data.userData.email === email) return;
|
||||
if (socket.id === data.socketId || organization !== data.organization)
|
||||
return;
|
||||
|
||||
const model = groupRef.current.getObjectByProperty(
|
||||
"uuid",
|
||||
data.data.userData._id
|
||||
);
|
||||
if (model) {
|
||||
groupRef.current.remove(model);
|
||||
}
|
||||
|
||||
loader.load(camModel, (gltf) => {
|
||||
const newModel = gltf.scene.clone();
|
||||
newModel.uuid = data.data.userData._id;
|
||||
newModel.position.set(
|
||||
data.data.position.x,
|
||||
data.data.position.y,
|
||||
data.data.position.z
|
||||
);
|
||||
newModel.rotation.set(
|
||||
data.data.rotation.x,
|
||||
data.data.rotation.y,
|
||||
data.data.rotation.z
|
||||
);
|
||||
newModel.userData = data.data.userData;
|
||||
newModel.userData.target = new THREE.Vector3(
|
||||
data.data.target.x,
|
||||
data.data.target.y,
|
||||
data.data.target.z
|
||||
);
|
||||
|
||||
setCams((prev) => dedupeCams([...prev, newModel]));
|
||||
setActiveUsers((prev: any) =>
|
||||
dedupeUsers([...prev, data.data.userData])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("userDisConnectResponse", (data: any) => {
|
||||
if (!groupRef.current) return;
|
||||
if (socket.id === data.socketId || organization !== data.organization)
|
||||
return;
|
||||
|
||||
setCams((prev) =>
|
||||
prev.filter((cam) => cam.uuid !== data.data.userData._id)
|
||||
);
|
||||
setActiveUsers((prev: any) =>
|
||||
prev.filter((user: any) => user._id !== data.data.userData._id)
|
||||
);
|
||||
});
|
||||
|
||||
socket.on("v1:camera:Response:update", (data: any) => {
|
||||
// console.log('data: ', data);
|
||||
if (
|
||||
!groupRef.current ||
|
||||
socket.id === data.socketId ||
|
||||
organization !== data.organization
|
||||
)
|
||||
return;
|
||||
|
||||
if (selectedUser && selectedUser?.id === data.data.userId) {
|
||||
setSelectedUser({
|
||||
color: selectedUser.color,
|
||||
name: selectedUser.name,
|
||||
id: selectedUser.id,
|
||||
location: {
|
||||
position: data.data.position,
|
||||
rotation: data.data.rotation,
|
||||
target: data.data.target,
|
||||
},
|
||||
});
|
||||
}
|
||||
setModels((prev) => ({
|
||||
...prev,
|
||||
[data.data.userId]: {
|
||||
targetPosition: new THREE.Vector3(
|
||||
data.data.position.x,
|
||||
data.data.position.y,
|
||||
data.data.position.z
|
||||
),
|
||||
targetRotation: new THREE.Euler(
|
||||
data.data.rotation.x,
|
||||
data.data.rotation.y,
|
||||
data.data.rotation.z
|
||||
),
|
||||
target: new THREE.Vector3(
|
||||
data.data.target.x,
|
||||
data.data.target.y,
|
||||
data.data.target.z
|
||||
),
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.off("userConnectResponse");
|
||||
socket.off("userDisConnectResponse");
|
||||
socket.off("v1:camera:Response:update");
|
||||
};
|
||||
const navigate = useNavigate();
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
const { organization, email } = getUserData();
|
||||
const { setActiveUsers } = useActiveUsers();
|
||||
const { socket } = useSocketStore();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { selectedUser, setSelectedUser } = useSelectedUserStore();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [email, loader, navigate, setActiveUsers, socket]);
|
||||
const loader = new GLTFLoader();
|
||||
const dracoLoader = new DRACOLoader();
|
||||
dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/");
|
||||
loader.setDRACOLoader(dracoLoader);
|
||||
|
||||
useFrame(() => {
|
||||
if (!groupRef.current) return;
|
||||
Object.keys(models).forEach((uuid) => {
|
||||
const model = groupRef.current!.getObjectByProperty("uuid", uuid);
|
||||
if (!model) return;
|
||||
const { camMode } = useCamMode();
|
||||
const { camera, controls } = useThree();
|
||||
|
||||
const { targetPosition, targetRotation } = models[uuid];
|
||||
model.position.lerp(targetPosition, 0.1);
|
||||
model.rotation.x = THREE.MathUtils.lerp(
|
||||
model.rotation.x,
|
||||
targetRotation.x,
|
||||
0.1
|
||||
);
|
||||
model.rotation.y = THREE.MathUtils.lerp(
|
||||
model.rotation.y,
|
||||
targetRotation.y,
|
||||
0.1
|
||||
);
|
||||
model.rotation.z = THREE.MathUtils.lerp(
|
||||
model.rotation.z,
|
||||
targetRotation.z,
|
||||
0.1
|
||||
);
|
||||
});
|
||||
});
|
||||
useEffect(() => {
|
||||
if (camMode !== "FollowPerson") return;
|
||||
if (selectedUser?.location) {
|
||||
const { position, rotation, target } = selectedUser.location;
|
||||
if (rotation && target)
|
||||
setCameraView({
|
||||
controls,
|
||||
camera,
|
||||
position,
|
||||
rotation,
|
||||
target,
|
||||
username: selectedUser.name,
|
||||
});
|
||||
}
|
||||
}, [selectedUser, camera, controls, camMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!groupRef.current) return;
|
||||
|
||||
getActiveUsersData(organization).then((data) => {
|
||||
const filteredData = data.cameraDatas.filter(
|
||||
(camera: any) => camera.userData.email !== email
|
||||
);
|
||||
|
||||
if (filteredData.length > 0) {
|
||||
loader.load(camModel, (gltf) => {
|
||||
const newCams = filteredData.map((cam: any) => {
|
||||
const newModel = gltf.scene.clone();
|
||||
newModel.uuid = cam.userData._id;
|
||||
newModel.position.set(
|
||||
cam.position.x,
|
||||
cam.position.y,
|
||||
cam.position.z
|
||||
);
|
||||
newModel.rotation.set(
|
||||
cam.rotation.x,
|
||||
cam.rotation.y,
|
||||
cam.rotation.z
|
||||
);
|
||||
newModel.userData = cam.userData;
|
||||
cam.userData.position = newModel.position;
|
||||
cam.userData.rotation = newModel.rotation;
|
||||
newModel.userData.target = cam.target;
|
||||
|
||||
return newModel;
|
||||
});
|
||||
|
||||
const users = filteredData.map((cam: any) => cam.userData);
|
||||
setActiveUsers((prev: any) => dedupeUsers([...prev, ...users]));
|
||||
setCams((prev) => dedupeCams([...prev, ...newCams]));
|
||||
});
|
||||
}
|
||||
}).catch(() => {
|
||||
console.log('Error fetching active users data')
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<group ref={groupRef} name="Cam-Model-Group">
|
||||
{cams.map((cam, index) => (
|
||||
<primitive
|
||||
key={cam.uuid}
|
||||
//eslint-disable-next-line
|
||||
object={cam}
|
||||
visible={
|
||||
selectedUser?.name !== cam.userData.userName &&
|
||||
activeModule !== "visualization" &&
|
||||
!isPlaying
|
||||
}
|
||||
const [cams, setCams] = useState<any[]>([]);
|
||||
const [models, setModels] = useState<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
targetPosition: THREE.Vector3;
|
||||
targetRotation: THREE.Euler;
|
||||
target: THREE.Vector3;
|
||||
}
|
||||
>
|
||||
<Html
|
||||
as="div"
|
||||
center
|
||||
zIndexRange={[1, 0]}
|
||||
sprite
|
||||
style={{
|
||||
color: "white",
|
||||
textAlign: "center",
|
||||
fontFamily: "Arial, sans-serif",
|
||||
display: `${activeModule !== "visualization" ? "" : "none"}`,
|
||||
opacity: `${selectedUser?.name !== cam.userData.userName && !isPlaying
|
||||
? 1
|
||||
: 0
|
||||
}`,
|
||||
transition: "opacity .2s ease",
|
||||
}}
|
||||
position={[-0.015, 0, 0.7]}
|
||||
>
|
||||
<CollabUserIcon
|
||||
userImage={cam.userData.userImage ?? ""}
|
||||
userName={cam.userData.userName}
|
||||
id={cam.userData._id}
|
||||
color={getAvatarColor(index, cam.userData.userName)}
|
||||
position={cam.position}
|
||||
rotation={cam.rotation}
|
||||
target={cam.userData.target}
|
||||
/>
|
||||
</Html>
|
||||
</primitive>
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
>({});
|
||||
|
||||
const dedupeCams = (cams: any[]) => {
|
||||
const seen = new Set();
|
||||
return cams.filter((cam) => {
|
||||
if (seen.has(cam.uuid)) return false;
|
||||
seen.add(cam.uuid);
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
const dedupeUsers = (users: any[]) => {
|
||||
const seen = new Set();
|
||||
return users.filter((user) => {
|
||||
if (seen.has(user._id)) return false;
|
||||
seen.add(user._id);
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!email) navigate("/");
|
||||
|
||||
if (!socket) return;
|
||||
|
||||
socket.on("userConnectResponse", (data: any) => {
|
||||
if (!groupRef.current) return;
|
||||
if (data.data.userData.email === email) return;
|
||||
if (socket.id === data.socketId || organization !== data.organization)
|
||||
return;
|
||||
|
||||
const model = groupRef.current.getObjectByProperty(
|
||||
"uuid",
|
||||
data.data.userData._id
|
||||
);
|
||||
if (model) {
|
||||
groupRef.current.remove(model);
|
||||
}
|
||||
|
||||
loader.load(camModel, (gltf) => {
|
||||
const newModel = gltf.scene.clone();
|
||||
newModel.uuid = data.data.userData._id;
|
||||
newModel.position.set(
|
||||
data.data.position.x,
|
||||
data.data.position.y,
|
||||
data.data.position.z
|
||||
);
|
||||
newModel.rotation.set(
|
||||
data.data.rotation.x,
|
||||
data.data.rotation.y,
|
||||
data.data.rotation.z
|
||||
);
|
||||
newModel.userData = data.data.userData;
|
||||
newModel.userData.target = new THREE.Vector3(
|
||||
data.data.target.x,
|
||||
data.data.target.y,
|
||||
data.data.target.z
|
||||
);
|
||||
|
||||
setCams((prev) => dedupeCams([...prev, newModel]));
|
||||
setActiveUsers((prev: any) =>
|
||||
dedupeUsers([...prev, data.data.userData])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("userDisConnectResponse", (data: any) => {
|
||||
if (!groupRef.current) return;
|
||||
if (socket.id === data.socketId || organization !== data.organization)
|
||||
return;
|
||||
|
||||
setCams((prev) =>
|
||||
prev.filter((cam) => cam.uuid !== data.data.userData._id)
|
||||
);
|
||||
setActiveUsers((prev: any) =>
|
||||
prev.filter((user: any) => user._id !== data.data.userData._id)
|
||||
);
|
||||
});
|
||||
|
||||
socket.on("v1:camera:Response:update", (data: any) => {
|
||||
// console.log('data: ', data);
|
||||
if (
|
||||
!groupRef.current ||
|
||||
socket.id === data.socketId ||
|
||||
organization !== data.organization
|
||||
)
|
||||
return;
|
||||
|
||||
if (selectedUser && selectedUser?.id === data.data.userId) {
|
||||
setSelectedUser({
|
||||
color: selectedUser.color,
|
||||
name: selectedUser.name,
|
||||
id: selectedUser.id,
|
||||
location: {
|
||||
position: data.data.position,
|
||||
rotation: data.data.rotation,
|
||||
target: data.data.target,
|
||||
},
|
||||
});
|
||||
}
|
||||
setModels((prev) => ({
|
||||
...prev,
|
||||
[data.data.userId]: {
|
||||
targetPosition: new THREE.Vector3(
|
||||
data.data.position.x,
|
||||
data.data.position.y,
|
||||
data.data.position.z
|
||||
),
|
||||
targetRotation: new THREE.Euler(
|
||||
data.data.rotation.x,
|
||||
data.data.rotation.y,
|
||||
data.data.rotation.z
|
||||
),
|
||||
target: new THREE.Vector3(
|
||||
data.data.target.x,
|
||||
data.data.target.y,
|
||||
data.data.target.z
|
||||
),
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.off("userConnectResponse");
|
||||
socket.off("userDisConnectResponse");
|
||||
socket.off("v1:camera:Response:update");
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [email, loader, navigate, setActiveUsers, socket]);
|
||||
|
||||
useFrame(() => {
|
||||
if (!groupRef.current) return;
|
||||
Object.keys(models).forEach((uuid) => {
|
||||
const model = groupRef.current!.getObjectByProperty("uuid", uuid);
|
||||
if (!model) return;
|
||||
|
||||
const { targetPosition, targetRotation } = models[uuid];
|
||||
model.position.lerp(targetPosition, 0.1);
|
||||
model.rotation.x = THREE.MathUtils.lerp(
|
||||
model.rotation.x,
|
||||
targetRotation.x,
|
||||
0.1
|
||||
);
|
||||
model.rotation.y = THREE.MathUtils.lerp(
|
||||
model.rotation.y,
|
||||
targetRotation.y,
|
||||
0.1
|
||||
);
|
||||
model.rotation.z = THREE.MathUtils.lerp(
|
||||
model.rotation.z,
|
||||
targetRotation.z,
|
||||
0.1
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!groupRef.current) return;
|
||||
|
||||
getActiveUsersData(organization).then((data) => {
|
||||
const filteredData = data.cameraDatas.filter(
|
||||
(camera: any) => camera.userData.email !== email
|
||||
);
|
||||
|
||||
if (filteredData.length > 0) {
|
||||
loader.load(camModel, (gltf) => {
|
||||
const newCams = filteredData.map((cam: any) => {
|
||||
const newModel = gltf.scene.clone();
|
||||
newModel.uuid = cam.userData._id;
|
||||
newModel.position.set(
|
||||
cam.position.x,
|
||||
cam.position.y,
|
||||
cam.position.z
|
||||
);
|
||||
newModel.rotation.set(
|
||||
cam.rotation.x,
|
||||
cam.rotation.y,
|
||||
cam.rotation.z
|
||||
);
|
||||
newModel.userData = cam.userData;
|
||||
cam.userData.position = newModel.position;
|
||||
cam.userData.rotation = newModel.rotation;
|
||||
newModel.userData.target = cam.target;
|
||||
|
||||
return newModel;
|
||||
});
|
||||
|
||||
const users = filteredData.map((cam: any) => cam.userData);
|
||||
setActiveUsers((prev: any) => dedupeUsers([...prev, ...users]));
|
||||
setCams((prev) => dedupeCams([...prev, ...newCams]));
|
||||
});
|
||||
}
|
||||
}).catch(() => {
|
||||
console.log('Error fetching active users data')
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<group ref={groupRef} name="Cam-Model-Group">
|
||||
{cams.map((cam, index) => (
|
||||
<primitive
|
||||
key={cam.uuid}
|
||||
//eslint-disable-next-line
|
||||
object={cam}
|
||||
visible={
|
||||
selectedUser?.name !== cam.userData.userName &&
|
||||
activeModule !== "visualization" &&
|
||||
!isPlaying
|
||||
}
|
||||
>
|
||||
<Html
|
||||
as="div"
|
||||
center
|
||||
zIndexRange={[1, 0]}
|
||||
sprite
|
||||
style={{
|
||||
color: "white",
|
||||
textAlign: "center",
|
||||
fontFamily: "Arial, sans-serif",
|
||||
display: `${activeModule !== "visualization" ? "" : "none"}`,
|
||||
opacity: `${selectedUser?.name !== cam.userData.userName && !isPlaying
|
||||
? 1
|
||||
: 0
|
||||
}`,
|
||||
transition: "opacity .2s ease",
|
||||
}}
|
||||
position={[-0.015, 0, 0.7]}
|
||||
>
|
||||
<CollabUserIcon
|
||||
userImage={cam.userData.userImage ?? ""}
|
||||
userName={cam.userData.userName}
|
||||
id={cam.userData._id}
|
||||
color={getAvatarColor(index, cam.userData.userName)}
|
||||
position={cam.position}
|
||||
rotation={cam.rotation}
|
||||
target={cam.userData.target}
|
||||
/>
|
||||
</Html>
|
||||
</primitive>
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
};
|
||||
|
||||
export default CamModelsGroup;
|
||||
|
||||
@@ -9,124 +9,89 @@ import { detectModifierKeys } from "../../../utils/shortcutkeys/detectModifierKe
|
||||
import { firstPersonCamera } from "./firstPersonCamera";
|
||||
|
||||
const CamMode: React.FC = () => {
|
||||
const { camMode, setCamMode } = useCamMode();
|
||||
const [, get] = useKeyboardControls();
|
||||
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||
const state: any = useThree();
|
||||
const { toggleView } = useToggleView();
|
||||
const [isShiftActive, setIsShiftActive] = useState(false);
|
||||
const { camMode, setCamMode } = useCamMode();
|
||||
const [_, get] = useKeyboardControls();
|
||||
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||
const state: any = useThree();
|
||||
const { toggleView } = useToggleView();
|
||||
const [isShiftActive, setIsShiftActive] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handlePointerLockChange = async () => {
|
||||
if (document.pointerLockElement && !toggleView) {
|
||||
// Pointer is locked
|
||||
} else if (camMode === "FirstPerson" && !toggleView) {
|
||||
// Pointer is unlocked
|
||||
setCamMode("ThirdPerson");
|
||||
await switchToThirdPerson(state.controls, state.camera);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
const handlePointerLockChange = async () => {
|
||||
if (document.pointerLockElement && !toggleView) {
|
||||
} else if (camMode === "FirstPerson" && !toggleView) {
|
||||
setCamMode("ThirdPerson");
|
||||
await switchToThirdPerson(state.controls, state.camera);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("pointerlockchange", handlePointerLockChange);
|
||||
document.addEventListener("pointerlockchange", handlePointerLockChange);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener(
|
||||
"pointerlockchange",
|
||||
handlePointerLockChange
|
||||
);
|
||||
};
|
||||
}, [camMode, toggleView, setCamMode, state.controls, state.camera]);
|
||||
return () => {
|
||||
document.removeEventListener("pointerlockchange", handlePointerLockChange);
|
||||
};
|
||||
}, [camMode, toggleView, setCamMode, state.controls, state.camera]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "Shift") {
|
||||
setIsShiftActive(true);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
const handleKeyPress = async (event: KeyboardEvent) => {
|
||||
if (!state.controls) return;
|
||||
|
||||
const handleKeyUp = (event: KeyboardEvent) => {
|
||||
if (event.key === "Shift") {
|
||||
setIsShiftActive(false);
|
||||
}
|
||||
};
|
||||
const keyCombination = detectModifierKeys(event);
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
window.addEventListener("keyup", handleKeyUp);
|
||||
if (keyCombination === "/" && !isTransitioning && !toggleView) {
|
||||
firstPersonCamera({
|
||||
setIsTransitioning,
|
||||
state,
|
||||
camMode,
|
||||
setCamMode,
|
||||
switchToFirstPerson,
|
||||
switchToThirdPerson,
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
window.removeEventListener("keyup", handleKeyUp);
|
||||
};
|
||||
}, []);
|
||||
if (keyCombination === 'Shift') {
|
||||
setIsShiftActive(true);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyPress = async (event: KeyboardEvent) => {
|
||||
if (!state.controls) return;
|
||||
const handleKeyUp = (event: KeyboardEvent) => {
|
||||
if (event.key === "Shift") {
|
||||
setIsShiftActive(false);
|
||||
}
|
||||
};
|
||||
|
||||
const keyCombination = detectModifierKeys(event);
|
||||
window.addEventListener("keydown", handleKeyPress);
|
||||
window.addEventListener("keyup", handleKeyUp);
|
||||
|
||||
if (keyCombination === "/" && !isTransitioning && !toggleView) {
|
||||
firstPersonCamera({
|
||||
setIsTransitioning,
|
||||
state,
|
||||
camMode,
|
||||
setCamMode,
|
||||
switchToFirstPerson,
|
||||
switchToThirdPerson,
|
||||
});
|
||||
}
|
||||
};
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyPress);
|
||||
window.removeEventListener("keyup", handleKeyUp);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [camMode, isTransitioning, toggleView, state.controls, state.camera, setCamMode]);
|
||||
|
||||
window.addEventListener("keydown", handleKeyPress);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyPress);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
camMode,
|
||||
isTransitioning,
|
||||
toggleView,
|
||||
state.controls,
|
||||
state.camera,
|
||||
setCamMode,
|
||||
]);
|
||||
useFrame(() => {
|
||||
const { forward, backward, left, right } = get();
|
||||
if (!state.controls) return;
|
||||
if (camMode === "ThirdPerson" || !document.pointerLockElement) return;
|
||||
|
||||
useFrame(() => {
|
||||
const { forward, backward, left, right } = get();
|
||||
if (!state.controls) return;
|
||||
if (camMode === "ThirdPerson" || !document.pointerLockElement) return;
|
||||
const speedMultiplier = isShiftActive ? 4 : 1;
|
||||
|
||||
const speedMultiplier = isShiftActive ? 4 : 1;
|
||||
if (forward) {
|
||||
state.controls.forward(CONSTANTS.firstPersonControls.forwardSpeed * speedMultiplier, true);
|
||||
}
|
||||
if (backward) {
|
||||
state.controls.forward(CONSTANTS.firstPersonControls.backwardSpeed * speedMultiplier, true);
|
||||
}
|
||||
if (left) {
|
||||
state.controls.truck(CONSTANTS.firstPersonControls.leftSpeed * speedMultiplier, 0, true);
|
||||
}
|
||||
if (right) {
|
||||
state.controls.truck(CONSTANTS.firstPersonControls.rightSpeed * speedMultiplier, 0, true);
|
||||
}
|
||||
});
|
||||
|
||||
if (forward) {
|
||||
state.controls.forward(
|
||||
CONSTANTS.firstPersonControls.forwardSpeed * speedMultiplier,
|
||||
true
|
||||
);
|
||||
}
|
||||
if (backward) {
|
||||
state.controls.forward(
|
||||
CONSTANTS.firstPersonControls.backwardSpeed * speedMultiplier,
|
||||
true
|
||||
);
|
||||
}
|
||||
if (left) {
|
||||
state.controls.truck(
|
||||
CONSTANTS.firstPersonControls.leftSpeed * speedMultiplier,
|
||||
0,
|
||||
true
|
||||
);
|
||||
}
|
||||
if (right) {
|
||||
state.controls.truck(
|
||||
CONSTANTS.firstPersonControls.rightSpeed * speedMultiplier,
|
||||
0,
|
||||
true
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return null; // This component does not render any UI
|
||||
return null;
|
||||
};
|
||||
|
||||
export default CamMode;
|
||||
|
||||
@@ -1,73 +1,68 @@
|
||||
import * as THREE from "three";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useToggleView } from "../../../store/builder/store";
|
||||
import { useEffect } from "react";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi";
|
||||
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||
import * as THREE from 'three';
|
||||
import { PerspectiveCamera, OrthographicCamera, CameraControls } from '@react-three/drei';
|
||||
import { useParams } from "react-router-dom";
|
||||
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||
import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi";
|
||||
import { getUserData } from "../../../functions/getUserData";
|
||||
import { CameraControls } from "@react-three/drei";
|
||||
import { useToggleView } from "../../../store/builder/store";
|
||||
|
||||
export default function SwitchView() {
|
||||
const { toggleView } = useToggleView();
|
||||
const state: any = useThree();
|
||||
const { set } = useThree();
|
||||
const perspectiveCamera = useRef<THREE.PerspectiveCamera | null>(null);
|
||||
const orthoCamera = useRef<THREE.OrthographicCamera | null>(null);
|
||||
orthoCamera.current = new THREE.OrthographicCamera(-window.innerWidth / 2, window.innerWidth / 2, window.innerHeight / 2, -window.innerHeight / 2, 0.01, 1000);
|
||||
perspectiveCamera.current = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.01, 1000);
|
||||
const { projectId } = useParams();
|
||||
const { organization } = getUserData();
|
||||
const { toggleView } = useToggleView();
|
||||
const { controls } = useThree();
|
||||
const { projectId } = useParams();
|
||||
const { organization } = getUserData();
|
||||
|
||||
useEffect(() => {
|
||||
if (!perspectiveCamera.current || !orthoCamera.current) return;
|
||||
if (toggleView) {
|
||||
orthoCamera.current.zoom = 10;
|
||||
orthoCamera.current.position.set(...CONSTANTS.twoDimension.defaultPosition);
|
||||
orthoCamera.current.lookAt(new THREE.Vector3(...CONSTANTS.twoDimension.defaultTarget));
|
||||
orthoCamera.current.updateProjectionMatrix();
|
||||
set({ camera: orthoCamera.current });
|
||||
orthoCamera.current.updateProjectionMatrix();
|
||||
} else if (!toggleView) {
|
||||
perspectiveCamera.current.position.set(...CONSTANTS.threeDimension.defaultPosition);
|
||||
perspectiveCamera.current.lookAt(new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget));
|
||||
set({ camera: perspectiveCamera.current });
|
||||
}
|
||||
}, [toggleView, set]);
|
||||
useEffect(() => {
|
||||
if (toggleView && controls) {
|
||||
(controls as any).mouseButtons.left = CONSTANTS.twoDimension.leftMouse;
|
||||
(controls as any).mouseButtons.right = CONSTANTS.twoDimension.rightMouse;
|
||||
} else {
|
||||
try {
|
||||
getCamera(organization, localStorage.getItem('userId')!, projectId).then((data) => {
|
||||
if (data && data.position && data.target) {
|
||||
(controls as CameraControls)?.setPosition(data.position.x, data.position.y, data.position.z);
|
||||
(controls as CameraControls)?.setTarget(data.target.x, data.target.y, data.target.z);
|
||||
} else {
|
||||
(controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
|
||||
(controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
echo.error("Failed to retrieve camera position or target");
|
||||
(controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
|
||||
(controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (toggleView && state.controls) {
|
||||
state.controls.mouseButtons.left = CONSTANTS.twoDimension.leftMouse;
|
||||
state.controls.mouseButtons.right = CONSTANTS.twoDimension.rightMouse;
|
||||
} else {
|
||||
try {
|
||||
getCamera(organization, localStorage.getItem('userId')!, projectId).then((data) => {
|
||||
if (data && data.position && data.target) {
|
||||
// state.controls?.setLookAt(data.position.x, data.position.y, data.position.z, data.target.x, data.target.y, data.target.z, true)
|
||||
state.controls?.setPosition(data.position.x, data.position.y, data.position.z);
|
||||
state.controls?.setTarget(data.target.x, data.target.y, data.target.z);
|
||||
} else {
|
||||
// state.controls?.setLookAt(...CONSTANTS.threeDimension.defaultPosition, ...CONSTANTS.threeDimension.defaultTarget, true);
|
||||
state.controls?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
|
||||
state.controls?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
echo.error("Failed to retrieve camera position or target");
|
||||
console.error("Failed to retrieve camera position or target:", error);
|
||||
// state.controls?.setLookAt(...CONSTANTS.threeDimension.defaultPosition, ...CONSTANTS.threeDimension.defaultTarget, true);
|
||||
state.controls?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
|
||||
state.controls?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
|
||||
}
|
||||
if (controls) {
|
||||
(controls as any).mouseButtons.left = CONSTANTS.threeDimension.leftMouse;
|
||||
(controls as any).mouseButtons.right = CONSTANTS.threeDimension.rightMouse;
|
||||
}
|
||||
}
|
||||
}, [toggleView, controls]);
|
||||
|
||||
if (state.controls) {
|
||||
state.controls.mouseButtons.left = CONSTANTS.threeDimension.leftMouse;
|
||||
state.controls.mouseButtons.right = CONSTANTS.threeDimension.rightMouse;
|
||||
}
|
||||
}
|
||||
}, [toggleView, state.controls]);
|
||||
|
||||
return (
|
||||
<></>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{toggleView ? (
|
||||
<OrthographicCamera
|
||||
makeDefault
|
||||
position={CONSTANTS.twoDimension.defaultPosition}
|
||||
zoom={10}
|
||||
near={0.01}
|
||||
far={1000}
|
||||
onUpdate={(self) => self.lookAt(new THREE.Vector3(...CONSTANTS.twoDimension.defaultTarget))}
|
||||
/>
|
||||
) : (
|
||||
<PerspectiveCamera
|
||||
makeDefault
|
||||
fov={75}
|
||||
position={CONSTANTS.threeDimension.defaultPosition}
|
||||
near={0.01}
|
||||
far={1000}
|
||||
onUpdate={(self) => self.lookAt(new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget))}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -13,16 +13,17 @@ import SelectionControls3D from "./selectionControls/selection3D/selectionContro
|
||||
import TransformControl from "./transformControls/transformControls";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { getUserData } from "../../../functions/getUserData";
|
||||
|
||||
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
|
||||
import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls";
|
||||
|
||||
export default function Controls() {
|
||||
const controlsRef = useRef<CameraControls>(null);
|
||||
|
||||
const state = useThree();
|
||||
const { toggleView } = useToggleView();
|
||||
const { resetCamera, setResetCamera } = useResetCamera();
|
||||
const { socket } = useSocketStore();
|
||||
const state = useThree();
|
||||
const { projectId } = useParams();
|
||||
const { userId, organization } = getUserData();
|
||||
|
||||
@@ -33,7 +34,6 @@ export default function Controls() {
|
||||
}
|
||||
|
||||
getCamera(organization, userId, projectId).then((data) => {
|
||||
// console.log('data: ', data);
|
||||
if (data && data.position && data.target) {
|
||||
controlsRef.current?.setPosition(data.position.x, data.position.y, data.position.z);
|
||||
controlsRef.current?.setTarget(data.target.x, data.target.y, data.target.z);
|
||||
@@ -41,8 +41,7 @@ export default function Controls() {
|
||||
controlsRef.current?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
|
||||
controlsRef.current?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
|
||||
}
|
||||
})
|
||||
.catch((error) => console.error("Failed to fetch camera data:", error));
|
||||
}).catch((error) => console.error("Failed to fetch camera data:", error));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -60,7 +59,6 @@ export default function Controls() {
|
||||
socketId: socket.id,
|
||||
projectId
|
||||
};
|
||||
// console.log('CameracamData: ', camData);
|
||||
socket.emit('v1:Camera:set', camData)
|
||||
|
||||
setResetCamera(false);
|
||||
@@ -128,9 +126,10 @@ export default function Controls() {
|
||||
maxZoom={CONSTANTS.thirdPersonControls.maxZoom}
|
||||
maxPolarAngle={CONSTANTS.thirdPersonControls.maxPolarAngle}
|
||||
camera={state.camera}
|
||||
dollyToCursor={toggleView}
|
||||
verticalDragToForward={true}
|
||||
boundaryEnclosesCamera={true}
|
||||
dollyToCursor={toggleView}
|
||||
dollyDragInverted
|
||||
>
|
||||
|
||||
<SwitchView />
|
||||
|
||||
@@ -36,11 +36,12 @@ function MoveControls2D({
|
||||
const { projectId } = useParams();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext();
|
||||
const { setPosition: setAislePosition, getAislesByPointId } = aisleStore();
|
||||
const { setPosition: setWallPosition, getWallsByPointId } = wallStore();
|
||||
const { setPosition: setFloorPosition, getFloorsByPointId } = floorStore();
|
||||
const { setPosition: setZonePosition, getZonesByPointId } = zoneStore();
|
||||
const { aisleStore, wallStore, floorStore, zoneStore, undoRedo2DStore } = useSceneContext();
|
||||
const { push2D } = undoRedo2DStore();
|
||||
const { setPosition: setAislePosition, getAislesByPointId, getAisleById } = aisleStore();
|
||||
const { setPosition: setWallPosition, getWallsByPointId, getWallById } = wallStore();
|
||||
const { setPosition: setFloorPosition, getFloorsByPointId, getFloorById } = floorStore();
|
||||
const { setPosition: setZonePosition, getZonesByPointId, getZoneById } = zoneStore();
|
||||
const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
|
||||
const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
|
||||
const [initialStates, setInitialStates] = useState<Record<string, { position: THREE.Vector3; rotation?: THREE.Euler; }>>({});
|
||||
@@ -223,6 +224,12 @@ function MoveControls2D({
|
||||
const placeMovedAssets = () => {
|
||||
if (movedObjects.length === 0) return;
|
||||
|
||||
const undoPoints: UndoRedo2DDataTypeSchema[] = [];
|
||||
const processedAisles: UndoRedo2DDataTypeSchema[] = [];
|
||||
const processedWalls: UndoRedo2DDataTypeSchema[] = [];
|
||||
const processedFloors: UndoRedo2DDataTypeSchema[] = [];
|
||||
const processedZones: UndoRedo2DDataTypeSchema[] = [];
|
||||
|
||||
movedObjects.forEach((movedObject: THREE.Object3D) => {
|
||||
if (movedObject.userData.pointUuid) {
|
||||
const point: Point = movedObject.userData as Point;
|
||||
@@ -236,45 +243,84 @@ function MoveControls2D({
|
||||
|
||||
// upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '');
|
||||
|
||||
// SOCKET
|
||||
// SOCKET
|
||||
|
||||
socket.emit('v1:model-aisle:add', {
|
||||
projectId: projectId,
|
||||
projectId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
userId,
|
||||
organization,
|
||||
aisleUuid: updatedAisle.aisleUuid,
|
||||
points: updatedAisle.points,
|
||||
type: updatedAisle.type
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
const old = initialStates[movedObject.uuid];
|
||||
if (old) {
|
||||
processedAisles.push({
|
||||
type: 'Aisle',
|
||||
lineData: {
|
||||
...updatedAisle,
|
||||
points: [
|
||||
updatedAisle.points[0].pointUuid === point.pointUuid
|
||||
? { ...updatedAisle.points[0], position: [old.position.x, old.position.y, old.position.z] }
|
||||
: updatedAisle.points[0],
|
||||
updatedAisle.points[1].pointUuid === point.pointUuid
|
||||
? { ...updatedAisle.points[1], position: [old.position.x, old.position.y, old.position.z] }
|
||||
: updatedAisle.points[1]
|
||||
] as [Point, Point],
|
||||
},
|
||||
newData: updatedAisle,
|
||||
timeStamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (point.pointType === 'Wall') {
|
||||
const updatedWalls = getWallsByPointId(point.pointUuid);
|
||||
if (updatedWalls && updatedWalls.length > 0 && projectId) {
|
||||
updatedWalls.forEach((updatedWall) => {
|
||||
if (updatedWalls?.length && projectId) {
|
||||
updatedWalls.forEach(updatedWall => {
|
||||
|
||||
// API
|
||||
|
||||
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
|
||||
|
||||
// SOCKET
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
socket.emit('v1:model-Wall:add', {
|
||||
wallData: updatedWall,
|
||||
projectId: projectId,
|
||||
projectId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
userId: userId,
|
||||
organization: organization
|
||||
}
|
||||
userId,
|
||||
organization
|
||||
});
|
||||
|
||||
socket.emit('v1:model-Wall:add', data);
|
||||
const old = initialStates[movedObject.uuid];
|
||||
if (old) {
|
||||
processedWalls.push({
|
||||
type: 'Wall',
|
||||
lineData: {
|
||||
...updatedWall,
|
||||
points: [
|
||||
updatedWall.points[0].pointUuid === point.pointUuid
|
||||
? { ...updatedWall.points[0], position: [old.position.x, old.position.y, old.position.z] }
|
||||
: updatedWall.points[0],
|
||||
updatedWall.points[1].pointUuid === point.pointUuid
|
||||
? { ...updatedWall.points[1], position: [old.position.x, old.position.y, old.position.z] }
|
||||
: updatedWall.points[1]
|
||||
] as [Point, Point],
|
||||
},
|
||||
newData: updatedWall,
|
||||
timeStamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (point.pointType === 'Floor') {
|
||||
const Floors = getFloorsByPointId(point.pointUuid);
|
||||
const updatedFloors = getFloorsByPointId(point.pointUuid);
|
||||
if (updatedFloors && updatedFloors.length > 0 && projectId) {
|
||||
updatedFloors.forEach((updatedFloor) => {
|
||||
if (updatedFloors?.length && projectId) {
|
||||
updatedFloors.forEach(updatedFloor => {
|
||||
|
||||
// API
|
||||
|
||||
@@ -282,21 +328,38 @@ function MoveControls2D({
|
||||
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
socket.emit('v1:model-Floor:add', {
|
||||
floorData: updatedFloor,
|
||||
projectId: projectId,
|
||||
projectId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
userId: userId,
|
||||
organization: organization
|
||||
}
|
||||
userId,
|
||||
organization
|
||||
});
|
||||
|
||||
socket.emit('v1:model-Floor:add', data);
|
||||
const updatedFloorsData = updatedFloors.map((floor) => {
|
||||
const originalFloor = Floors.find(f => f.floorUuid === floor.floorUuid) || floor;
|
||||
|
||||
const updatedPoints = originalFloor.points.map((pt: Point) => {
|
||||
const init = initialStates[pt.pointUuid];
|
||||
return init ? { ...pt, position: [init.position.x, init.position.y, init.position.z] } : pt;
|
||||
}) as [Point, Point];
|
||||
|
||||
return {
|
||||
type: "Floor" as const,
|
||||
lineData: { ...originalFloor, points: updatedPoints },
|
||||
newData: floor,
|
||||
timeStamp: new Date().toISOString(),
|
||||
};
|
||||
});
|
||||
|
||||
processedFloors.push(...updatedFloorsData);
|
||||
});
|
||||
}
|
||||
} else if (point.pointType === 'Zone') {
|
||||
const Zones = getZonesByPointId(point.pointUuid);
|
||||
const updatedZones = getZonesByPointId(point.pointUuid);
|
||||
if (updatedZones && updatedZones.length > 0 && projectId) {
|
||||
updatedZones.forEach((updatedZone) => {
|
||||
if (updatedZones?.length && projectId) {
|
||||
updatedZones.forEach(updatedZone => {
|
||||
|
||||
// API
|
||||
|
||||
@@ -304,23 +367,160 @@ function MoveControls2D({
|
||||
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
socket.emit('v1:zone:add', {
|
||||
zoneData: updatedZone,
|
||||
projectId: projectId,
|
||||
projectId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
userId: userId,
|
||||
organization: organization
|
||||
}
|
||||
userId,
|
||||
organization
|
||||
});
|
||||
|
||||
socket.emit('v1:zone:add', data);
|
||||
const updatedZonesData = updatedZones.map((zone) => {
|
||||
const originalZone = Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone;
|
||||
|
||||
const updatedPoints = originalZone.points.map((pt: Point) => {
|
||||
const init = initialStates[pt.pointUuid];
|
||||
return init ? { ...pt, position: [init.position.x, init.position.y, init.position.z] } : pt;
|
||||
}) as [Point, Point];
|
||||
|
||||
return {
|
||||
type: "Zone" as const,
|
||||
lineData: { ...originalZone, points: updatedPoints },
|
||||
newData: zone,
|
||||
timeStamp: new Date().toISOString(),
|
||||
};
|
||||
});
|
||||
|
||||
processedZones.push(...updatedZonesData);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
if (processedWalls.length > 0) {
|
||||
const wallMap = new Map<string, UndoRedo2DDataTypeSchema[]>();
|
||||
|
||||
for (const wall of processedWalls) {
|
||||
if (wall.type !== 'Wall' || !wall.lineData.wallUuid) continue;
|
||||
const uuid = wall.lineData.wallUuid;
|
||||
if (!wallMap.has(uuid)) wallMap.set(uuid, []);
|
||||
wallMap.get(uuid)!.push(wall);
|
||||
}
|
||||
|
||||
wallMap.forEach((actions, uuid) => {
|
||||
const hasUpdate = actions.some(action => 'newData' in action);
|
||||
if (hasUpdate) {
|
||||
const wallData = getWallById(uuid);
|
||||
if (wallData) {
|
||||
undoPoints.push({
|
||||
type: 'Wall',
|
||||
lineData: actions[0].lineData as Wall,
|
||||
newData: wallData as Wall,
|
||||
timeStamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (processedAisles.length > 0) {
|
||||
const aisleMap = new Map<string, UndoRedo2DDataTypeSchema[]>();
|
||||
|
||||
for (const aisle of processedAisles) {
|
||||
if (aisle.type !== 'Aisle' || !aisle.lineData.aisleUuid) continue;
|
||||
const uuid = aisle.lineData.aisleUuid;
|
||||
if (!aisleMap.has(uuid)) aisleMap.set(uuid, []);
|
||||
aisleMap.get(uuid)!.push(aisle);
|
||||
}
|
||||
|
||||
aisleMap.forEach((actions, uuid) => {
|
||||
const hasUpdate = actions.some(action => 'newData' in action);
|
||||
if (hasUpdate) {
|
||||
const aisleData = getAisleById(uuid);
|
||||
if (aisleData) {
|
||||
undoPoints.push({
|
||||
type: 'Aisle',
|
||||
lineData: actions[0].lineData as Aisle,
|
||||
newData: aisleData as Aisle,
|
||||
timeStamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (processedFloors.length > 0) {
|
||||
const floorMap = new Map<string, UndoRedo2DDataTypeSchema[]>();
|
||||
|
||||
for (const floor of processedFloors) {
|
||||
if (floor.type !== 'Floor' || !floor.lineData.floorUuid) continue;
|
||||
const uuid = floor.lineData.floorUuid;
|
||||
if (!floorMap.has(uuid)) {
|
||||
floorMap.set(uuid, []);
|
||||
}
|
||||
floorMap.get(uuid)!.push(floor);
|
||||
}
|
||||
|
||||
floorMap.forEach((actions, uuid) => {
|
||||
const hasUpdate = actions.some(action => 'newData' in action);
|
||||
if (hasUpdate) {
|
||||
const floorData = getFloorById(uuid);
|
||||
if (floorData) {
|
||||
undoPoints.push({
|
||||
type: 'Floor',
|
||||
lineData: actions[0].lineData as Floor,
|
||||
newData: floorData as Floor,
|
||||
timeStamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (processedZones.length > 0) {
|
||||
const zoneMap = new Map<string, UndoRedo2DDataTypeSchema[]>();
|
||||
|
||||
for (const zone of processedZones) {
|
||||
if (zone.type !== 'Zone' || !zone.lineData.zoneUuid) continue;
|
||||
const uuid = zone.lineData.zoneUuid;
|
||||
if (!zoneMap.has(uuid)) {
|
||||
zoneMap.set(uuid, []);
|
||||
}
|
||||
zoneMap.get(uuid)!.push(zone);
|
||||
}
|
||||
|
||||
zoneMap.forEach((actions, uuid) => {
|
||||
const hasUpdate = actions.some(action => 'newData' in action);
|
||||
if (hasUpdate) {
|
||||
const zoneData = getZoneById(uuid);
|
||||
if (zoneData) {
|
||||
undoPoints.push({
|
||||
type: 'Zone',
|
||||
lineData: actions[0].lineData as Zone,
|
||||
newData: zoneData as Zone,
|
||||
timeStamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (undoPoints.length > 0) {
|
||||
push2D({
|
||||
type: 'Draw',
|
||||
actions: [
|
||||
{
|
||||
actionType: 'Lines-Update',
|
||||
points: undoPoints
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}, 0);
|
||||
|
||||
echo.success("Object moved!");
|
||||
|
||||
clearSelection();
|
||||
};
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import MoveControls2D from "./moveControls2D";
|
||||
// import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi";
|
||||
|
||||
const SelectionControls2D: React.FC = () => {
|
||||
const { camera, controls, gl, scene, raycaster, pointer } = useThree();
|
||||
const { camera, controls, gl, scene, pointer } = useThree();
|
||||
const { toggleView } = useToggleView();
|
||||
const { selectedPoints, setSelectedPoints, clearSelectedPoints } = useSelectedPoints();
|
||||
const [movedObjects, setMovedObjects] = useState<THREE.Object3D[]>([]);
|
||||
@@ -38,11 +38,12 @@ const SelectionControls2D: React.FC = () => {
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
const { projectId } = useParams();
|
||||
const { hoveredLine, hoveredPoint } = useBuilderStore();
|
||||
const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext();
|
||||
const { aisleStore, wallStore, floorStore, zoneStore, undoRedo2DStore } = useSceneContext();
|
||||
const { push2D } = undoRedo2DStore();
|
||||
const { removePoint: removeAislePoint } = aisleStore();
|
||||
const { removePoint: removeWallPoint } = wallStore();
|
||||
const { removePoint: removeFloorPoint } = floorStore();
|
||||
const { removePoint: removeZonePoint } = zoneStore();
|
||||
const { removePoint: removeFloorPoint, getFloorsByPointId, getFloorById } = floorStore();
|
||||
const { removePoint: removeZonePoint, getZonesByPointId, getZoneById } = zoneStore();
|
||||
|
||||
const isDragging = useRef(false);
|
||||
const isLeftMouseDown = useRef(false);
|
||||
@@ -223,6 +224,13 @@ const SelectionControls2D: React.FC = () => {
|
||||
const deleteSelection = () => {
|
||||
if (selectedPoints.length > 0 && duplicatedObjects.length === 0) {
|
||||
|
||||
const deletedPoints: UndoRedo2DDataTypeSchema[] = [];
|
||||
const updatedPoints: UndoRedo2DDataTypeSchema[] = [];
|
||||
const processedAisles: UndoRedo2DDataTypeSchema[] = [];
|
||||
const processedWalls: UndoRedo2DDataTypeSchema[] = [];
|
||||
const processedFloors: UndoRedo2DDataTypeSchema[] = [];
|
||||
const processedZones: UndoRedo2DDataTypeSchema[] = [];
|
||||
|
||||
selectedPoints.forEach((selectedPoint) => {
|
||||
if (selectedPoint.userData.pointUuid) {
|
||||
const point: Point = selectedPoint.userData as Point;
|
||||
@@ -249,6 +257,14 @@ const SelectionControls2D: React.FC = () => {
|
||||
socket.emit('v1:model-aisle:delete', data);
|
||||
}
|
||||
});
|
||||
|
||||
const removedAislesData = removedAisles.map((aisle) => ({
|
||||
type: "Aisle" as const,
|
||||
lineData: aisle,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
processedAisles.push(...removedAislesData);
|
||||
}
|
||||
}
|
||||
if (point.pointType === 'Wall') {
|
||||
@@ -274,9 +290,18 @@ const SelectionControls2D: React.FC = () => {
|
||||
socket.emit('v1:model-Wall:delete', data);
|
||||
}
|
||||
});
|
||||
|
||||
const removedWallsData = removedWalls.map((wall) => ({
|
||||
type: "Wall" as const,
|
||||
lineData: wall,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
processedWalls.push(...removedWallsData);
|
||||
}
|
||||
}
|
||||
if (point.pointType === 'Floor') {
|
||||
const Floors = getFloorsByPointId(point.pointUuid);
|
||||
const { removedFloors, updatedFloors } = removeFloorPoint(point.pointUuid);
|
||||
if (removedFloors.length > 0) {
|
||||
removedFloors.forEach(floor => {
|
||||
@@ -299,6 +324,14 @@ const SelectionControls2D: React.FC = () => {
|
||||
socket.emit('v1:model-Floor:delete', data);
|
||||
}
|
||||
});
|
||||
|
||||
const removedFloorsData = removedFloors.map((floor) => ({
|
||||
type: "Floor" as const,
|
||||
lineData: floor,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
processedFloors.push(...removedFloorsData);
|
||||
}
|
||||
if (updatedFloors.length > 0) {
|
||||
updatedFloors.forEach(floor => {
|
||||
@@ -321,9 +354,19 @@ const SelectionControls2D: React.FC = () => {
|
||||
socket.emit('v1:model-Floor:add', data);
|
||||
}
|
||||
});
|
||||
|
||||
const updatedFloorsData = updatedFloors.map((floor) => ({
|
||||
type: "Floor" as const,
|
||||
lineData: Floors.find(f => f.floorUuid === floor.floorUuid) || floor,
|
||||
newData: floor,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
processedFloors.push(...updatedFloorsData);
|
||||
}
|
||||
}
|
||||
if (point.pointType === 'Zone') {
|
||||
const Zones = getZonesByPointId(point.pointUuid);
|
||||
const { removedZones, updatedZones } = removeZonePoint(point.pointUuid);
|
||||
if (removedZones.length > 0) {
|
||||
removedZones.forEach(zone => {
|
||||
@@ -346,6 +389,14 @@ const SelectionControls2D: React.FC = () => {
|
||||
socket.emit('v1:zone:delete', data);
|
||||
}
|
||||
});
|
||||
|
||||
const removedZonesData = removedZones.map((zone) => ({
|
||||
type: "Zone" as const,
|
||||
lineData: zone,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
processedZones.push(...removedZonesData);
|
||||
}
|
||||
if (updatedZones.length > 0) {
|
||||
updatedZones.forEach(zone => {
|
||||
@@ -368,11 +419,173 @@ const SelectionControls2D: React.FC = () => {
|
||||
socket.emit('v1:zone:add', data);
|
||||
}
|
||||
});
|
||||
|
||||
const updatedZonesData = updatedZones.map((zone) => ({
|
||||
type: "Zone" as const,
|
||||
lineData: Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone,
|
||||
newData: zone,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
processedZones.push(...updatedZonesData);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
if (processedWalls.length > 0) {
|
||||
const wallMap = new Map<string, UndoRedo2DDataTypeSchema[]>();
|
||||
|
||||
for (const wall of processedWalls) {
|
||||
if (wall.type !== 'Wall' || !wall.lineData.wallUuid) continue;
|
||||
const uuid = wall.lineData.wallUuid;
|
||||
if (!wallMap.has(uuid)) wallMap.set(uuid, []);
|
||||
wallMap.get(uuid)!.push(wall);
|
||||
}
|
||||
|
||||
wallMap.forEach((actions) => {
|
||||
const hasDelete = actions.some(action => !('newData' in action));
|
||||
if (hasDelete) {
|
||||
deletedPoints.push({
|
||||
type: 'Wall',
|
||||
lineData: actions[0].lineData as Wall,
|
||||
timeStamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (processedAisles.length > 0) {
|
||||
const aisleMap = new Map<string, UndoRedo2DDataTypeSchema[]>();
|
||||
|
||||
for (const aisle of processedAisles) {
|
||||
if (aisle.type !== 'Aisle' || !aisle.lineData.aisleUuid) continue;
|
||||
const uuid = aisle.lineData.aisleUuid;
|
||||
if (!aisleMap.has(uuid)) aisleMap.set(uuid, []);
|
||||
aisleMap.get(uuid)!.push(aisle);
|
||||
}
|
||||
|
||||
aisleMap.forEach((actions) => {
|
||||
const hasDelete = actions.some(action => !('newData' in action));
|
||||
if (hasDelete) {
|
||||
deletedPoints.push({
|
||||
type: 'Aisle',
|
||||
lineData: actions[0].lineData as Aisle,
|
||||
timeStamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (processedFloors.length > 0) {
|
||||
const floorMap = new Map<string, UndoRedo2DDataTypeSchema[]>();
|
||||
|
||||
for (const floor of processedFloors) {
|
||||
if (floor.type !== 'Floor' || !floor.lineData.floorUuid) return;
|
||||
const uuid = floor.lineData.floorUuid;
|
||||
if (!floorMap.has(uuid)) {
|
||||
floorMap.set(uuid, []);
|
||||
}
|
||||
floorMap.get(uuid)!.push(floor);
|
||||
}
|
||||
|
||||
floorMap.forEach((actions, uuid) => {
|
||||
const hasDelete = actions.some(action => !('newData' in action));
|
||||
const hasUpdate = actions.some(action => 'newData' in action);
|
||||
|
||||
if (hasDelete) {
|
||||
deletedPoints.push({
|
||||
type: 'Floor',
|
||||
lineData: actions[0].lineData as Floor,
|
||||
timeStamp: new Date().toISOString()
|
||||
});
|
||||
} else if (!hasDelete && hasUpdate) {
|
||||
const floorData = getFloorById(uuid);
|
||||
if (floorData) {
|
||||
updatedPoints.push({
|
||||
type: 'Floor',
|
||||
lineData: actions[0].lineData as Floor,
|
||||
newData: floorData as Floor,
|
||||
timeStamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (processedZones.length > 0) {
|
||||
const zoneMap = new Map<string, UndoRedo2DDataTypeSchema[]>();
|
||||
|
||||
for (const zone of processedZones) {
|
||||
if (zone.type !== 'Zone' || !zone.lineData.zoneUuid) return;
|
||||
const uuid = zone.lineData.zoneUuid;
|
||||
if (!zoneMap.has(uuid)) {
|
||||
zoneMap.set(uuid, []);
|
||||
}
|
||||
zoneMap.get(uuid)!.push(zone);
|
||||
}
|
||||
|
||||
zoneMap.forEach((actions, uuid) => {
|
||||
const hasDelete = actions.some(action => !('newData' in action));
|
||||
const hasUpdate = actions.some(action => 'newData' in action);
|
||||
|
||||
if (hasDelete) {
|
||||
deletedPoints.push({
|
||||
type: 'Zone',
|
||||
lineData: actions[0].lineData as Zone,
|
||||
timeStamp: new Date().toISOString()
|
||||
});
|
||||
} else if (!hasDelete && hasUpdate) {
|
||||
const zoneData = getZoneById(uuid);
|
||||
if (zoneData) {
|
||||
updatedPoints.push({
|
||||
type: 'Zone',
|
||||
lineData: actions[0].lineData as Zone,
|
||||
newData: zoneData as Zone,
|
||||
timeStamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (deletedPoints.length > 0 && updatedPoints.length > 0) {
|
||||
push2D({
|
||||
type: 'Draw',
|
||||
actions: [
|
||||
{
|
||||
actionType: 'Lines-Delete',
|
||||
points: deletedPoints
|
||||
},
|
||||
{
|
||||
actionType: 'Lines-Update',
|
||||
points: updatedPoints
|
||||
}
|
||||
]
|
||||
});
|
||||
} else if (deletedPoints.length > 0 && updatedPoints.length === 0) {
|
||||
push2D({
|
||||
type: 'Draw',
|
||||
actions: [
|
||||
{
|
||||
actionType: 'Lines-Delete',
|
||||
points: deletedPoints
|
||||
}
|
||||
]
|
||||
});
|
||||
} else if (updatedPoints.length > 0 && deletedPoints.length === 0) {
|
||||
push2D({
|
||||
type: 'Draw',
|
||||
actions: [
|
||||
{
|
||||
actionType: 'Lines-Update',
|
||||
points: updatedPoints
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
echo.success("Selected points removed!");
|
||||
clearSelection();
|
||||
@@ -380,6 +593,7 @@ const SelectionControls2D: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
<MoveControls2D movedObjects={movedObjects} setMovedObjects={setMovedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} />
|
||||
|
||||
</>
|
||||
|
||||
@@ -333,6 +333,7 @@ const SelectionControls3D: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
<MoveControls3D movedObjects={movedObjects} setMovedObjects={setMovedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} boundingBoxRef={boundingBoxRef} />
|
||||
|
||||
<RotateControls3D rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} movedObjects={movedObjects} setMovedObjects={setMovedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} />
|
||||
@@ -340,6 +341,7 @@ const SelectionControls3D: React.FC = () => {
|
||||
<DuplicationControls3D duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} setpastedObjects={setpastedObjects} movedObjects={movedObjects} setMovedObjects={setMovedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} boundingBoxRef={boundingBoxRef} />
|
||||
|
||||
<CopyPasteControls3D copiedObjects={copiedObjects} setCopiedObjects={setCopiedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} setDuplicatedObjects={setDuplicatedObjects} movedObjects={movedObjects} setMovedObjects={setMovedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} boundingBoxRef={boundingBoxRef} />
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -296,7 +296,7 @@ function useRedoHandler() {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
aisleData,
|
||||
...aisleData,
|
||||
projectId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
userId,
|
||||
@@ -338,7 +338,7 @@ function useRedoHandler() {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
aisleData: updatedData,
|
||||
...updatedData,
|
||||
projectId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
userId,
|
||||
|
||||
@@ -297,7 +297,7 @@ function useUndoHandler() {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
aisleData,
|
||||
...aisleData,
|
||||
projectId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
userId,
|
||||
@@ -339,7 +339,7 @@ function useUndoHandler() {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
aisleData: updatedData,
|
||||
...updatedData,
|
||||
projectId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
userId,
|
||||
|
||||
@@ -6,9 +6,9 @@ import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||
|
||||
export default function Sun() {
|
||||
const savedTheme: string | null = localStorage.getItem("theme");
|
||||
const { elevation, setElevation } = useElevation();
|
||||
const { sunPosition, setSunPosition } = useSunPosition();
|
||||
const { azimuth, setAzimuth } = useAzimuth();
|
||||
const { elevation } = useElevation();
|
||||
const { setSunPosition } = useSunPosition();
|
||||
const { azimuth } = useAzimuth();
|
||||
const [turbidity, setTurbidity] = useState(CONSTANTS.skyConfig.defaultTurbidity);
|
||||
const sunPositionRef = useRef(new THREE.Vector3(0, 0, 0));
|
||||
const [_, forceUpdate] = useState(0);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { EffectComposer, N8AO, Outline } from "@react-three/postprocessing";
|
||||
import { DepthOfField, Bloom, EffectComposer, N8AO, Outline } from "@react-three/postprocessing";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import { BlendFunction } from "postprocessing";
|
||||
import {
|
||||
useDeletableFloorItem,
|
||||
@@ -11,6 +12,7 @@ import { useEffect } from "react";
|
||||
import { useBuilderStore } from "../../../store/builder/useBuilderStore";
|
||||
|
||||
export default function PostProcessing() {
|
||||
const { scene } = useThree();
|
||||
const { selectedPoints } = useSelectedPoints();
|
||||
const { deletableFloorItem } = useDeletableFloorItem();
|
||||
const { selectedWallItem } = useSelectedWallItem();
|
||||
@@ -77,6 +79,17 @@ export default function PostProcessing() {
|
||||
denoiseRadius={6}
|
||||
denoiseSamples={16}
|
||||
/>
|
||||
{/* <DepthOfField
|
||||
focusDistance={0}
|
||||
focalLength={0.15}
|
||||
bokehScale={2}
|
||||
/> */}
|
||||
{/* <Bloom
|
||||
intensity={0.1}
|
||||
luminanceThreshold={0.9}
|
||||
luminanceSmoothing={0.025}
|
||||
mipmapBlur={false}
|
||||
/> */}
|
||||
{selectedWallAsset && (
|
||||
<Outline
|
||||
selection={flattenChildren(selectedWallAsset.children)}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Canvas } from "@react-three/fiber";
|
||||
import { KeyboardControls } from "@react-three/drei";
|
||||
import { Physics } from "@react-three/rapier";
|
||||
import { Color, SRGBColorSpace } from "three";
|
||||
import { KeyboardControls, Stats } from "@react-three/drei";
|
||||
import { useSceneContext } from "./sceneContext";
|
||||
|
||||
import Builder from "../builder/builder";
|
||||
@@ -14,8 +16,6 @@ import { useParams } from "react-router-dom";
|
||||
import { getAllProjects } from "../../services/dashboard/getAllProjects";
|
||||
import { getUserData } from "../../functions/getUserData";
|
||||
import { useLoadingProgress, useSocketStore } from "../../store/builder/store";
|
||||
import { Color } from "three";
|
||||
import { Physics } from "@react-three/rapier";
|
||||
|
||||
export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Comparison Layout' }) {
|
||||
const map = useMemo(() => [
|
||||
@@ -54,6 +54,7 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
// eslint-disable-next-line
|
||||
}, [activeModule, assets, loadingProgress])
|
||||
|
||||
return (
|
||||
@@ -66,10 +67,11 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
performance={{ min: 0.9, max: 1.0 }}
|
||||
onCreated={(e) => {
|
||||
e.scene.background = layout === 'Main Layout' ? null : new Color(0x19191d);
|
||||
}}
|
||||
gl={{ powerPreference: "high-performance", antialias: true, preserveDrawingBuffer: true }}
|
||||
gl={{ outputColorSpace: SRGBColorSpace, powerPreference: "high-performance", antialias: true, preserveDrawingBuffer: true }}
|
||||
>
|
||||
<Setup />
|
||||
<Collaboration />
|
||||
@@ -81,7 +83,8 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co
|
||||
<PhysicsSimulator />
|
||||
</Physics>
|
||||
<Visualization />
|
||||
<Stats />
|
||||
</Canvas>
|
||||
</KeyboardControls>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import Sun from '../environment/sky'
|
||||
import Shadows from '../environment/shadow'
|
||||
import PostProcessing from '../postProcessing/postProcessing'
|
||||
import Controls from '../controls/controls';
|
||||
import { Environment } from '@react-three/drei'
|
||||
import { AdaptiveDpr, AdaptiveEvents, Environment } from '@react-three/drei'
|
||||
|
||||
import background from "../../../assets/textures/hdr/mudroadpuresky2k.hdr";
|
||||
|
||||
@@ -24,8 +24,12 @@ function Setup() {
|
||||
<Environment files={background} environmentIntensity={1.5} />
|
||||
|
||||
{/* <SecondaryCamera /> */}
|
||||
|
||||
<AdaptiveEvents />
|
||||
|
||||
<AdaptiveDpr pixelated />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Setup
|
||||
export default Setup;
|
||||
|
||||
@@ -55,7 +55,6 @@ const ArmBotUI = () => {
|
||||
})
|
||||
}
|
||||
|
||||
// Fetch and setup selected ArmBot data
|
||||
useEffect(() => {
|
||||
if (selectedEventSphere) {
|
||||
const selectedArmBot = getEventByModelUuid(selectedProduct.productUuid, selectedEventSphere.userData.modelUuid);
|
||||
@@ -86,13 +85,11 @@ const ArmBotUI = () => {
|
||||
const modelData = getEventByModelUuid(selectedProduct.productUuid, modelUuid);
|
||||
|
||||
if (modelData?.type === "roboticArm") {
|
||||
const baseX = modelData.point.position?.[0] || 0;
|
||||
const baseY = modelData.point.position?.[1] || 0;;
|
||||
const baseZ = modelData.point.position?.[2] || 0;
|
||||
const baseY = modelData.point.position?.[1] || 0;
|
||||
return {
|
||||
pick: [baseX, baseY, baseZ + 0.5],
|
||||
drop: [baseX, baseY, baseZ - 0.5],
|
||||
default: [baseX, baseY, baseZ],
|
||||
pick: [0, baseY, 0 + 0.5],
|
||||
drop: [0, baseY, 0 - 0.5],
|
||||
default: [0, baseY, 0],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -225,7 +222,7 @@ const ArmBotUI = () => {
|
||||
</React.Fragment>
|
||||
);
|
||||
} else {
|
||||
return null; // important! must return something
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</>
|
||||
|
||||
@@ -40,7 +40,6 @@ export default function PolygonGenerator({
|
||||
aisle.type.aisleType === "arc-aisle"
|
||||
)
|
||||
|
||||
|
||||
arcAndCircleResult.forEach((arc) => {
|
||||
const arcGroup = scene.getObjectByProperty("uuid", arc.aisleUuid);
|
||||
if (!arcGroup) return;
|
||||
@@ -60,7 +59,6 @@ export default function PolygonGenerator({
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
const wallPoints: THREE.Vector3[][] = walls
|
||||
.map((wall) =>
|
||||
wall.points.map((pt) => new THREE.Vector3(pt.position[0], pt.position[1], pt.position[2]))
|
||||
|
||||
@@ -74,10 +74,10 @@
|
||||
border-radius: #{$border-radius-large};
|
||||
outline: 1px solid var(--border-color);
|
||||
}
|
||||
.upload-btn{
|
||||
.upload-btn {
|
||||
padding: 4px 16px !important;
|
||||
}
|
||||
.generate-walls-btn{
|
||||
.generate-walls-btn {
|
||||
padding: 4px 16px;
|
||||
@include flex-center;
|
||||
gap: 4px;
|
||||
@@ -117,9 +117,20 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
.label-toogler.bottom{
|
||||
.label-toogler.bottom {
|
||||
bottom: 32%;
|
||||
}
|
||||
|
||||
.scene-performance-stats {
|
||||
bottom: 36px !important;
|
||||
left: 12px !important;
|
||||
border-radius: 6px;
|
||||
backdrop-filter: blur(4px);
|
||||
svg {
|
||||
display: none;
|
||||
}
|
||||
.c-jiwtRJ{
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ export const thirdPersonControls: ThirdPersonControls = {
|
||||
polarRotateSpeed: 1, // Speed of rotation around the polar axis
|
||||
truckSpeed: 2, // Speed of truck movement
|
||||
maxDistance: 100, // Maximum distance from the target
|
||||
maxPolarAngle: Math.PI / 2 - 0.05, // Maximum polar angle
|
||||
maxPolarAngle: Math.PI / 2, // Maximum polar angle
|
||||
minZoom: 6, // Minimum zoom level
|
||||
maxZoom: 100, // Maximum zoom level
|
||||
targetOffset: 20, // Offset of the target from the camera
|
||||
|
||||
@@ -245,8 +245,12 @@ const KeyPressListener: React.FC = () => {
|
||||
if (keyCombination === "Ctrl+Shift+?") {
|
||||
setShowShortcuts(!showShortcuts);
|
||||
}
|
||||
|
||||
// if (keyCombination === "Ctrl+Shift+P") {
|
||||
// pref
|
||||
// }
|
||||
|
||||
if (keyCombination === "U") {
|
||||
// console.log("viewSceneLabels: ", viewSceneLabels);
|
||||
setViewSceneLabels((prev) => !prev);
|
||||
}
|
||||
|
||||
|
||||