feat: implement duplicate scene functionality with builder, controls, and post-processing components
This commit is contained in:
parent
ae20cf1437
commit
d0538ccfae
|
@ -82,7 +82,7 @@ function ComparisonScene() {
|
|||
{comparisonProduct && !isPlaying &&
|
||||
<div className="initial-selectLayout-wrapper">
|
||||
<RegularDropDown
|
||||
header={selectedProduct.productName}
|
||||
header={'Product 1'}
|
||||
options={products.map((l) => l.productName)} // Pass layout names as options
|
||||
onSelect={handleSelectLayout}
|
||||
search={false}
|
||||
|
|
|
@ -14,10 +14,10 @@ import { useProductStore } from "../../../store/simulation/useProductStore";
|
|||
import Scene from "../../../modules/scene/scene";
|
||||
import { useComparisonProduct } from "../../../store/simulation/useSimulationStore";
|
||||
import { usePauseButtonStore, usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
||||
import DuplicateScene from "../../../modules/duplicate/sceneDuplicate";
|
||||
|
||||
const CompareLayOut = () => {
|
||||
const { comparisonProduct, setComparisonProduct, clearComparisonProduct } =
|
||||
useComparisonProduct();
|
||||
const { comparisonProduct, setComparisonProduct, clearComparisonProduct } = useComparisonProduct();
|
||||
const { products } = useProductStore();
|
||||
const { setLoadingProgress } = useLoadingProgress();
|
||||
const [width, setWidth] = useState("50vw");
|
||||
|
@ -127,7 +127,7 @@ const CompareLayOut = () => {
|
|||
ref={wrapperRef}
|
||||
style={{ width }}
|
||||
>
|
||||
{loadingProgress == 0 && (
|
||||
{loadingProgress == 0 && comparisonProduct && (
|
||||
<button
|
||||
title="resize-canvas"
|
||||
id="compare-resize-slider-btn"
|
||||
|
@ -141,7 +141,8 @@ const CompareLayOut = () => {
|
|||
{comparisonProduct && (
|
||||
<div className="compare-layout-canvas-container">
|
||||
<Suspense fallback={null}>
|
||||
<Scene layout="Comparison Layout" />
|
||||
{/* <Scene layout="Comparison Layout" /> */}
|
||||
<DuplicateScene />
|
||||
</Suspense>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -26,8 +26,6 @@ function AisleInstances() {
|
|||
return points;
|
||||
}, [aisles]);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{toggleView &&
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
////////// Three and React Three Fiber Imports //////////
|
||||
|
||||
import * as THREE from "three";
|
||||
import { useRef } from "react";
|
||||
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
import Ground from "../../scene/environment/ground";
|
||||
import { Bvh } from "@react-three/drei";
|
||||
import AssetsGroupDuplicate from "./duplicateAsset/assetsGroupDuplicate";
|
||||
import FloorGroupDuplicate from "./duplicateFloor/floorGroupDuplicate";
|
||||
import WallsDuplicate from "./duplicateWall/wallsDuplicate";
|
||||
import ZoneDuplicate from "./duplicateZone/zoneDuplicate";
|
||||
import AislesDuplicate from "./duplicateAisle/aislesDuplicate";
|
||||
|
||||
export default function BuilderDuplicate({ projectId }: { projectId: string }) {
|
||||
const plane = useRef<THREE.Mesh>(null); // Reference for a plane object for raycaster reference.
|
||||
const grid = useRef() as any; // Reference for a grid object for raycaster reference.
|
||||
const lines = useRef([]) as Types.RefLines; // Reference for lines which stores all the lines that are ever drawn.
|
||||
const floorGroup = useRef() as Types.RefGroup; // Reference to the THREE.Group that has the roofs and the floors.
|
||||
|
||||
////////// Return //////////
|
||||
|
||||
return (
|
||||
<>
|
||||
<Ground grid={grid} plane={plane} />
|
||||
|
||||
<WallsDuplicate
|
||||
lines={lines}
|
||||
projectId={projectId}
|
||||
/>
|
||||
|
||||
<Bvh firstHitOnly>
|
||||
|
||||
<FloorGroupDuplicate
|
||||
floorGroup={floorGroup}
|
||||
lines={lines}
|
||||
projectId={projectId}
|
||||
/>
|
||||
|
||||
<ZoneDuplicate projectId={projectId} />
|
||||
|
||||
<AssetsGroupDuplicate projectId={projectId} />
|
||||
|
||||
<AislesDuplicate projectId={projectId} />
|
||||
</Bvh>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { getAisleApi } from '../../../../services/factoryBuilder/aisle/getAisleApi';
|
||||
import AisleInstance from '../../../builder/aisle/Instances/instance/aisleInstance';
|
||||
|
||||
function AislesDuplicate({ projectId }: { projectId: string }) {
|
||||
const [aisles, setAisles] = useState<Aisles>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAisle = async () => {
|
||||
if (projectId) {
|
||||
const aisles = await getAisleApi(projectId);
|
||||
setAisles(aisles);
|
||||
}
|
||||
}
|
||||
fetchAisle()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<group name='Aisles-Group'>
|
||||
{aisles.map((aisle) => (
|
||||
<AisleInstance aisle={aisle} key={aisle.aisleUuid} />
|
||||
))}
|
||||
</group>
|
||||
)
|
||||
}
|
||||
|
||||
export default AislesDuplicate
|
|
@ -0,0 +1,123 @@
|
|||
import * as THREE from "three"
|
||||
import { useEffect, useState } from 'react'
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
|
||||
import ModelDuplicate from "./modelDuplicate";
|
||||
import { useLoadingProgress } from "../../../../store/builder/store";
|
||||
import { getFloorAssets } from "../../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi";
|
||||
import { FloorItems } from "../../../../types/world/worldTypes";
|
||||
|
||||
const gltfLoaderWorker = new Worker(
|
||||
new URL(
|
||||
"../../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js",
|
||||
import.meta.url
|
||||
)
|
||||
);
|
||||
|
||||
function AssetsGroupDuplicate({ projectId }: { projectId: string }) {
|
||||
const { setLoadingProgress } = useLoadingProgress();
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
const [assetsDuplicates, setAssetsDuplicates] = useState<Assets>([]);
|
||||
|
||||
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);
|
||||
|
||||
useEffect(() => {
|
||||
let totalAssets = 0;
|
||||
let loadedAssets = 0;
|
||||
|
||||
const updateLoadingProgress = (progress: number) => {
|
||||
if (progress < 100) {
|
||||
setLoadingProgress(progress);
|
||||
} else if (progress === 100) {
|
||||
setTimeout(() => {
|
||||
setLoadingProgress(100);
|
||||
setTimeout(() => {
|
||||
setLoadingProgress(0);
|
||||
}, 1500);
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
getFloorAssets(organization, projectId).then((data) => {
|
||||
if (data.length > 0) {
|
||||
const uniqueItems = (data as FloorItems).filter((item, index, self) => index === self.findIndex((t) => t.assetId === item.assetId));
|
||||
totalAssets = uniqueItems.length;
|
||||
if (totalAssets === 0) {
|
||||
updateLoadingProgress(100);
|
||||
return;
|
||||
}
|
||||
gltfLoaderWorker.postMessage({ floorItems: uniqueItems });
|
||||
} else {
|
||||
gltfLoaderWorker.postMessage({ floorItems: [] });
|
||||
updateLoadingProgress(100);
|
||||
}
|
||||
});
|
||||
|
||||
gltfLoaderWorker.onmessage = async (event) => {
|
||||
if (event.data.message === "gltfLoaded" && event.data.modelBlob) {
|
||||
const blobUrl = URL.createObjectURL(event.data.modelBlob);
|
||||
loader.load(blobUrl, (gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(event.data.modelID, gltf);
|
||||
|
||||
loadedAssets++;
|
||||
const progress = Math.round((loadedAssets / totalAssets) * 100);
|
||||
updateLoadingProgress(progress);
|
||||
|
||||
if (loadedAssets === totalAssets) {
|
||||
getFloorAssets(organization, projectId).then((data: FloorItems) => {
|
||||
const newAssets: Assets = data.map((item) => {
|
||||
if (item.eventData) {
|
||||
return {
|
||||
modelUuid: item.modelUuid,
|
||||
modelName: item.modelName,
|
||||
assetId: item.assetId,
|
||||
position: item.position,
|
||||
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
isLocked: item.isLocked,
|
||||
isCollidable: false,
|
||||
isVisible: item.isVisible,
|
||||
opacity: 1,
|
||||
eventData: item.eventData
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
modelUuid: item.modelUuid,
|
||||
modelName: item.modelName,
|
||||
assetId: item.assetId,
|
||||
position: item.position,
|
||||
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
isLocked: item.isLocked,
|
||||
isCollidable: false,
|
||||
isVisible: item.isVisible,
|
||||
opacity: 1,
|
||||
};
|
||||
}
|
||||
});
|
||||
setAssetsDuplicates(newAssets);
|
||||
});
|
||||
updateLoadingProgress(100);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{assetsDuplicates.map((asset) =>
|
||||
<ModelDuplicate key={`${asset.modelUuid}_Duplicate`} asset={asset} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AssetsGroupDuplicate;
|
|
@ -0,0 +1,123 @@
|
|||
import * as THREE from 'three';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import { useRenderDistance } from '../../../../store/builder/store';
|
||||
import { AssetBoundingBox } from '../../../builder/asset/functions/assetBoundingBox';
|
||||
import { retrieveGLTF, storeGLTF } from '../../../../utils/indexDB/idbUtils';
|
||||
|
||||
function ModelDuplicate({ asset }: { readonly asset: Asset }) {
|
||||
const { camera } = useThree();
|
||||
const { renderDistance } = useRenderDistance();
|
||||
const [isRendered, setIsRendered] = useState(false);
|
||||
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
const [gltfScene, setGltfScene] = useState<GLTF["scene"] | null>(null);
|
||||
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
|
||||
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 {
|
||||
// Check Cache
|
||||
const assetId = asset.assetId;
|
||||
const cachedModel = THREE.Cache.get(assetId);
|
||||
if (cachedModel) {
|
||||
setGltfScene(cachedModel.scene.clone());
|
||||
calculateBoundingBox(cachedModel.scene);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check IndexedDB
|
||||
const indexedDBModel = await retrieveGLTF(assetId);
|
||||
if (indexedDBModel) {
|
||||
const blobUrl = URL.createObjectURL(indexedDBModel);
|
||||
loader.load(blobUrl, (gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(assetId, gltf);
|
||||
setGltfScene(gltf.scene.clone());
|
||||
calculateBoundingBox(gltf.scene);
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
echo.error(`[IndexedDB] Error loading ${asset.modelName}:`);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch from Backend
|
||||
const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${assetId}`;
|
||||
const handleBackendLoad = async (gltf: GLTF) => {
|
||||
try {
|
||||
const response = await fetch(modelUrl);
|
||||
const modelBlob = await response.blob();
|
||||
await storeGLTF(assetId, modelBlob);
|
||||
THREE.Cache.add(assetId, gltf);
|
||||
setGltfScene(gltf.scene.clone());
|
||||
calculateBoundingBox(gltf.scene);
|
||||
} catch (error) {
|
||||
console.error(`[Backend] Error storing/loading ${asset.modelName}:`, error);
|
||||
}
|
||||
};
|
||||
loader.load(
|
||||
modelUrl,
|
||||
handleBackendLoad,
|
||||
undefined,
|
||||
(error) => {
|
||||
echo.error(`[Backend] Error loading ${asset.modelName}:`);
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("Failed to load model:", asset.assetId, err);
|
||||
}
|
||||
};
|
||||
|
||||
const calculateBoundingBox = (scene: THREE.Object3D) => {
|
||||
const box = new THREE.Box3().setFromObject(scene);
|
||||
setBoundingBox(box);
|
||||
};
|
||||
|
||||
loadModel();
|
||||
|
||||
}, []);
|
||||
|
||||
useFrame(() => {
|
||||
const assetPosition = new THREE.Vector3(...asset.position);
|
||||
if (!isRendered && assetPosition.distanceTo(camera.position) <= renderDistance) {
|
||||
setIsRendered(true);
|
||||
} else if (isRendered && assetPosition.distanceTo(camera.position) > renderDistance) {
|
||||
setIsRendered(false);
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<group
|
||||
key={asset.modelUuid}
|
||||
name='Asset Model'
|
||||
ref={groupRef}
|
||||
uuid={asset.modelUuid}
|
||||
position={asset.position}
|
||||
rotation={asset.rotation}
|
||||
visible={asset.isVisible}
|
||||
userData={asset}
|
||||
>
|
||||
{gltfScene && (
|
||||
isRendered ? (
|
||||
<primitive object={gltfScene} />
|
||||
) : (
|
||||
<AssetBoundingBox boundingBox={boundingBox} />
|
||||
)
|
||||
)}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModelDuplicate;
|
|
@ -0,0 +1,47 @@
|
|||
import {
|
||||
useRoofVisibility,
|
||||
useToggleView,
|
||||
useWallVisibility,
|
||||
} from "../../../../store/builder/store";
|
||||
import { useEffect } from "react";
|
||||
import loadFloor from "../../../builder/geomentries/floors/loadFloor";
|
||||
import { getLines } from "../../../../services/factoryBuilder/lines/getLinesApi";
|
||||
import objectLinesToArray from "../../../builder/geomentries/lines/lineConvertions/objectLinesToArray";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import hideRoof from "../../../builder/geomentries/roofs/hideRoof";
|
||||
import hideWalls from "../../../builder/geomentries/walls/hideWalls";
|
||||
|
||||
const FloorGroupDuplicate = ({
|
||||
floorGroup,
|
||||
lines,
|
||||
projectId
|
||||
}: any) => {
|
||||
const { scene, camera } = useThree();
|
||||
const { toggleView } = useToggleView();
|
||||
const { roofVisibility } = useRoofVisibility();
|
||||
const { wallVisibility } = useWallVisibility();
|
||||
|
||||
useEffect(() => {
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
getLines(organization, projectId).then((data) => {
|
||||
const Lines = objectLinesToArray(data);
|
||||
|
||||
if (Lines) {
|
||||
lines.current = Lines;
|
||||
loadFloor(lines, floorGroup);
|
||||
}
|
||||
})
|
||||
}, []);
|
||||
|
||||
useFrame(() => {
|
||||
hideRoof(roofVisibility, floorGroup, camera);
|
||||
hideWalls(wallVisibility, scene, camera);
|
||||
});
|
||||
|
||||
return (
|
||||
<group ref={floorGroup} visible={!toggleView} name="floorGroup"></group>
|
||||
);
|
||||
};
|
||||
|
||||
export default FloorGroupDuplicate;
|
|
@ -0,0 +1,66 @@
|
|||
import * as THREE from "three";
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
import * as CONSTANTS from "../../../../types/world/worldConstants";
|
||||
import { Base } from "@react-three/csg";
|
||||
import { MeshDiscardMaterial } from "@react-three/drei";
|
||||
import { useEffect } from "react";
|
||||
import objectLinesToArray from "../../../builder/geomentries/lines/lineConvertions/objectLinesToArray";
|
||||
import loadWalls from "../../../builder/geomentries/walls/loadWalls";
|
||||
import texturePath from "../../../../assets/textures/floor/wall-tex.png";
|
||||
import { getLines } from "../../../../services/factoryBuilder/lines/getLinesApi";
|
||||
|
||||
const WallsMeshDuplicate = ({ projectId, walls, setWalls, lines }: any) => {
|
||||
|
||||
useEffect(() => {
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
|
||||
getLines(organization, projectId).then((data) => {
|
||||
const Lines: Types.Lines = objectLinesToArray(data);
|
||||
if (Lines) {
|
||||
lines.current = Lines;
|
||||
loadWalls(lines, setWalls);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const textureLoader = new THREE.TextureLoader();
|
||||
const wallTexture = textureLoader.load(texturePath);
|
||||
|
||||
wallTexture.wrapS = wallTexture.wrapT = THREE.RepeatWrapping;
|
||||
wallTexture.repeat.set(0.1, 0.1);
|
||||
wallTexture.colorSpace = THREE.SRGBColorSpace;
|
||||
|
||||
return (
|
||||
<>
|
||||
{walls.map((wall: Types.Wall, index: number) => (
|
||||
<mesh key={index} renderOrder={1}>
|
||||
<Base
|
||||
name={`Wall${index + 1}`}
|
||||
geometry={wall[0]}
|
||||
rotation={wall[1]}
|
||||
position={wall[2]}
|
||||
userData={{ WallType: wall[3], Layer: wall[4] }}
|
||||
>
|
||||
<meshStandardMaterial
|
||||
side={THREE.DoubleSide}
|
||||
color={CONSTANTS.wallConfig.defaultColor}
|
||||
map={wallTexture}
|
||||
/>
|
||||
</Base>
|
||||
<mesh
|
||||
castShadow
|
||||
geometry={wall[0]}
|
||||
rotation={wall[1]}
|
||||
position={wall[2]}
|
||||
name={`WallRaycastReference_${index + 1}`}
|
||||
>
|
||||
<MeshDiscardMaterial />
|
||||
</mesh>
|
||||
</mesh>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WallsMeshDuplicate;
|
|
@ -0,0 +1,29 @@
|
|||
import { Geometry } from "@react-three/csg";
|
||||
import {
|
||||
useToggleView,
|
||||
} from "../../../../store/builder/store";
|
||||
import WallsMeshDuplicate from "./wallMeshDuplicate";
|
||||
import { useState } from "react";
|
||||
|
||||
const WallsDuplicate = ({
|
||||
lines,
|
||||
projectId
|
||||
}: any) => {
|
||||
const { toggleView } = useToggleView();
|
||||
const [walls, setWalls] = useState([]);
|
||||
|
||||
return (
|
||||
<mesh
|
||||
name="Walls"
|
||||
key={walls.length}
|
||||
receiveShadow
|
||||
visible={!toggleView}
|
||||
>
|
||||
<Geometry computeVertexNormals useGroups>
|
||||
<WallsMeshDuplicate projectId={projectId} setWalls={setWalls} walls={walls} lines={lines} />
|
||||
</Geometry>
|
||||
</mesh>
|
||||
);
|
||||
};
|
||||
|
||||
export default WallsDuplicate;
|
|
@ -0,0 +1,182 @@
|
|||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Html } from "@react-three/drei";
|
||||
import * as THREE from "three";
|
||||
import {
|
||||
useToggleView,
|
||||
useZonePoints,
|
||||
} from "../../../../store/builder/store";
|
||||
import { getZonesApi } from "../../../../services/factoryBuilder/zones/getZonesApi";
|
||||
import * as CONSTANTS from "../../../../types/world/worldConstants";
|
||||
import * as turf from "@turf/turf";
|
||||
import { useSelectedZoneStore } from "../../../../store/visualization/useZoneStore";
|
||||
|
||||
const ZoneDuplicate = ({ projectId }: { projectId: string }) => {
|
||||
const [zones, setZones] = useState([]);
|
||||
const { setZonePoints } = useZonePoints();
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
const { toggleView } = useToggleView();
|
||||
|
||||
const groupsRef = useRef<any>();
|
||||
|
||||
const zoneMaterial = useMemo(
|
||||
() =>
|
||||
new THREE.ShaderMaterial({
|
||||
side: THREE.DoubleSide,
|
||||
vertexShader: `
|
||||
varying vec2 vUv;
|
||||
void main(){
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
vUv = uv;
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
varying vec2 vUv;
|
||||
uniform vec3 uOuterColor;
|
||||
void main(){
|
||||
float alpha = 1.0 - vUv.y;
|
||||
gl_FragColor = vec4(uOuterColor, alpha);
|
||||
}
|
||||
`,
|
||||
uniforms: {
|
||||
uOuterColor: { value: new THREE.Color(CONSTANTS.zoneConfig.color) },
|
||||
},
|
||||
transparent: true,
|
||||
depthWrite: false,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchZones = async () => {
|
||||
const email = localStorage.getItem("email");
|
||||
if (!email) return;
|
||||
|
||||
const organization = email.split("@")[1].split(".")[0];
|
||||
const data = await getZonesApi(organization, projectId);
|
||||
|
||||
if (data.length > 0) {
|
||||
const fetchedZones = data.map((zone: any) => ({
|
||||
zoneUuid: zone.zoneUuid,
|
||||
zoneName: zone.zoneName,
|
||||
points: zone.points,
|
||||
viewPortCenter: zone.viewPortCenter,
|
||||
viewPortposition: zone.viewPortposition,
|
||||
layer: zone.layer,
|
||||
}));
|
||||
|
||||
setZones(fetchedZones);
|
||||
|
||||
const fetchedPoints = data.flatMap((zone: any) =>
|
||||
zone.points.slice(0, 4).map((point: [number, number, number]) => new THREE.Vector3(...point))
|
||||
);
|
||||
|
||||
setZonePoints(fetchedPoints);
|
||||
}
|
||||
};
|
||||
|
||||
fetchZones();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<group ref={groupsRef} name="zoneGroup">
|
||||
<group name="zones" visible={!toggleView}>
|
||||
{zones.map((zone: any) => (
|
||||
<group
|
||||
key={zone.zoneUuid}
|
||||
name={zone.zoneName}
|
||||
visible={zone.zoneUuid === selectedZone.zoneUuid}
|
||||
>
|
||||
{zone.points
|
||||
.slice(0, -1)
|
||||
.map((point: [number, number, number], index: number) => {
|
||||
const nextPoint = zone.points[index + 1];
|
||||
|
||||
const point1 = new THREE.Vector3(point[0], point[1], point[2]);
|
||||
const point2 = new THREE.Vector3(
|
||||
nextPoint[0],
|
||||
nextPoint[1],
|
||||
nextPoint[2]
|
||||
);
|
||||
|
||||
const planeWidth = point1.distanceTo(point2);
|
||||
const planeHeight = CONSTANTS.zoneConfig.height;
|
||||
|
||||
const midpoint = new THREE.Vector3(
|
||||
(point1.x + point2.x) / 2,
|
||||
CONSTANTS.zoneConfig.height / 2 +
|
||||
(zone.layer - 1) * CONSTANTS.zoneConfig.height,
|
||||
(point1.z + point2.z) / 2
|
||||
);
|
||||
|
||||
const angle = Math.atan2(
|
||||
point2.z - point1.z,
|
||||
point2.x - point1.x
|
||||
);
|
||||
|
||||
return (
|
||||
<mesh
|
||||
key={index}
|
||||
position={midpoint}
|
||||
rotation={[0, -angle, 0]}
|
||||
>
|
||||
<planeGeometry args={[planeWidth, planeHeight]} />
|
||||
<primitive
|
||||
object={zoneMaterial.clone()}
|
||||
attach="material"
|
||||
/>
|
||||
</mesh>
|
||||
);
|
||||
})}
|
||||
{!toggleView &&
|
||||
(() => {
|
||||
const points3D = zone.points || [];
|
||||
const coords2D = points3D.map((p: any) => [p[0], p[2]]);
|
||||
|
||||
// Ensure the polygon is closed
|
||||
if (
|
||||
coords2D.length >= 4 &&
|
||||
(coords2D[0][0] !== coords2D[coords2D.length - 1][0] ||
|
||||
coords2D[0][1] !== coords2D[coords2D.length - 1][1])
|
||||
) {
|
||||
coords2D.push(coords2D[0]);
|
||||
}
|
||||
if (coords2D.length < 4) return null;
|
||||
|
||||
const polygon = turf.polygon([coords2D]);
|
||||
const center2D = turf.center(polygon).geometry.coordinates;
|
||||
|
||||
// Calculate the average Y value
|
||||
const sumY = points3D.reduce(
|
||||
(sum: number, p: any) => sum + p[1],
|
||||
0
|
||||
);
|
||||
const avgY = points3D.length > 0 ? sumY / points3D.length : 0;
|
||||
|
||||
const htmlPosition: [number, number, number] = [
|
||||
center2D[0],
|
||||
avgY + (CONSTANTS.zoneConfig.height || 0) + 1.5,
|
||||
center2D[1],
|
||||
];
|
||||
|
||||
return (
|
||||
<Html
|
||||
// data
|
||||
key={zone.zoneUuid}
|
||||
position={htmlPosition}
|
||||
// class
|
||||
className="zone-name-wrapper"
|
||||
// others
|
||||
center
|
||||
>
|
||||
<div className="zone-name">{zone.zoneName}</div>
|
||||
</Html>
|
||||
);
|
||||
})()}
|
||||
</group>
|
||||
))}
|
||||
</group>
|
||||
</group>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZoneDuplicate;
|
|
@ -0,0 +1,54 @@
|
|||
import { CameraControls } from "@react-three/drei";
|
||||
import { useRef, useEffect } from "react";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||
|
||||
import { useToggleView } from "../../../store/builder/store";
|
||||
import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi";
|
||||
|
||||
export default function ControlsDuplicate({ projectId }: { projectId: string }) {
|
||||
const controlsRef = useRef<CameraControls>(null);
|
||||
|
||||
const { toggleView } = useToggleView();
|
||||
const state = useThree();
|
||||
|
||||
useEffect(() => {
|
||||
if (controlsRef.current) {
|
||||
(controlsRef.current as any).mouseButtons.left = CONSTANTS.thirdPersonControls.leftMouse;
|
||||
(controlsRef.current as any).mouseButtons.right = CONSTANTS.thirdPersonControls.rightMouse;
|
||||
}
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
const userId = localStorage.getItem("userId")!;
|
||||
|
||||
getCamera(organization, userId, projectId).then((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);
|
||||
} else {
|
||||
controlsRef.current?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
|
||||
controlsRef.current?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
|
||||
}
|
||||
})
|
||||
.catch((error) => console.error("Failed to fetch camera data:", error));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CameraControls
|
||||
makeDefault
|
||||
ref={controlsRef}
|
||||
minDistance={toggleView ? CONSTANTS.twoDimension.minDistance : CONSTANTS.threeDimension.minDistance}
|
||||
maxDistance={CONSTANTS.thirdPersonControls.maxDistance}
|
||||
minZoom={CONSTANTS.thirdPersonControls.minZoom}
|
||||
maxZoom={CONSTANTS.thirdPersonControls.maxZoom}
|
||||
maxPolarAngle={CONSTANTS.thirdPersonControls.maxPolarAngle}
|
||||
camera={state.camera}
|
||||
verticalDragToForward={true}
|
||||
boundaryEnclosesCamera={true}
|
||||
dollyToCursor={toggleView}
|
||||
>
|
||||
</CameraControls>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import { EffectComposer, N8AO } from "@react-three/postprocessing";
|
||||
|
||||
export default function PostProcessingDuplicate() {
|
||||
|
||||
return (
|
||||
<EffectComposer autoClear={false}>
|
||||
<N8AO
|
||||
color="black"
|
||||
aoRadius={20}
|
||||
intensity={7}
|
||||
distanceFalloff={4}
|
||||
aoSamples={32}
|
||||
denoiseRadius={6}
|
||||
denoiseSamples={16}
|
||||
/>
|
||||
</EffectComposer>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
import { Canvas } from "@react-three/fiber";
|
||||
|
||||
import BuilderDuplicate from "./duplicateBuilder/builderDuplicate";
|
||||
import { Environment } from "@react-three/drei";
|
||||
import Shadows from "../scene/environment/shadow";
|
||||
import Sun from "../scene/environment/sky";
|
||||
|
||||
import background from "../../assets/textures/hdr/mudroadpuresky2k.hdr";
|
||||
import ControlsDuplicate from "./duplicateSetup/controlsDuplicate";
|
||||
import PostProcessingDuplicate from "./duplicateSetup/postProcessingDuplicate";
|
||||
import { Color } from "three";
|
||||
|
||||
export default function DuplicateScene() {
|
||||
const projectId = "684bcd620a64bc2a815a88d6";
|
||||
|
||||
return (
|
||||
<Canvas
|
||||
id="sceneCanvas"
|
||||
shadows
|
||||
color="#aaaa"
|
||||
eventPrefix="client"
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
onCreated={(e) => {
|
||||
e.scene.background = new Color(0x19191d);
|
||||
}}
|
||||
gl={{ powerPreference: "high-performance", antialias: true, preserveDrawingBuffer: true }}
|
||||
>
|
||||
|
||||
<Sun />
|
||||
|
||||
<Shadows />
|
||||
|
||||
<ControlsDuplicate projectId={projectId} />
|
||||
|
||||
<PostProcessingDuplicate />
|
||||
|
||||
<Environment files={background} environmentIntensity={1.5} />
|
||||
|
||||
<BuilderDuplicate projectId={projectId} />
|
||||
</Canvas>
|
||||
);
|
||||
}
|
|
@ -22,7 +22,6 @@ export default function Controls() {
|
|||
const state = useThree();
|
||||
const { projectId } = useParams();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (controlsRef.current) {
|
||||
(controlsRef.current as any).mouseButtons.left = CONSTANTS.thirdPersonControls.leftMouse;
|
||||
|
|
|
@ -14,6 +14,7 @@ import { getAllProjects } from "../../services/dashboard/getAllProjects";
|
|||
import { getUserData } from "../../components/Dashboard/functions/getUserData";
|
||||
import { useLoadingProgress, useSocketStore } from "../../store/builder/store";
|
||||
import { useAssetsStore } from "../../store/builder/useAssetStore";
|
||||
import { Color } from "three";
|
||||
|
||||
export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Comparison Layout' }) {
|
||||
const map = useMemo(() => [
|
||||
|
@ -28,6 +29,7 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co
|
|||
const { projectId } = useParams();
|
||||
const { projectSocket } = useSocketStore();
|
||||
const { loadingProgress } = useLoadingProgress();
|
||||
|
||||
const handleUpdatingProject = async () => {
|
||||
if (!projectId) return;
|
||||
try {
|
||||
|
@ -36,7 +38,6 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co
|
|||
(val: any) => val.projectUuid === projectId || val._id === projectId
|
||||
);
|
||||
|
||||
|
||||
if (activeModule === "builder" && loadingProgress !== 1) {
|
||||
const canvas =
|
||||
document.getElementById("sceneCanvas")?.children[0]?.children[0];
|
||||
|
@ -64,8 +65,6 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co
|
|||
projectName: projectUuid.projectName,
|
||||
thumbnail: screenshotDataUrl,
|
||||
};
|
||||
// console.log('screenshotDataUrl: ', screenshotDataUrl);
|
||||
// console.log('updateProjects: ', updateProjects);
|
||||
if (projectSocket) {
|
||||
projectSocket.emit("v1:project:update", updateProjects);
|
||||
}
|
||||
|
@ -89,7 +88,7 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co
|
|||
e.preventDefault();
|
||||
}}
|
||||
onCreated={(e) => {
|
||||
e.scene.background = null;
|
||||
e.scene.background = layout === 'Main Layout' ? null : new Color(0x19191d);
|
||||
}}
|
||||
gl={{ powerPreference: "high-performance", antialias: true, preserveDrawingBuffer: true }}
|
||||
>
|
||||
|
|
|
@ -23,7 +23,7 @@ import { useLogger } from "../../components/ui/log/LoggerContext";
|
|||
import { useComparisonProduct } from "../../store/simulation/useSimulationStore";
|
||||
|
||||
const KeyPressListener: React.FC = () => {
|
||||
const { clearComparisonProduct } = useComparisonProduct();
|
||||
const { comparisonProduct, clearComparisonProduct } = useComparisonProduct();
|
||||
const { activeModule, setActiveModule } = useModuleStore();
|
||||
const { setActiveSubTool } = useActiveSubTool();
|
||||
const { toggleUILeft, toggleUIRight, setToggleUI } = useToggleStore();
|
||||
|
@ -195,7 +195,7 @@ const KeyPressListener: React.FC = () => {
|
|||
handleBuilderShortcuts(keyCombination);
|
||||
|
||||
// Shortcut to enter play mode
|
||||
if (keyCombination === "Ctrl+P" && !toggleView) {
|
||||
if (keyCombination === "Ctrl+P" && !toggleView && !comparisonProduct) {
|
||||
setIsPlaying(true);
|
||||
}
|
||||
|
||||
|
@ -242,6 +242,7 @@ const KeyPressListener: React.FC = () => {
|
|||
hidePlayer,
|
||||
selectedFloorItem,
|
||||
isRenameMode,
|
||||
comparisonProduct
|
||||
]);
|
||||
|
||||
return null;
|
||||
|
|
Loading…
Reference in New Issue