add initial components and utility functions for simulation and builder modules
This commit is contained in:
929
app/package-lock.json
generated
929
app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "dwinzo-app",
|
"name": "dwinzo-beta",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -18,6 +18,8 @@
|
|||||||
"chart.js": "^4.4.8",
|
"chart.js": "^4.4.8",
|
||||||
"glob": "^11.0.0",
|
"glob": "^11.0.0",
|
||||||
"gsap": "^3.12.5",
|
"gsap": "^3.12.5",
|
||||||
|
"leva": "^0.10.0",
|
||||||
|
"mqtt": "^5.10.4",
|
||||||
"postprocessing": "^6.36.4",
|
"postprocessing": "^6.36.4",
|
||||||
"prompt-sync": "^4.2.0",
|
"prompt-sync": "^4.2.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
|||||||
BIN
app/src/assets/floor/concreteFloorWorn001Diff2k.jpg
Normal file
BIN
app/src/assets/floor/concreteFloorWorn001Diff2k.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 472 KiB |
BIN
app/src/assets/floor/concreteFloorWorn001NorGl2k.jpg
Normal file
BIN
app/src/assets/floor/concreteFloorWorn001NorGl2k.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 850 KiB |
BIN
app/src/assets/gltf-glb/arch.glb
Normal file
BIN
app/src/assets/gltf-glb/arch.glb
Normal file
Binary file not shown.
121
app/src/assets/gltf-glb/camera face 2.gltf
Normal file
121
app/src/assets/gltf-glb/camera face 2.gltf
Normal file
File diff suppressed because one or more lines are too long
BIN
app/src/assets/gltf-glb/crate_box.glb
Normal file
BIN
app/src/assets/gltf-glb/crate_box.glb
Normal file
Binary file not shown.
BIN
app/src/assets/gltf-glb/door.glb
Normal file
BIN
app/src/assets/gltf-glb/door.glb
Normal file
Binary file not shown.
BIN
app/src/assets/gltf-glb/window.glb
Normal file
BIN
app/src/assets/gltf-glb/window.glb
Normal file
Binary file not shown.
BIN
app/src/assets/hdr/mudroadpuresky2k.hdr
Normal file
BIN
app/src/assets/hdr/mudroadpuresky2k.hdr
Normal file
Binary file not shown.
BIN
app/src/assets/image/userImage.png
Normal file
BIN
app/src/assets/image/userImage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 480 KiB |
BIN
app/src/assets/textures/floor/concreteFloorWorn001Diff2k.jpg
Normal file
BIN
app/src/assets/textures/floor/concreteFloorWorn001Diff2k.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 472 KiB |
BIN
app/src/assets/textures/floor/concreteFloorWorn001NorGl2k.jpg
Normal file
BIN
app/src/assets/textures/floor/concreteFloorWorn001NorGl2k.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 850 KiB |
BIN
app/src/assets/textures/hdr/mudroadpuresky2k.hdr
Normal file
BIN
app/src/assets/textures/hdr/mudroadpuresky2k.hdr
Normal file
Binary file not shown.
@@ -44,6 +44,7 @@ const AssetProperties: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="asset-properties-container">
|
<div className="asset-properties-container">
|
||||||
|
{/* Name */}
|
||||||
<div className="header">Selected Object</div>
|
<div className="header">Selected Object</div>
|
||||||
|
|
||||||
<div className="split"></div>
|
<div className="split"></div>
|
||||||
|
|||||||
@@ -87,8 +87,7 @@ const Simulations: React.FC = () => {
|
|||||||
{productsList.map((action, index) => (
|
{productsList.map((action, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`list-item ${
|
className={`list-item ${selectedItem === action ? "active" : ""
|
||||||
selectedItem === action ? "active" : ""
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -123,8 +122,8 @@ const Simulations: React.FC = () => {
|
|||||||
<ArrowIcon />
|
<ArrowIcon />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{Value.map((val) => (
|
{Value.map((val, index) => (
|
||||||
<DropList val={val} />
|
<DropList key={index} val={val} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="compare-simulations-container">
|
<div className="compare-simulations-container">
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { handleSaveTemplate } from "../../modules/visualization/handleSaveTempla
|
|||||||
import { usePlayButtonStore } from "../../store/usePlayButtonStore";
|
import { usePlayButtonStore } from "../../store/usePlayButtonStore";
|
||||||
import useTemplateStore from "../../store/useTemplateStore";
|
import useTemplateStore from "../../store/useTemplateStore";
|
||||||
import { useSelectedZoneStore } from "../../store/useZoneStore";
|
import { useSelectedZoneStore } from "../../store/useZoneStore";
|
||||||
|
import { useAddAction, useDeleteModels, useSelectedWallItem, useToggleView } from "../../store/store";
|
||||||
|
|
||||||
const Tools: React.FC = () => {
|
const Tools: React.FC = () => {
|
||||||
const { templates } = useTemplateStore();
|
const { templates } = useTemplateStore();
|
||||||
@@ -32,12 +33,32 @@ const Tools: React.FC = () => {
|
|||||||
const { addTemplate } = useTemplateStore();
|
const { addTemplate } = useTemplateStore();
|
||||||
const { selectedZone } = useSelectedZoneStore();
|
const { selectedZone } = useSelectedZoneStore();
|
||||||
|
|
||||||
|
// wall options
|
||||||
|
const { setToggleView } = useToggleView();
|
||||||
|
const { setDeleteModels } = useDeleteModels();
|
||||||
|
const { setAddAction } = useAddAction();
|
||||||
|
const { setSelectedWallItem } = useSelectedWallItem();
|
||||||
|
|
||||||
|
|
||||||
// Reset activeTool whenever activeModule changes
|
// Reset activeTool whenever activeModule changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setActiveTool(activeSubTool);
|
setActiveTool(activeSubTool);
|
||||||
setActiveSubTool(activeSubTool);
|
setActiveSubTool(activeSubTool);
|
||||||
}, [activeModule]);
|
}, [activeModule]);
|
||||||
|
|
||||||
|
const toggleSwitch = () => {
|
||||||
|
if (toggleThreeD) {
|
||||||
|
setSelectedWallItem(null);
|
||||||
|
setDeleteModels(false);
|
||||||
|
setAddAction(null);
|
||||||
|
setToggleView(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setToggleView(false);
|
||||||
|
}
|
||||||
|
setToggleThreeD(!toggleThreeD);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleOutsideClick = (event: MouseEvent) => {
|
const handleOutsideClick = (event: MouseEvent) => {
|
||||||
if (
|
if (
|
||||||
@@ -61,8 +82,7 @@ const Tools: React.FC = () => {
|
|||||||
<div className="activeDropicon">
|
<div className="activeDropicon">
|
||||||
{activeSubTool == "cursor" && (
|
{activeSubTool == "cursor" && (
|
||||||
<div
|
<div
|
||||||
className={`tool-button ${
|
className={`tool-button ${activeTool === "cursor" ? "active" : ""
|
||||||
activeTool === "cursor" ? "active" : ""
|
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActiveTool("cursor");
|
setActiveTool("cursor");
|
||||||
@@ -73,8 +93,7 @@ const Tools: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
{activeSubTool == "free-hand" && (
|
{activeSubTool == "free-hand" && (
|
||||||
<div
|
<div
|
||||||
className={`tool-button ${
|
className={`tool-button ${activeTool === "free-hand" ? "active" : ""
|
||||||
activeTool === "free-hand" ? "active" : ""
|
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActiveTool("free-hand");
|
setActiveTool("free-hand");
|
||||||
@@ -134,8 +153,7 @@ const Tools: React.FC = () => {
|
|||||||
<div className="split"></div>
|
<div className="split"></div>
|
||||||
<div className="draw-tools">
|
<div className="draw-tools">
|
||||||
<div
|
<div
|
||||||
className={`tool-button ${
|
className={`tool-button ${activeTool === "draw-wall" ? "active" : ""
|
||||||
activeTool === "draw-wall" ? "active" : ""
|
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActiveTool("draw-wall");
|
setActiveTool("draw-wall");
|
||||||
@@ -144,8 +162,7 @@ const Tools: React.FC = () => {
|
|||||||
<WallIcon isActive={activeTool === "draw-wall"} />
|
<WallIcon isActive={activeTool === "draw-wall"} />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`tool-button ${
|
className={`tool-button ${activeTool === "draw-zone" ? "active" : ""
|
||||||
activeTool === "draw-zone" ? "active" : ""
|
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActiveTool("draw-zone");
|
setActiveTool("draw-zone");
|
||||||
@@ -154,8 +171,7 @@ const Tools: React.FC = () => {
|
|||||||
<ZoneIcon isActive={activeTool === "draw-zone"} />
|
<ZoneIcon isActive={activeTool === "draw-zone"} />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`tool-button ${
|
className={`tool-button ${activeTool === "draw-aisle" ? "active" : ""
|
||||||
activeTool === "draw-aisle" ? "active" : ""
|
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActiveTool("draw-aisle");
|
setActiveTool("draw-aisle");
|
||||||
@@ -164,8 +180,7 @@ const Tools: React.FC = () => {
|
|||||||
<AsileIcon isActive={activeTool === "draw-aisle"} />
|
<AsileIcon isActive={activeTool === "draw-aisle"} />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`tool-button ${
|
className={`tool-button ${activeTool === "draw-floor" ? "active" : ""
|
||||||
activeTool === "draw-floor" ? "active" : ""
|
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActiveTool("draw-floor");
|
setActiveTool("draw-floor");
|
||||||
@@ -233,9 +248,7 @@ const Tools: React.FC = () => {
|
|||||||
<div className="split"></div>
|
<div className="split"></div>
|
||||||
<div
|
<div
|
||||||
className={`toggle-threed-button${toggleThreeD ? " toggled" : ""}`}
|
className={`toggle-threed-button${toggleThreeD ? " toggled" : ""}`}
|
||||||
onClick={() => {
|
onClick={toggleSwitch}
|
||||||
setToggleThreeD(!toggleThreeD);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className={`toggle-option${!toggleThreeD ? " active" : ""}`}>
|
<div className={`toggle-option${!toggleThreeD ? " active" : ""}`}>
|
||||||
2d
|
2d
|
||||||
|
|||||||
54
app/src/modules/builder/csg/csg.tsx
Normal file
54
app/src/modules/builder/csg/csg.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import * as THREE from "three";
|
||||||
|
import { Geometry, Base, Subtraction } from "@react-three/csg";
|
||||||
|
import { useDeleteModels } from "../../../store/store";
|
||||||
|
import { useRef } from "react";
|
||||||
|
|
||||||
|
export interface CsgProps {
|
||||||
|
position: THREE.Vector3 | [number, number, number];
|
||||||
|
scale: THREE.Vector3 | [number, number, number];
|
||||||
|
model: THREE.Object3D;
|
||||||
|
hoveredDeletableWallItem: { current: THREE.Mesh | null };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Csg: React.FC<CsgProps> = (props) => {
|
||||||
|
const { deleteModels } = useDeleteModels();
|
||||||
|
const modelRef = useRef<THREE.Object3D>();
|
||||||
|
const originalMaterials = useRef<Map<THREE.Mesh, THREE.Material>>(new Map());
|
||||||
|
|
||||||
|
const handleHover = (hovered: boolean, object: THREE.Mesh | null) => {
|
||||||
|
if (modelRef.current && deleteModels) {
|
||||||
|
modelRef.current.traverse((child) => {
|
||||||
|
if (child instanceof THREE.Mesh) {
|
||||||
|
if (!originalMaterials.current.has(child)) {
|
||||||
|
originalMaterials.current.set(child, child.material);
|
||||||
|
}
|
||||||
|
child.material = child.material.clone();
|
||||||
|
child.material.color.set(hovered && deleteModels ? 0xff0000 : (originalMaterials.current.get(child) as any).color);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
props.hoveredDeletableWallItem.current = hovered ? object : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Geometry>
|
||||||
|
<Subtraction {...props}>
|
||||||
|
<Geometry>
|
||||||
|
<Base geometry={new THREE.BoxGeometry()} />
|
||||||
|
</Geometry>
|
||||||
|
</Subtraction>
|
||||||
|
<primitive
|
||||||
|
object={props.model}
|
||||||
|
ref={modelRef}
|
||||||
|
onPointerOver={(e: any) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleHover(true, e.object.parent);
|
||||||
|
}}
|
||||||
|
onPointerOut={(e: any) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleHover(false, null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Geometry>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import { DragControls } from 'three/examples/jsm/controls/DragControls';
|
||||||
|
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||||
|
import DragPoint from '../geomentries/points/dragPoint';
|
||||||
|
|
||||||
|
import * as Types from "../../../types/world/worldTypes";
|
||||||
|
// import { updatePoint } from '../../../services/factoryBuilder/lines/updatePointApi';
|
||||||
|
import { Socket } from 'socket.io-client';
|
||||||
|
|
||||||
|
export default async function addDragControl(
|
||||||
|
dragPointControls: Types.RefDragControl,
|
||||||
|
currentLayerPoint: Types.RefMeshArray,
|
||||||
|
state: Types.ThreeState,
|
||||||
|
floorPlanGroupPoint: Types.RefGroup,
|
||||||
|
floorPlanGroupLine: Types.RefGroup,
|
||||||
|
lines: Types.RefLines,
|
||||||
|
onlyFloorlines: Types.RefOnlyFloorLines,
|
||||||
|
socket: Socket<any>
|
||||||
|
) {
|
||||||
|
|
||||||
|
////////// Dragging Point and also change the size to indicate during hover //////////
|
||||||
|
|
||||||
|
dragPointControls.current = new DragControls(currentLayerPoint.current, state.camera, state.gl.domElement);
|
||||||
|
dragPointControls.current.enabled = false;
|
||||||
|
|
||||||
|
dragPointControls.current.addEventListener('drag', function (event) {
|
||||||
|
const object = event.object;
|
||||||
|
if (object.visible) {
|
||||||
|
(state.controls as any).enabled = false;
|
||||||
|
DragPoint(event as any, floorPlanGroupPoint, floorPlanGroupLine, state.scene, lines, onlyFloorlines)
|
||||||
|
} else {
|
||||||
|
(state.controls as any).enabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dragPointControls.current.addEventListener('dragstart', function (event) {
|
||||||
|
});
|
||||||
|
|
||||||
|
dragPointControls.current.addEventListener('dragend', async function (event) {
|
||||||
|
if (!dragPointControls.current) return;
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// await updatePoint(
|
||||||
|
// organization,
|
||||||
|
// { "x": event.object.position.x, "y": 0.01, "z": event.object.position.z },
|
||||||
|
// event.object.uuid,
|
||||||
|
// )
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
organization: organization,
|
||||||
|
position: { "x": event.object.position.x, "y": 0.01, "z": event.object.position.z },
|
||||||
|
uuid: event.object.uuid,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:Line:update', data);
|
||||||
|
|
||||||
|
if (state.controls) {
|
||||||
|
(state.controls as any).enabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dragPointControls.current.addEventListener('hoveron', function (event: any) {
|
||||||
|
if ((event.object as Types.Mesh).name === "point") {
|
||||||
|
event.object.material.uniforms.uInnerColor.value.set(event.object.userData.color)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dragPointControls.current.addEventListener('hoveroff', function (event: any) {
|
||||||
|
if ((event.object as Types.Mesh).name === "point") {
|
||||||
|
event.object.material.uniforms.uInnerColor.value.set(new THREE.Color(CONSTANTS.pointConfig.defaultInnerColor))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import * as Types from "../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
export default function handleContextMenu(
|
||||||
|
menuVisible: Types.Boolean,
|
||||||
|
setMenuVisible: Types.BooleanState
|
||||||
|
): void {
|
||||||
|
// setMenuVisible(true)
|
||||||
|
}
|
||||||
64
app/src/modules/builder/eventFunctions/handleMeshDown.ts
Normal file
64
app/src/modules/builder/eventFunctions/handleMeshDown.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import * as Types from "../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function handleMeshDown(
|
||||||
|
event: Types.MeshEvent,
|
||||||
|
currentWallItem: Types.RefMesh,
|
||||||
|
setSelectedWallItem: Types.setSelectedWallItemSetState,
|
||||||
|
setSelectedItemsIndex: Types.setSelectedItemsIndexSetState,
|
||||||
|
wallItems: Types.wallItems,
|
||||||
|
toggleView: Types.Boolean
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// To select which of the Wall item and CSG is selected to be dragged //////////
|
||||||
|
|
||||||
|
if (!toggleView) {
|
||||||
|
if (currentWallItem.current) {
|
||||||
|
currentWallItem.current.children.forEach((child) => {
|
||||||
|
if ((child as THREE.Mesh).isMesh && child.name !== "CSG_REF") {
|
||||||
|
const material = (child as THREE.Mesh).material;
|
||||||
|
if (Array.isArray(material)) {
|
||||||
|
material.forEach(mat => {
|
||||||
|
if (mat instanceof THREE.MeshStandardMaterial) {
|
||||||
|
mat.emissive = new THREE.Color("black");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (material instanceof THREE.MeshStandardMaterial) {
|
||||||
|
material.emissive = new THREE.Color("black");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
currentWallItem.current = null;
|
||||||
|
setSelectedWallItem(null);
|
||||||
|
setSelectedItemsIndex(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.intersections.length > 0) {
|
||||||
|
const clickedIndex = wallItems.findIndex((item) => item.model === event.intersections[0]?.object?.parent?.parent);
|
||||||
|
if (clickedIndex !== -1) {
|
||||||
|
setSelectedItemsIndex(clickedIndex);
|
||||||
|
const wallItemModel = wallItems[clickedIndex]?.model;
|
||||||
|
if (wallItemModel && wallItemModel.parent && wallItemModel.parent.parent) {
|
||||||
|
currentWallItem.current = (wallItemModel.parent.parent.children[0]?.children[1]?.children[0] as Types.Mesh) || null;
|
||||||
|
setSelectedWallItem(wallItemModel.parent);
|
||||||
|
// currentWallItem.current?.children.forEach((child) => {
|
||||||
|
// if ((child as THREE.Mesh).isMesh && child.name !== "CSG_REF") {
|
||||||
|
// const material = (child as THREE.Mesh).material;
|
||||||
|
// if (Array.isArray(material)) {
|
||||||
|
// material.forEach(mat => {
|
||||||
|
// if (mat instanceof THREE.MeshStandardMaterial) {
|
||||||
|
// mat.emissive = new THREE.Color("green");
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// } else if (material instanceof THREE.MeshStandardMaterial) {
|
||||||
|
// material.emissive = new THREE.Color("green");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default handleMeshDown;
|
||||||
34
app/src/modules/builder/eventFunctions/handleMeshMissed.ts
Normal file
34
app/src/modules/builder/eventFunctions/handleMeshMissed.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as Types from "../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function handleMeshMissed(
|
||||||
|
currentWallItem: Types.RefMesh,
|
||||||
|
setSelectedWallItem: Types.setSelectedWallItemSetState,
|
||||||
|
setSelectedItemsIndex: Types.setSelectedItemsIndexSetState
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// If an item is selected and then clicked outside other than the selected object, this runs and removes the color of the selected object and sets setSelectedWallItem and setSelectedItemsIndex as null //////////
|
||||||
|
|
||||||
|
if (currentWallItem.current) {
|
||||||
|
currentWallItem.current.children.forEach((child) => {
|
||||||
|
if ((child as THREE.Mesh).isMesh && child.name !== "CSG_REF") {
|
||||||
|
const material = (child as THREE.Mesh).material;
|
||||||
|
|
||||||
|
if (Array.isArray(material)) {
|
||||||
|
material.forEach(mat => {
|
||||||
|
if (mat instanceof THREE.MeshStandardMaterial) {
|
||||||
|
mat.emissive = new THREE.Color("black");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (material instanceof THREE.MeshStandardMaterial) {
|
||||||
|
material.emissive = new THREE.Color("black");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
currentWallItem.current = null;
|
||||||
|
setSelectedWallItem(null);
|
||||||
|
setSelectedItemsIndex(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default handleMeshMissed;
|
||||||
87
app/src/modules/builder/functions/deletableLineOrPoint.ts
Normal file
87
app/src/modules/builder/functions/deletableLineOrPoint.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||||
|
import * as Types from "../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function DeletableLineorPoint(
|
||||||
|
state: Types.ThreeState,
|
||||||
|
plane: Types.RefMesh,
|
||||||
|
floorPlanGroupLine: Types.RefGroup,
|
||||||
|
floorPlanGroupPoint: Types.RefGroup,
|
||||||
|
hoveredDeletableLine: Types.RefMesh,
|
||||||
|
hoveredDeletablePoint: Types.RefMesh
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Altering the color of the hovered line or point during the deletion time //////////
|
||||||
|
|
||||||
|
if (!plane.current) return;
|
||||||
|
let intersects = state.raycaster.intersectObject(plane.current, true);
|
||||||
|
|
||||||
|
let visibleIntersectLines;
|
||||||
|
if (floorPlanGroupLine.current) { visibleIntersectLines = state.raycaster?.intersectObjects(floorPlanGroupLine.current.children, true); }
|
||||||
|
const visibleIntersectLine = visibleIntersectLines?.find(intersect => intersect.object.visible) as THREE.Line | undefined || null;
|
||||||
|
|
||||||
|
let visibleIntersectPoints;
|
||||||
|
if (floorPlanGroupPoint.current) {
|
||||||
|
visibleIntersectPoints = state.raycaster?.intersectObjects(floorPlanGroupPoint.current.children, true);
|
||||||
|
}
|
||||||
|
const visibleIntersectPoint = visibleIntersectPoints?.find(intersect => intersect.object.visible) as THREE.Mesh | undefined;
|
||||||
|
|
||||||
|
function getLineColor(lineType: string | undefined): string {
|
||||||
|
switch (lineType) {
|
||||||
|
case CONSTANTS.lineConfig.wallName: return CONSTANTS.lineConfig.wallColor;
|
||||||
|
case CONSTANTS.lineConfig.floorName: return CONSTANTS.lineConfig.floorColor;
|
||||||
|
case CONSTANTS.lineConfig.aisleName: return CONSTANTS.lineConfig.aisleColor;
|
||||||
|
default: return CONSTANTS.lineConfig.defaultColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intersects.length > 0) {
|
||||||
|
if (visibleIntersectPoint) {
|
||||||
|
if (hoveredDeletableLine.current) {
|
||||||
|
const lineType = hoveredDeletableLine.current.userData.linePoints[1]?.[3];
|
||||||
|
const color = getLineColor(lineType);
|
||||||
|
(hoveredDeletableLine.current.material as THREE.MeshBasicMaterial).color = new THREE.Color(color);
|
||||||
|
hoveredDeletableLine.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
hoveredDeletablePoint.current = (visibleIntersectPoint as any).object;
|
||||||
|
(hoveredDeletablePoint.current as any).material.uniforms.uInnerColor.value.set(new THREE.Color("red"));
|
||||||
|
(hoveredDeletablePoint.current as any).material.uniforms.uColor.value.set(new THREE.Color("red"));
|
||||||
|
// (hoveredDeletablePoint.current as THREE.Mesh).scale.set(1.5, 1.5, 1.5);
|
||||||
|
} else if (hoveredDeletablePoint.current) {
|
||||||
|
(hoveredDeletablePoint.current as any).material.uniforms.uInnerColor.value.set(CONSTANTS.pointConfig.defaultInnerColor);
|
||||||
|
(hoveredDeletablePoint.current as any).material.uniforms.uColor.value.set((hoveredDeletablePoint.current as any).userData.color);
|
||||||
|
// hoveredDeletablePoint.current.scale.set(1, 1, 1);
|
||||||
|
hoveredDeletablePoint.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visibleIntersectLine && !visibleIntersectPoint) {
|
||||||
|
if (hoveredDeletableLine.current) {
|
||||||
|
const lineType = hoveredDeletableLine.current.userData.linePoints[1]?.[3];
|
||||||
|
const color = getLineColor(lineType);
|
||||||
|
(hoveredDeletableLine.current.material as THREE.MeshBasicMaterial).color = new THREE.Color(color);
|
||||||
|
hoveredDeletableLine.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hoveredDeletablePoint.current) {
|
||||||
|
(hoveredDeletablePoint.current as any).material.uniforms.uInnerColor.value.set(CONSTANTS.pointConfig.defaultInnerColor);
|
||||||
|
(hoveredDeletablePoint.current as any).material.uniforms.uColor.value.set((hoveredDeletablePoint.current as any).userData.color);
|
||||||
|
// hoveredDeletablePoint.current.scale.set(1, 1, 1);
|
||||||
|
hoveredDeletablePoint.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
hoveredDeletableLine.current = (visibleIntersectLine as any).object;
|
||||||
|
if (hoveredDeletableLine.current) {
|
||||||
|
(hoveredDeletableLine.current.material as THREE.MeshBasicMaterial).color = new THREE.Color("red");
|
||||||
|
}
|
||||||
|
} else if (hoveredDeletableLine.current) {
|
||||||
|
const lineType = hoveredDeletableLine.current.userData.linePoints[1]?.[3];
|
||||||
|
const color = getLineColor(lineType);
|
||||||
|
(hoveredDeletableLine.current.material as THREE.MeshBasicMaterial).color = new THREE.Color(color);
|
||||||
|
hoveredDeletableLine.current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeletableLineorPoint;
|
||||||
97
app/src/modules/builder/functions/draw.ts
Normal file
97
app/src/modules/builder/functions/draw.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import * as Types from "../../../types/world/worldTypes";
|
||||||
|
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||||
|
import createAndMoveReferenceLine from "../geomentries/lines/createAndMoveReferenceLine";
|
||||||
|
|
||||||
|
async function Draw(
|
||||||
|
state: Types.ThreeState,
|
||||||
|
plane: Types.RefMesh,
|
||||||
|
cursorPosition: Types.Vector3,
|
||||||
|
floorPlanGroupPoint: Types.RefGroup,
|
||||||
|
floorPlanGroupLine: Types.RefGroup,
|
||||||
|
snappedPoint: Types.RefVector3,
|
||||||
|
isSnapped: Types.RefBoolean,
|
||||||
|
isSnappedUUID: Types.RefString,
|
||||||
|
line: Types.RefLine,
|
||||||
|
lines: Types.RefLines,
|
||||||
|
ispreSnapped: Types.RefBoolean,
|
||||||
|
floorPlanGroup: Types.RefGroup,
|
||||||
|
ReferenceLineMesh: Types.RefMesh,
|
||||||
|
LineCreated: Types.RefBoolean,
|
||||||
|
setRefTextUpdate: Types.NumberIncrementState,
|
||||||
|
Tube: Types.RefTubeGeometry,
|
||||||
|
anglesnappedPoint: Types.RefVector3,
|
||||||
|
isAngleSnapped: Types.RefBoolean,
|
||||||
|
toolMode: Types.String,
|
||||||
|
): Promise<void> {
|
||||||
|
|
||||||
|
////////// Snapping the cursor during the drawing time and also changing the color of the intersected lines //////////
|
||||||
|
|
||||||
|
if (!plane.current) return;
|
||||||
|
const intersects = state.raycaster.intersectObject(plane.current, true);
|
||||||
|
|
||||||
|
if (intersects.length > 0 && (toolMode === "Wall" || toolMode === "Aisle" || toolMode === "Floor")) {
|
||||||
|
const intersectionPoint = intersects[0].point;
|
||||||
|
cursorPosition.copy(intersectionPoint);
|
||||||
|
const snapThreshold = 1;
|
||||||
|
|
||||||
|
if (line.current.length === 0) {
|
||||||
|
for (const point of floorPlanGroupPoint.current.children) {
|
||||||
|
const pointType = point.userData.type;
|
||||||
|
|
||||||
|
const canSnap =
|
||||||
|
((toolMode === "Wall") && (pointType === CONSTANTS.lineConfig.wallName || pointType === CONSTANTS.lineConfig.floorName)) ||
|
||||||
|
((toolMode === "Floor") && (pointType === CONSTANTS.lineConfig.wallName || pointType === CONSTANTS.lineConfig.floorName)) ||
|
||||||
|
((toolMode === "Aisle") && pointType === CONSTANTS.lineConfig.aisleName);;
|
||||||
|
|
||||||
|
if (canSnap && cursorPosition.distanceTo(point.position) < snapThreshold + 0.5 && point.visible) {
|
||||||
|
cursorPosition.copy(point.position);
|
||||||
|
snappedPoint.current = point.position;
|
||||||
|
ispreSnapped.current = true;
|
||||||
|
isSnapped.current = false;
|
||||||
|
isSnappedUUID.current = point.uuid;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
ispreSnapped.current = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (line.current.length > 0 && line.current[0]) {
|
||||||
|
for (const point of floorPlanGroupPoint.current.children) {
|
||||||
|
const pointType = point.userData.type;
|
||||||
|
|
||||||
|
let canSnap =
|
||||||
|
((toolMode === "Wall") && (pointType === CONSTANTS.lineConfig.wallName || pointType === CONSTANTS.lineConfig.floorName)) ||
|
||||||
|
((toolMode === "Floor") && (pointType === CONSTANTS.lineConfig.wallName || pointType === CONSTANTS.lineConfig.floorName)) ||
|
||||||
|
((toolMode === "Aisle") && pointType === CONSTANTS.lineConfig.aisleName);
|
||||||
|
|
||||||
|
if (canSnap && cursorPosition.distanceTo(point.position) < snapThreshold && point.visible) {
|
||||||
|
cursorPosition.copy(point.position);
|
||||||
|
snappedPoint.current = point.position;
|
||||||
|
isSnapped.current = true;
|
||||||
|
ispreSnapped.current = false;
|
||||||
|
isSnappedUUID.current = point.uuid;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
isSnapped.current = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createAndMoveReferenceLine(
|
||||||
|
line.current[0][0],
|
||||||
|
cursorPosition,
|
||||||
|
isSnapped,
|
||||||
|
ispreSnapped,
|
||||||
|
line,
|
||||||
|
setRefTextUpdate,
|
||||||
|
floorPlanGroup,
|
||||||
|
ReferenceLineMesh,
|
||||||
|
LineCreated,
|
||||||
|
Tube,
|
||||||
|
anglesnappedPoint,
|
||||||
|
isAngleSnapped
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Draw;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as Types from '../../../../types/world/worldTypes';
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
|
||||||
|
export default async function addAisleToScene(
|
||||||
|
aisle: Types.Line,
|
||||||
|
floorGroupAisle: Types.RefGroup,
|
||||||
|
): Promise<void> {
|
||||||
|
if (aisle.length >= 2 && aisle[0] && aisle[1]) {
|
||||||
|
const start: Types.Vector3 = aisle[0][0];
|
||||||
|
const end: Types.Vector3 = aisle[1][0];
|
||||||
|
|
||||||
|
const direction = new THREE.Vector3(
|
||||||
|
end.x - start.x,
|
||||||
|
end.y - start.y,
|
||||||
|
end.z - start.z
|
||||||
|
).normalize();
|
||||||
|
|
||||||
|
const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize();
|
||||||
|
const offsetDistance = CONSTANTS.aisleConfig.width;
|
||||||
|
|
||||||
|
const leftStart = new THREE.Vector3().copy(start).addScaledVector(perp, offsetDistance);
|
||||||
|
const rightStart = new THREE.Vector3().copy(start).addScaledVector(perp, -offsetDistance);
|
||||||
|
const leftEnd = new THREE.Vector3().copy(end).addScaledVector(perp, offsetDistance);
|
||||||
|
const rightEnd = new THREE.Vector3().copy(end).addScaledVector(perp, -offsetDistance);
|
||||||
|
|
||||||
|
const stripShape = new THREE.Shape();
|
||||||
|
stripShape.moveTo(leftStart.x, leftStart.z);
|
||||||
|
stripShape.lineTo(leftEnd.x, leftEnd.z);
|
||||||
|
stripShape.lineTo(rightEnd.x, rightEnd.z);
|
||||||
|
stripShape.lineTo(rightStart.x, rightStart.z);
|
||||||
|
stripShape.lineTo(leftStart.x, leftStart.z);
|
||||||
|
|
||||||
|
const extrudeSettings = {
|
||||||
|
depth: CONSTANTS.aisleConfig.height,
|
||||||
|
bevelEnabled: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const stripGeometry = new THREE.ExtrudeGeometry(stripShape, extrudeSettings);
|
||||||
|
const stripMaterial = new THREE.MeshStandardMaterial({
|
||||||
|
color: CONSTANTS.aisleConfig.defaultColor,
|
||||||
|
polygonOffset: true,
|
||||||
|
polygonOffsetFactor: -1,
|
||||||
|
polygonOffsetUnits: -1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const stripMesh = new THREE.Mesh(stripGeometry, stripMaterial);
|
||||||
|
stripMesh.receiveShadow = true;
|
||||||
|
stripMesh.castShadow = true;
|
||||||
|
|
||||||
|
stripMesh.position.y = (aisle[0][2] - 1) * CONSTANTS.wallConfig.height + 0.01;
|
||||||
|
stripMesh.rotateX(Math.PI / 2);
|
||||||
|
|
||||||
|
floorGroupAisle.current.add(stripMesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/src/modules/builder/geomentries/aisles/loadAisles.ts
Normal file
19
app/src/modules/builder/geomentries/aisles/loadAisles.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import * as Types from '../../../../types/world/worldTypes';
|
||||||
|
import addAisleToScene from './addAilseToScene';
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
|
||||||
|
export default async function loadAisles(
|
||||||
|
lines: Types.RefLines,
|
||||||
|
floorGroupAisle: Types.RefGroup
|
||||||
|
) {
|
||||||
|
// console.log('lines: ', lines.current[0][0][0]);
|
||||||
|
if (!floorGroupAisle.current) return
|
||||||
|
floorGroupAisle.current.children = [];
|
||||||
|
const aisles = lines.current.filter((line) => line[0][3] && line[1][3] === CONSTANTS.lineConfig.aisleName);
|
||||||
|
|
||||||
|
if (aisles.length > 0) {
|
||||||
|
aisles.forEach((aisle: Types.Line) => {
|
||||||
|
addAisleToScene(aisle, floorGroupAisle)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
186
app/src/modules/builder/geomentries/assets/addAssetModel.ts
Normal file
186
app/src/modules/builder/geomentries/assets/addAssetModel.ts
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||||
|
import gsap from 'gsap';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import TempLoader from './tempLoader';
|
||||||
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
import { retrieveGLTF, storeGLTF } from '../../../../utils/indexDB/idbUtils';
|
||||||
|
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
|
||||||
|
import { Socket } from 'socket.io-client';
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
|
||||||
|
async function addAssetModel(
|
||||||
|
raycaster: THREE.Raycaster,
|
||||||
|
camera: THREE.Camera,
|
||||||
|
pointer: THREE.Vector2,
|
||||||
|
floorGroup: Types.RefGroup,
|
||||||
|
setFloorItems: Types.setFloorItemSetState,
|
||||||
|
itemsGroup: Types.RefGroup,
|
||||||
|
isTempLoader: Types.RefBoolean,
|
||||||
|
tempLoader: Types.RefMesh,
|
||||||
|
socket: Socket<any>,
|
||||||
|
selectedItem: any,
|
||||||
|
setSelectedItem: any,
|
||||||
|
plane: Types.RefMesh,
|
||||||
|
): Promise<void> {
|
||||||
|
|
||||||
|
////////// Load Floor GLtf's and set the positions, rotation, type etc. in state and store in localstorage //////////
|
||||||
|
|
||||||
|
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
isTempLoader.current = true;
|
||||||
|
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 floorIntersections = raycaster.intersectObjects(floorGroup.current.children, true);
|
||||||
|
const intersectedFloor = floorIntersections.find(intersect => intersect.object.name.includes("Floor"));
|
||||||
|
|
||||||
|
const planeIntersections = raycaster.intersectObject(plane.current!, true);
|
||||||
|
const intersectedPlane = planeIntersections[0];
|
||||||
|
|
||||||
|
let intersectPoint: THREE.Vector3 | null = null;
|
||||||
|
|
||||||
|
if (intersectedFloor && intersectedPlane) {
|
||||||
|
intersectPoint = intersectedFloor.distance < intersectedPlane.distance ? (new THREE.Vector3(intersectedFloor.point.x, Math.round(intersectedFloor.point.y), intersectedFloor.point.z)) : (new THREE.Vector3(intersectedPlane.point.x, 0, intersectedPlane.point.z));
|
||||||
|
} else if (intersectedFloor) {
|
||||||
|
intersectPoint = new THREE.Vector3(intersectedFloor.point.x, Math.round(intersectedFloor.point.y), intersectedFloor.point.z);
|
||||||
|
} else if (intersectedPlane) {
|
||||||
|
intersectPoint = new THREE.Vector3(intersectedPlane.point.x, 0, intersectedPlane.point.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intersectPoint) {
|
||||||
|
if (intersectPoint.y < 0) {
|
||||||
|
intersectPoint = new THREE.Vector3(intersectPoint.x, 0, intersectPoint.z);
|
||||||
|
}
|
||||||
|
const cachedModel = THREE.Cache.get(selectedItem.id);
|
||||||
|
if (cachedModel) {
|
||||||
|
// console.log(`[Cache] Fetching ${selectedItem.name}`);
|
||||||
|
handleModelLoad(cachedModel, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
const cachedModelBlob = await retrieveGLTF(selectedItem.id);
|
||||||
|
|
||||||
|
if (cachedModelBlob) {
|
||||||
|
// console.log(`Added ${selectedItem.name} from indexDB`);
|
||||||
|
|
||||||
|
const blobUrl = URL.createObjectURL(cachedModelBlob);
|
||||||
|
loader.load(blobUrl, (gltf) => {
|
||||||
|
URL.revokeObjectURL(blobUrl);
|
||||||
|
THREE.Cache.remove(blobUrl);
|
||||||
|
THREE.Cache.add(selectedItem.id, gltf);
|
||||||
|
handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// console.log(`Added ${selectedItem.name} from Backend`);
|
||||||
|
|
||||||
|
loader.load(`${url_Backend_dwinzo}/api/v1/AssetFile/${selectedItem.id}`, async (gltf) => {
|
||||||
|
const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v1/AssetFile/${selectedItem.id}`).then((res) => res.blob());
|
||||||
|
await storeGLTF(selectedItem.id, modelBlob);
|
||||||
|
THREE.Cache.add(selectedItem.id, gltf);
|
||||||
|
await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching asset model:', error);
|
||||||
|
} finally {
|
||||||
|
setSelectedItem({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleModelLoad(
|
||||||
|
gltf: any,
|
||||||
|
intersectPoint: THREE.Vector3,
|
||||||
|
selectedItem: any,
|
||||||
|
itemsGroup: Types.RefGroup,
|
||||||
|
tempLoader: Types.RefMesh,
|
||||||
|
isTempLoader: Types.RefBoolean,
|
||||||
|
setFloorItems: Types.setFloorItemSetState,
|
||||||
|
socket: Socket<any>
|
||||||
|
) {
|
||||||
|
const model = gltf.scene.clone();
|
||||||
|
model.userData = { name: selectedItem.name, modelId: selectedItem.id };
|
||||||
|
model.position.set(intersectPoint!.x, 3 + intersectPoint!.y, intersectPoint!.z);
|
||||||
|
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
|
||||||
|
|
||||||
|
model.traverse((child: any) => {
|
||||||
|
if (child) {
|
||||||
|
child.castShadow = true;
|
||||||
|
child.receiveShadow = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
itemsGroup.current.add(model);
|
||||||
|
if (tempLoader.current) {
|
||||||
|
(<any>tempLoader.current.material).dispose();
|
||||||
|
(<any>tempLoader.current.geometry).dispose();
|
||||||
|
itemsGroup.current.remove(tempLoader.current);
|
||||||
|
tempLoader.current = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newFloorItem: Types.FloorItemType = {
|
||||||
|
modeluuid: model.uuid,
|
||||||
|
modelname: selectedItem.name,
|
||||||
|
modelfileID: selectedItem.id,
|
||||||
|
position: [intersectPoint!.x, intersectPoint!.y, intersectPoint!.z],
|
||||||
|
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, },
|
||||||
|
isLocked: false,
|
||||||
|
isVisible: true
|
||||||
|
};
|
||||||
|
|
||||||
|
setFloorItems((prevItems) => {
|
||||||
|
const updatedItems = [...(prevItems || []), newFloorItem];
|
||||||
|
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
|
||||||
|
return updatedItems;
|
||||||
|
});
|
||||||
|
|
||||||
|
const email = localStorage.getItem("email");
|
||||||
|
const organization = email ? email.split("@")[1].split(".")[0] : "default";
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// await setFloorItemApi(
|
||||||
|
// organization,
|
||||||
|
// newFloorItem.modeluuid,
|
||||||
|
// newFloorItem.modelname,
|
||||||
|
// newFloorItem.position,
|
||||||
|
// { "x": model.rotation.x, "y": model.rotation.y, "z": model.rotation.z },
|
||||||
|
// newFloorItem.modelfileID!,
|
||||||
|
// false,
|
||||||
|
// true,
|
||||||
|
// );
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
organization,
|
||||||
|
modeluuid: newFloorItem.modeluuid,
|
||||||
|
modelname: newFloorItem.modelname,
|
||||||
|
modelfileID: newFloorItem.modelfileID,
|
||||||
|
position: newFloorItem.position,
|
||||||
|
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
|
||||||
|
isLocked: false,
|
||||||
|
isVisible: true,
|
||||||
|
socketId: socket.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.emit("v1:FloorItems:set", data);
|
||||||
|
|
||||||
|
gsap.to(model.position, { y: newFloorItem.position[1], duration: 1.5, ease: "power2.out" });
|
||||||
|
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: "power2.out", onComplete: () => { toast.success("Model Added!"); } });
|
||||||
|
}
|
||||||
|
|
||||||
|
export default addAssetModel;
|
||||||
153
app/src/modules/builder/geomentries/assets/assetManager.ts
Normal file
153
app/src/modules/builder/geomentries/assets/assetManager.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import * as THREE from "three";
|
||||||
|
import gsap from "gsap";
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||||
|
import { initializeDB, retrieveGLTF, storeGLTF } from "../../../../utils/indexDB/idbUtils";
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||||
|
let currentTaskId = 0; // Track the active task
|
||||||
|
let activePromises = new Map<number, boolean>(); // Map to track task progress
|
||||||
|
|
||||||
|
export default async function assetManager(
|
||||||
|
data: any,
|
||||||
|
itemsGroup: Types.RefGroup,
|
||||||
|
loader: GLTFLoader,
|
||||||
|
) {
|
||||||
|
const taskId = ++currentTaskId; // Increment taskId for each call
|
||||||
|
activePromises.set(taskId, true); // Mark task as active
|
||||||
|
|
||||||
|
// console.log("Received message from worker:", data);
|
||||||
|
|
||||||
|
if (data.toRemove.length > 0) {
|
||||||
|
data.toRemove.forEach((uuid: string) => {
|
||||||
|
const item = itemsGroup.current.getObjectByProperty("uuid", uuid);
|
||||||
|
if (item) {
|
||||||
|
// Traverse and dispose of resources
|
||||||
|
// item.traverse((child: THREE.Object3D) => {
|
||||||
|
// if (child instanceof THREE.Mesh) {
|
||||||
|
// if (child.geometry) child.geometry.dispose();
|
||||||
|
// if (Array.isArray(child.material)) {
|
||||||
|
// child.material.forEach((material) => {
|
||||||
|
// if (material.map) material.map.dispose();
|
||||||
|
// material.dispose();
|
||||||
|
// });
|
||||||
|
// } else if (child.material) {
|
||||||
|
// if (child.material.map) child.material.map.dispose();
|
||||||
|
// child.material.dispose();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// Remove the object from the scene
|
||||||
|
itemsGroup.current.remove(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.toAdd.length > 0) {
|
||||||
|
await initializeDB();
|
||||||
|
|
||||||
|
for (const item of data.toAdd) {
|
||||||
|
if (!activePromises.get(taskId)) return; // Stop processing if task is canceled
|
||||||
|
|
||||||
|
await new Promise<void>(async (resolve) => {
|
||||||
|
const modelUrl = `${url_Backend_dwinzo}/api/v1/AssetFile/${item.modelfileID!}`;
|
||||||
|
|
||||||
|
// Check Three.js Cache
|
||||||
|
const cachedModel = THREE.Cache.get(item.modelfileID!);
|
||||||
|
if (cachedModel) {
|
||||||
|
// console.log(`[Cache] Fetching ${item.modelname}`);
|
||||||
|
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, resolve);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check IndexedDB
|
||||||
|
const indexedDBModel = await retrieveGLTF(item.modelfileID!);
|
||||||
|
if (indexedDBModel) {
|
||||||
|
// console.log(`[IndexedDB] Fetching ${item.modelname}`);
|
||||||
|
const blobUrl = URL.createObjectURL(indexedDBModel);
|
||||||
|
loader.load(
|
||||||
|
blobUrl,
|
||||||
|
(gltf) => {
|
||||||
|
URL.revokeObjectURL(blobUrl);
|
||||||
|
THREE.Cache.remove(blobUrl);
|
||||||
|
THREE.Cache.add(item.modelfileID!, gltf); // Add to cache
|
||||||
|
processLoadedModel(gltf.scene.clone(), item, itemsGroup, resolve);
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
(error) => {
|
||||||
|
toast.error(`[IndexedDB] Error loading ${item.modelname}:`);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch from Backend
|
||||||
|
// console.log(`[Backend] Fetching ${item.modelname}`);
|
||||||
|
loader.load(
|
||||||
|
modelUrl,
|
||||||
|
async (gltf) => {
|
||||||
|
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
||||||
|
await storeGLTF(item.modelfileID!, modelBlob); // Store in IndexedDB
|
||||||
|
THREE.Cache.add(item.modelfileID!, gltf); // Add to cache
|
||||||
|
processLoadedModel(gltf.scene.clone(), item, itemsGroup, resolve);
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
(error) => {
|
||||||
|
toast.error(`[Backend] Error loading ${item.modelname}:`);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function processLoadedModel(
|
||||||
|
gltf: any,
|
||||||
|
item: Types.FloorItemType,
|
||||||
|
itemsGroup: Types.RefGroup,
|
||||||
|
resolve: () => void
|
||||||
|
) {
|
||||||
|
if (!activePromises.get(taskId)) return; // Stop processing if task is canceled
|
||||||
|
|
||||||
|
const existingModel = itemsGroup.current.getObjectByProperty("uuid", item.modeluuid);
|
||||||
|
if (existingModel) {
|
||||||
|
// console.log(`Model ${item.modelname} already exists in the scene.`);
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const model = gltf;
|
||||||
|
model.uuid = item.modeluuid;
|
||||||
|
model.userData = { name: item.modelname, modelId: item.modelfileID };
|
||||||
|
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
|
||||||
|
model.position.set(...item.position);
|
||||||
|
model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z);
|
||||||
|
|
||||||
|
model.traverse((child: any) => {
|
||||||
|
if (child.isMesh) {
|
||||||
|
// Clone the material to ensure changes are independent
|
||||||
|
// child.material = child.material.clone();
|
||||||
|
|
||||||
|
child.castShadow = true;
|
||||||
|
child.receiveShadow = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
itemsGroup?.current?.add(model);
|
||||||
|
|
||||||
|
gsap.to(model.position, { y: item.position[1], duration: 1.5, ease: "power2.out" });
|
||||||
|
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 0.5, ease: "power2.out", onStart: resolve, });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activePromises.delete(taskId); // Mark task as complete
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel ongoing task when new call arrives
|
||||||
|
export function cancelOngoingTasks() {
|
||||||
|
activePromises.clear(); // Clear all ongoing tasks
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
let lastUpdateTime = 0;
|
||||||
|
|
||||||
|
export default function assetVisibility(
|
||||||
|
itemsGroup: Types.RefGroup,
|
||||||
|
cameraPosition: Types.Vector3,
|
||||||
|
renderDistance: Types.Number,
|
||||||
|
throttleTime = 100
|
||||||
|
): void {
|
||||||
|
const now = performance.now();
|
||||||
|
if (now - lastUpdateTime < throttleTime) return;
|
||||||
|
lastUpdateTime = now;
|
||||||
|
|
||||||
|
if (!itemsGroup?.current || !cameraPosition) return;
|
||||||
|
|
||||||
|
itemsGroup.current.children.forEach((child) => {
|
||||||
|
const Distance = cameraPosition.distanceTo(child.position);
|
||||||
|
if (Distance <= renderDistance) {
|
||||||
|
child.visible = true;
|
||||||
|
} else {
|
||||||
|
child.visible = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function DeletableHoveredFloorItems(
|
||||||
|
state: Types.ThreeState,
|
||||||
|
itemsGroup: Types.RefGroup,
|
||||||
|
hoveredDeletableFloorItem: Types.RefMesh,
|
||||||
|
setDeletableFloorItem: any
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Altering the color of the hovered GLTF item during the Deletion time //////////
|
||||||
|
|
||||||
|
state.raycaster.setFromCamera(state.pointer, state.camera);
|
||||||
|
const intersects = state.raycaster.intersectObjects(itemsGroup.current.children, true);
|
||||||
|
|
||||||
|
if (intersects.length > 0) {
|
||||||
|
if (intersects[0].object.name === "Pole") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hoveredDeletableFloorItem.current) {
|
||||||
|
hoveredDeletableFloorItem.current = undefined;
|
||||||
|
setDeletableFloorItem(null);
|
||||||
|
}
|
||||||
|
let currentObject = intersects[0].object;
|
||||||
|
|
||||||
|
while (currentObject) {
|
||||||
|
if (currentObject.name === "Scene") {
|
||||||
|
hoveredDeletableFloorItem.current = currentObject as THREE.Mesh;
|
||||||
|
setDeletableFloorItem(currentObject);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
currentObject = currentObject.parent as THREE.Object3D;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (hoveredDeletableFloorItem.current) {
|
||||||
|
hoveredDeletableFloorItem.current = undefined;
|
||||||
|
setDeletableFloorItem(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeletableHoveredFloorItems;
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
import { getFloorItems } from '../../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
|
||||||
|
// import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi';
|
||||||
|
import { Socket } from 'socket.io-client';
|
||||||
|
|
||||||
|
async function DeleteFloorItems(
|
||||||
|
itemsGroup: Types.RefGroup,
|
||||||
|
hoveredDeletableFloorItem: Types.RefMesh,
|
||||||
|
setFloorItems: Types.setFloorItemSetState,
|
||||||
|
socket: Socket<any>
|
||||||
|
): Promise<void> {
|
||||||
|
|
||||||
|
////////// Deleting the hovered Floor GLTF from the scene (itemsGroup.current) and from the floorItems and also update it in the localstorage //////////
|
||||||
|
|
||||||
|
if (hoveredDeletableFloorItem.current) {
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
const items = await getFloorItems(organization);
|
||||||
|
const removedItem = items.find(
|
||||||
|
(item: { modeluuid: string }) => item.modeluuid === hoveredDeletableFloorItem.current?.uuid
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!removedItem) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// const response = await deleteFloorItem(organization, removedItem.modeluuid, removedItem.modelname);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
organization: organization,
|
||||||
|
modeluuid: removedItem.modeluuid,
|
||||||
|
modelname: removedItem.modelname,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = socket.emit('v1:FloorItems:delete', data)
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
const updatedItems = items.filter(
|
||||||
|
(item: { modeluuid: string }) => item.modeluuid !== hoveredDeletableFloorItem.current?.uuid
|
||||||
|
);
|
||||||
|
|
||||||
|
const storedItems = JSON.parse(localStorage.getItem("FloorItems") || '[]');
|
||||||
|
const updatedStoredItems = storedItems.filter((item: { modeluuid: string }) => item.modeluuid !== hoveredDeletableFloorItem.current?.uuid);
|
||||||
|
localStorage.setItem("FloorItems", JSON.stringify(updatedStoredItems));
|
||||||
|
|
||||||
|
if (hoveredDeletableFloorItem.current) {
|
||||||
|
// Traverse and dispose of resources
|
||||||
|
hoveredDeletableFloorItem.current.traverse((child: THREE.Object3D) => {
|
||||||
|
if (child instanceof THREE.Mesh) {
|
||||||
|
if (child.geometry) child.geometry.dispose();
|
||||||
|
if (Array.isArray(child.material)) {
|
||||||
|
child.material.forEach((material) => {
|
||||||
|
if (material.map) material.map.dispose();
|
||||||
|
material.dispose();
|
||||||
|
});
|
||||||
|
} else if (child.material) {
|
||||||
|
if (child.material.map) child.material.map.dispose();
|
||||||
|
child.material.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove the object from the scene
|
||||||
|
itemsGroup.current.remove(hoveredDeletableFloorItem.current);
|
||||||
|
}
|
||||||
|
setFloorItems(updatedItems);
|
||||||
|
toast.success("Model Removed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeleteFloorItems;
|
||||||
29
app/src/modules/builder/geomentries/assets/tempLoader.ts
Normal file
29
app/src/modules/builder/geomentries/assets/tempLoader.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function TempLoader(
|
||||||
|
intersectPoint: Types.Vector3,
|
||||||
|
isTempLoader: Types.RefBoolean,
|
||||||
|
tempLoader: Types.RefMesh,
|
||||||
|
itemsGroup: Types.RefGroup
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Temporary Loader that indicates the gltf is being loaded //////////
|
||||||
|
|
||||||
|
////////// Bug: Can't Load More than one TempLoader if done, it won't leave the scene //////////
|
||||||
|
|
||||||
|
if (tempLoader.current) {
|
||||||
|
itemsGroup.current.remove(tempLoader.current);
|
||||||
|
}
|
||||||
|
if (isTempLoader.current) {
|
||||||
|
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
|
||||||
|
const cubeMaterial = new THREE.MeshBasicMaterial({ color: "white" });
|
||||||
|
tempLoader.current = new THREE.Mesh(cubeGeometry, cubeMaterial);
|
||||||
|
tempLoader.current.position.set(intersectPoint.x, 0.5 + intersectPoint.y, intersectPoint.z);
|
||||||
|
itemsGroup.current.add(tempLoader.current);
|
||||||
|
isTempLoader.current = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TempLoader;
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
import * as CONSTANTS from "../../../../types/world/worldConstants";
|
||||||
|
|
||||||
|
import texturePath from "../../../../assets/textures/floor/concreteFloorWorn001Diff2k.jpg";
|
||||||
|
import normalPath from "../../../../assets/textures/floor/concreteFloorWorn001NorGl2k.jpg";
|
||||||
|
|
||||||
|
// Cache for materials
|
||||||
|
const materialCache = new Map<string, THREE.Material>();
|
||||||
|
|
||||||
|
export default function addFloorToScene(
|
||||||
|
shape: THREE.Shape,
|
||||||
|
layer: number,
|
||||||
|
floorGroup: Types.RefGroup,
|
||||||
|
userData: any,
|
||||||
|
) {
|
||||||
|
const textureLoader = new THREE.TextureLoader();
|
||||||
|
|
||||||
|
const textureScale = CONSTANTS.floorConfig.textureScale;
|
||||||
|
|
||||||
|
const materialKey = `floorMaterial_${textureScale}`;
|
||||||
|
|
||||||
|
let material: THREE.Material;
|
||||||
|
|
||||||
|
if (materialCache.has(materialKey)) {
|
||||||
|
material = materialCache.get(materialKey) as THREE.Material;
|
||||||
|
} else {
|
||||||
|
const floorTexture = textureLoader.load(texturePath);
|
||||||
|
const normalMap = textureLoader.load(normalPath);
|
||||||
|
|
||||||
|
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
|
||||||
|
floorTexture.repeat.set(textureScale, textureScale);
|
||||||
|
floorTexture.colorSpace = THREE.SRGBColorSpace;
|
||||||
|
|
||||||
|
normalMap.wrapS = normalMap.wrapT = THREE.RepeatWrapping;
|
||||||
|
normalMap.repeat.set(textureScale, textureScale);
|
||||||
|
|
||||||
|
material = new THREE.MeshStandardMaterial({
|
||||||
|
map: floorTexture,
|
||||||
|
normalMap: normalMap,
|
||||||
|
side: THREE.DoubleSide,
|
||||||
|
});
|
||||||
|
|
||||||
|
materialCache.set(materialKey, material);
|
||||||
|
}
|
||||||
|
|
||||||
|
const extrudeSettings = {
|
||||||
|
depth: CONSTANTS.floorConfig.height,
|
||||||
|
bevelEnabled: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
||||||
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
|
||||||
|
mesh.receiveShadow = true;
|
||||||
|
mesh.position.y = layer;
|
||||||
|
mesh.rotateX(Math.PI / 2);
|
||||||
|
mesh.name = `Floor_Layer_${layer}`;
|
||||||
|
|
||||||
|
// Store UUIDs for debugging or future processing
|
||||||
|
mesh.userData.uuids = userData;
|
||||||
|
|
||||||
|
floorGroup.current.add(mesh);
|
||||||
|
}
|
||||||
179
app/src/modules/builder/geomentries/floors/drawOnlyFloor.ts
Normal file
179
app/src/modules/builder/geomentries/floors/drawOnlyFloor.ts
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
|
||||||
|
import addPointToScene from '../points/addPointToScene';
|
||||||
|
import addLineToScene from '../lines/addLineToScene';
|
||||||
|
import splitLine from '../lines/splitLine';
|
||||||
|
import removeReferenceLine from '../lines/removeReferenceLine';
|
||||||
|
import getClosestIntersection from '../lines/getClosestIntersection';
|
||||||
|
import arrayLineToObject from '../lines/lineConvertions/arrayLineToObject';
|
||||||
|
// import { setLine } from '../../../../services/factoryBuilder/lines/setLineApi';
|
||||||
|
import { Socket } from 'socket.io-client';
|
||||||
|
|
||||||
|
async function drawOnlyFloor(
|
||||||
|
raycaster: THREE.Raycaster,
|
||||||
|
state: Types.ThreeState,
|
||||||
|
camera: THREE.Camera,
|
||||||
|
plane: Types.RefMesh,
|
||||||
|
floorPlanGroupPoint: Types.RefGroup,
|
||||||
|
snappedPoint: Types.RefVector3,
|
||||||
|
isSnapped: Types.RefBoolean,
|
||||||
|
isSnappedUUID: Types.RefString,
|
||||||
|
line: Types.RefLine,
|
||||||
|
ispreSnapped: Types.RefBoolean,
|
||||||
|
anglesnappedPoint: Types.RefVector3,
|
||||||
|
isAngleSnapped: Types.RefBoolean,
|
||||||
|
onlyFloorline: Types.RefOnlyFloorLine,
|
||||||
|
onlyFloorlines: Types.RefOnlyFloorLines,
|
||||||
|
lines: Types.RefLines,
|
||||||
|
floorPlanGroupLine: Types.RefGroup,
|
||||||
|
floorPlanGroup: Types.RefGroup,
|
||||||
|
ReferenceLineMesh: Types.RefMesh,
|
||||||
|
LineCreated: Types.RefBoolean,
|
||||||
|
currentLayerPoint: Types.RefMeshArray,
|
||||||
|
dragPointControls: Types.RefDragControl,
|
||||||
|
setNewLines: any,
|
||||||
|
setDeletedLines: any,
|
||||||
|
activeLayer: Types.Number,
|
||||||
|
socket: Socket<any>
|
||||||
|
): Promise<void> {
|
||||||
|
|
||||||
|
////////// Creating lines Based on the positions clicked //////////
|
||||||
|
|
||||||
|
if (!plane.current) return
|
||||||
|
const intersects = raycaster.intersectObject(plane.current, true);
|
||||||
|
const intersectsLines = raycaster.intersectObjects(floorPlanGroupLine.current.children, true);
|
||||||
|
const intersectsPoint = raycaster.intersectObjects(floorPlanGroupPoint.current.children, true);
|
||||||
|
const VisibleintersectsPoint = intersectsPoint.find(intersect => intersect.object.visible);
|
||||||
|
const visibleIntersect = intersectsLines.find(intersect => intersect.object.visible && intersect.object.name !== CONSTANTS.lineConfig.referenceName);
|
||||||
|
|
||||||
|
if ((intersectsPoint.length === 0 || VisibleintersectsPoint === undefined) && intersectsLines.length > 0 && !isSnapped.current && !ispreSnapped.current) {
|
||||||
|
|
||||||
|
////////// Clicked on a preexisting Line //////////
|
||||||
|
|
||||||
|
if (visibleIntersect && (intersectsLines[0].object.userData.linePoints[0][3] === CONSTANTS.lineConfig.floorName || intersectsLines[0].object.userData.linePoints[0][3] === CONSTANTS.lineConfig.wallName)) {
|
||||||
|
let pointColor, lineColor;
|
||||||
|
if (intersectsLines[0].object.userData.linePoints[0][3] === CONSTANTS.lineConfig.wallName) {
|
||||||
|
pointColor = CONSTANTS.pointConfig.wallOuterColor;
|
||||||
|
lineColor = CONSTANTS.lineConfig.wallColor;
|
||||||
|
} else {
|
||||||
|
pointColor = CONSTANTS.pointConfig.floorOuterColor;
|
||||||
|
lineColor = CONSTANTS.lineConfig.floorColor;
|
||||||
|
}
|
||||||
|
let IntersectsPoint = new THREE.Vector3(intersects[0].point.x, 0.01, intersects[0].point.z);
|
||||||
|
if (isAngleSnapped.current && line.current.length > 0 && anglesnappedPoint.current) {
|
||||||
|
IntersectsPoint = anglesnappedPoint.current;
|
||||||
|
}
|
||||||
|
if (visibleIntersect.object instanceof THREE.Mesh) {
|
||||||
|
const ThroughPoint = (visibleIntersect.object.geometry.parameters.path).getPoints(CONSTANTS.lineConfig.lineIntersectionPoints);
|
||||||
|
let intersectionPoint = getClosestIntersection(ThroughPoint, IntersectsPoint);
|
||||||
|
|
||||||
|
if (intersectionPoint) {
|
||||||
|
|
||||||
|
const newLines = splitLine(visibleIntersect, intersectionPoint, currentLayerPoint, floorPlanGroupPoint, dragPointControls, isSnappedUUID, lines, setDeletedLines, floorPlanGroupLine, socket, pointColor, lineColor, intersectsLines[0].object.userData.linePoints[0][3]);
|
||||||
|
setNewLines([newLines[0], newLines[1]]);
|
||||||
|
|
||||||
|
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), isSnappedUUID.current!, activeLayer, CONSTANTS.lineConfig.floorName]);
|
||||||
|
|
||||||
|
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
|
||||||
|
lines.current.push(line.current as Types.Line);
|
||||||
|
const data = arrayLineToObject(line.current as Types.Line);
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// setLine(organization, data.layer!, data.line!, data.type!);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
organization: organization,
|
||||||
|
layer: data.layer,
|
||||||
|
line: data.line,
|
||||||
|
type: data.type,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:Line:create', input);
|
||||||
|
|
||||||
|
setNewLines([newLines[0], newLines[1], line.current]);
|
||||||
|
onlyFloorline.current.push(line.current as Types.Line);
|
||||||
|
onlyFloorlines.current.push(onlyFloorline.current);
|
||||||
|
onlyFloorline.current = [];
|
||||||
|
|
||||||
|
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.floorColor, line.current, floorPlanGroupLine);
|
||||||
|
|
||||||
|
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (intersects.length > 0 && intersectsLines.length === 0) {
|
||||||
|
|
||||||
|
////////// Clicked on an empty place or a point //////////
|
||||||
|
|
||||||
|
let intersectionPoint = intersects[0].point;
|
||||||
|
|
||||||
|
if (isAngleSnapped.current && line.current.length > 0 && anglesnappedPoint.current) {
|
||||||
|
intersectionPoint = anglesnappedPoint.current;
|
||||||
|
}
|
||||||
|
if (isSnapped.current && line.current.length > 0 && snappedPoint.current) {
|
||||||
|
intersectionPoint = snappedPoint.current;
|
||||||
|
}
|
||||||
|
if (ispreSnapped.current && snappedPoint.current) {
|
||||||
|
intersectionPoint = snappedPoint.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSnapped.current && !ispreSnapped.current) {
|
||||||
|
addPointToScene(intersectionPoint, CONSTANTS.pointConfig.floorOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, isSnappedUUID, CONSTANTS.lineConfig.floorName);
|
||||||
|
} else {
|
||||||
|
ispreSnapped.current = false;
|
||||||
|
isSnapped.current = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), isSnappedUUID.current!, activeLayer, CONSTANTS.lineConfig.floorName]);
|
||||||
|
|
||||||
|
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
|
||||||
|
onlyFloorline.current.push(line.current as Types.Line);
|
||||||
|
lines.current.push(line.current as Types.Line);
|
||||||
|
const data = arrayLineToObject(line.current as Types.Line);
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// setLine(organization, data.layer!, data.line!, data.type!);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
organization: organization,
|
||||||
|
layer: data.layer,
|
||||||
|
line: data.line,
|
||||||
|
type: data.type,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:Line:create', input);
|
||||||
|
|
||||||
|
setNewLines([line.current]);
|
||||||
|
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.floorColor, line.current, floorPlanGroupLine);
|
||||||
|
const lastPoint = line.current[line.current.length - 1];
|
||||||
|
line.current = [lastPoint];
|
||||||
|
}
|
||||||
|
if (isSnapped.current) { ////////// Add this to stop the drawing mode after snapping //////////
|
||||||
|
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
|
||||||
|
onlyFloorlines.current.push(onlyFloorline.current);
|
||||||
|
onlyFloorline.current = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default drawOnlyFloor;
|
||||||
50
app/src/modules/builder/geomentries/floors/loadFloor.ts
Normal file
50
app/src/modules/builder/geomentries/floors/loadFloor.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
import addRoofToScene from '../roofs/addRoofToScene';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
import loadOnlyFloors from './loadOnlyFloors';
|
||||||
|
import addFloorToScene from './addFloorToScene';
|
||||||
|
import getRoomsFromLines from '../lines/getRoomsFromLines';
|
||||||
|
|
||||||
|
async function loadFloor(
|
||||||
|
lines: Types.RefLines,
|
||||||
|
floorGroup: Types.RefGroup,
|
||||||
|
): Promise<void> {
|
||||||
|
|
||||||
|
if (!floorGroup.current) return;
|
||||||
|
|
||||||
|
floorGroup.current.children = [];
|
||||||
|
|
||||||
|
if (lines.current.length > 2) {
|
||||||
|
const linesByLayer = lines.current.reduce((acc: { [key: number]: any[] }, pair) => {
|
||||||
|
const layer = pair[0][2];
|
||||||
|
if (!acc[layer]) acc[layer] = [];
|
||||||
|
acc[layer].push(pair);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
for (const layer in linesByLayer) {
|
||||||
|
// Only Floor Polygons
|
||||||
|
loadOnlyFloors(floorGroup, linesByLayer, layer);
|
||||||
|
|
||||||
|
const rooms: Types.Rooms = await getRoomsFromLines({ current: linesByLayer[layer] });
|
||||||
|
|
||||||
|
rooms.forEach(({ coordinates: room, layer }) => {
|
||||||
|
const userData = room.map(point => point.uuid);
|
||||||
|
const shape = new THREE.Shape();
|
||||||
|
shape.moveTo(room[0].position.x, room[0].position.z);
|
||||||
|
room.forEach(point => shape.lineTo(point.position.x, point.position.z));
|
||||||
|
shape.closePath();
|
||||||
|
|
||||||
|
// Floor Polygons
|
||||||
|
addFloorToScene(shape, (layer - 1) * CONSTANTS.wallConfig.height, floorGroup, userData);
|
||||||
|
|
||||||
|
// Roof Polygons
|
||||||
|
addRoofToScene(shape, (layer - 1) * CONSTANTS.wallConfig.height, userData, floorGroup);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default loadFloor;
|
||||||
183
app/src/modules/builder/geomentries/floors/loadOnlyFloors.ts
Normal file
183
app/src/modules/builder/geomentries/floors/loadOnlyFloors.ts
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as turf from '@turf/turf';
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function loadOnlyFloors(
|
||||||
|
floorGroup: Types.RefGroup,
|
||||||
|
linesByLayer: any,
|
||||||
|
layer: any,
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Creating polygon floor based on the onlyFloorlines.current which does not add roof to it, The lines are still stored in Lines.current as well //////////
|
||||||
|
|
||||||
|
let floorsInLayer = linesByLayer[layer];
|
||||||
|
floorsInLayer = floorsInLayer.filter((line: any) => line[0][3] && line[1][3] === CONSTANTS.lineConfig.floorName);
|
||||||
|
const floorResult = floorsInLayer.map((pair: [THREE.Vector3, string, number, string][]) =>
|
||||||
|
pair.map((point) => ({
|
||||||
|
position: [point[0].x, point[0].z],
|
||||||
|
uuid: point[1]
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
const FloorLineFeatures = floorResult.map((line: any) => turf.lineString(line.map((p: any) => p.position)));
|
||||||
|
|
||||||
|
function identifyPolygonsAndConnectedLines(FloorLineFeatures: any) {
|
||||||
|
const floorpolygons = [];
|
||||||
|
const connectedLines = [];
|
||||||
|
const unprocessedLines = [...FloorLineFeatures]; // Copy the features
|
||||||
|
|
||||||
|
while (unprocessedLines.length > 0) {
|
||||||
|
const currentLine = unprocessedLines.pop();
|
||||||
|
const coordinates = currentLine.geometry.coordinates;
|
||||||
|
|
||||||
|
// Check if the line is closed (forms a polygon)
|
||||||
|
if (
|
||||||
|
coordinates[0][0] === coordinates[coordinates.length - 1][0] &&
|
||||||
|
coordinates[0][1] === coordinates[coordinates.length - 1][1]
|
||||||
|
) {
|
||||||
|
floorpolygons.push(turf.polygon([coordinates])); // Add as a polygon
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the line connects to another line
|
||||||
|
let connected = false;
|
||||||
|
for (let i = unprocessedLines.length - 1; i >= 0; i--) {
|
||||||
|
const otherCoordinates = unprocessedLines[i].geometry.coordinates;
|
||||||
|
|
||||||
|
// Check if lines share a start or end point
|
||||||
|
if (
|
||||||
|
coordinates[0][0] === otherCoordinates[otherCoordinates.length - 1][0] &&
|
||||||
|
coordinates[0][1] === otherCoordinates[otherCoordinates.length - 1][1]
|
||||||
|
) {
|
||||||
|
// Merge lines
|
||||||
|
const mergedCoordinates = [...otherCoordinates, ...coordinates.slice(1)];
|
||||||
|
unprocessedLines[i] = turf.lineString(mergedCoordinates);
|
||||||
|
connected = true;
|
||||||
|
break;
|
||||||
|
} else if (
|
||||||
|
coordinates[coordinates.length - 1][0] === otherCoordinates[0][0] &&
|
||||||
|
coordinates[coordinates.length - 1][1] === otherCoordinates[0][1]
|
||||||
|
) {
|
||||||
|
// Merge lines
|
||||||
|
const mergedCoordinates = [...coordinates, ...otherCoordinates.slice(1)];
|
||||||
|
unprocessedLines[i] = turf.lineString(mergedCoordinates);
|
||||||
|
connected = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!connected) {
|
||||||
|
connectedLines.push(currentLine); // Add unconnected line as-is
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { floorpolygons, connectedLines };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { floorpolygons, connectedLines } = identifyPolygonsAndConnectedLines(FloorLineFeatures);
|
||||||
|
|
||||||
|
function convertConnectedLinesToPolygons(connectedLines: any) {
|
||||||
|
return connectedLines.map((line: any) => {
|
||||||
|
const coordinates = line.geometry.coordinates;
|
||||||
|
|
||||||
|
// If the line has more than two points, close the polygon
|
||||||
|
if (coordinates.length > 2) {
|
||||||
|
const firstPoint = coordinates[0];
|
||||||
|
const lastPoint = coordinates[coordinates.length - 1];
|
||||||
|
|
||||||
|
// Check if already closed; if not, close it
|
||||||
|
if (firstPoint[0] !== lastPoint[0] || firstPoint[1] !== lastPoint[1]) {
|
||||||
|
coordinates.push(firstPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the closed line into a polygon
|
||||||
|
return turf.polygon([coordinates]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not enough points for a polygon, return the line unchanged
|
||||||
|
return line;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const convertedConnectedPolygons = convertConnectedLinesToPolygons(connectedLines);
|
||||||
|
|
||||||
|
if (convertedConnectedPolygons.length > 0) {
|
||||||
|
const validPolygons = convertedConnectedPolygons.filter(
|
||||||
|
(polygon: any) => polygon.geometry?.type === "Polygon"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (validPolygons.length > 0) {
|
||||||
|
floorpolygons.push(...validPolygons);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertPolygonsToOriginalFormat(floorpolygons: any, originalLines: [THREE.Vector3, string, number, string][][]) {
|
||||||
|
return floorpolygons.map((polygon: any) => {
|
||||||
|
const coordinates = polygon.geometry.coordinates[0]; // Extract the coordinates array (assume it's a single polygon)
|
||||||
|
|
||||||
|
// Map each coordinate back to its original structure
|
||||||
|
const mappedPoints = coordinates.map((coord: [number, number]) => {
|
||||||
|
const [x, z] = coord;
|
||||||
|
|
||||||
|
// Find the original point matching this coordinate
|
||||||
|
const originalPoint = originalLines.flat().find(([point]) => point.x === x && point.z === z);
|
||||||
|
|
||||||
|
if (!originalPoint) {
|
||||||
|
throw new Error(`Original point for coordinate [${x}, ${z}] not found.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalPoint;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create pairs of consecutive points
|
||||||
|
const pairs: typeof originalLines = [];
|
||||||
|
for (let i = 0; i < mappedPoints.length - 1; i++) {
|
||||||
|
pairs.push([mappedPoints[i], mappedPoints[i + 1]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pairs;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const convertedFloorPolygons: Types.OnlyFloorLines = convertPolygonsToOriginalFormat(floorpolygons, floorsInLayer);
|
||||||
|
|
||||||
|
convertedFloorPolygons.forEach((floor) => {
|
||||||
|
const points: THREE.Vector3[] = [];
|
||||||
|
|
||||||
|
floor.forEach((lineSegment) => {
|
||||||
|
const startPoint = lineSegment[0][0];
|
||||||
|
points.push(new THREE.Vector3(startPoint.x, startPoint.y, startPoint.z));
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastLine = floor[floor.length - 1];
|
||||||
|
const endPoint = lastLine[1][0];
|
||||||
|
points.push(new THREE.Vector3(endPoint.x, endPoint.y, endPoint.z));
|
||||||
|
|
||||||
|
const shape = new THREE.Shape();
|
||||||
|
shape.moveTo(points[0].x, points[0].z);
|
||||||
|
|
||||||
|
points.forEach(point => shape.lineTo(point.x, point.z));
|
||||||
|
shape.closePath();
|
||||||
|
|
||||||
|
const extrudeSettings = {
|
||||||
|
depth: CONSTANTS.floorConfig.height,
|
||||||
|
bevelEnabled: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
||||||
|
const material = new THREE.MeshStandardMaterial({ color: CONSTANTS.floorConfig.defaultColor, side: THREE.DoubleSide });
|
||||||
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
|
||||||
|
mesh.castShadow = true;
|
||||||
|
mesh.receiveShadow = true;
|
||||||
|
|
||||||
|
mesh.position.y = (floor[0][0][2] - 1) * CONSTANTS.wallConfig.height + 0.03;
|
||||||
|
mesh.rotateX(Math.PI / 2);
|
||||||
|
mesh.name = `Only_Floor_Line_${floor[0][0][2]}`;
|
||||||
|
|
||||||
|
mesh.userData = floor;
|
||||||
|
floorGroup?.current?.add(mesh);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default loadOnlyFloors;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function updateFloorLines(
|
||||||
|
onlyFloorlines: Types.RefOnlyFloorLines,
|
||||||
|
DragedPoint: Types.Mesh | { uuid: string, position: Types.Vector3 }
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Update onlyFloorlines.current if it contains the dragged point //////////
|
||||||
|
|
||||||
|
onlyFloorlines.current.forEach((floorline) => {
|
||||||
|
floorline.forEach((line) => {
|
||||||
|
line.forEach((point) => {
|
||||||
|
const [position, uuid] = point;
|
||||||
|
if (uuid === DragedPoint.uuid) {
|
||||||
|
position.x = DragedPoint.position.x;
|
||||||
|
position.y = 0.01;
|
||||||
|
position.z = DragedPoint.position.z;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default updateFloorLines;
|
||||||
89
app/src/modules/builder/geomentries/layers/deleteLayer.ts
Normal file
89
app/src/modules/builder/geomentries/layers/deleteLayer.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import RemoveConnectedLines from '../lines/removeConnectedLines';
|
||||||
|
|
||||||
|
import * as Types from '../../../../types/world/worldTypes';
|
||||||
|
import { Socket } from 'socket.io-client';
|
||||||
|
// import { deleteLayer } from '../../../../services/factoryBuilder/lines/deleteLayerApi';
|
||||||
|
|
||||||
|
async function DeleteLayer(
|
||||||
|
removedLayer: Types.Number,
|
||||||
|
lines: Types.RefLines,
|
||||||
|
floorPlanGroupLine: Types.RefGroup,
|
||||||
|
floorPlanGroupPoint: Types.RefGroup,
|
||||||
|
onlyFloorlines: Types.RefOnlyFloorLines,
|
||||||
|
floorGroup: Types.RefGroup,
|
||||||
|
setDeletedLines: any,
|
||||||
|
setRemovedLayer: Types.setRemoveLayerSetState,
|
||||||
|
socket: Socket<any>
|
||||||
|
): Promise<void> {
|
||||||
|
|
||||||
|
////////// Remove the Lines from the lines.current based on the removed layer and rearrange the layer number that are higher than the removed layer //////////
|
||||||
|
|
||||||
|
const removedLines: Types.Lines = lines.current.filter(line => line[0][2] === removedLayer);
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// await deleteLayer(organization, removedLayer);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
organization: organization,
|
||||||
|
layer: removedLayer,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:Line:delete:layer', data);
|
||||||
|
|
||||||
|
////////// Remove Points and lines from the removed layer //////////
|
||||||
|
|
||||||
|
removedLines.forEach((line) => {
|
||||||
|
line.forEach((removedPoint) => {
|
||||||
|
RemoveConnectedLines(removedPoint[1], floorPlanGroupLine, floorPlanGroupPoint, setDeletedLines, lines);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
////////// Update the remaining lines layer values in the userData and in lines.current //////////
|
||||||
|
|
||||||
|
let remaining = lines.current.filter(line => line[0][2] !== removedLayer);
|
||||||
|
let updatedLines: Types.Lines = [];
|
||||||
|
remaining.forEach(line => {
|
||||||
|
let newLines: Types.Line = [...line];
|
||||||
|
if (newLines[0][2] > removedLayer) {
|
||||||
|
newLines[0][2] -= 1;
|
||||||
|
newLines[1][2] -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchingLine = floorPlanGroupLine.current.children.find(l => l.userData.linePoints[0][1] === line[0][1] && l.userData.linePoints[1][1] === line[1][1]);
|
||||||
|
if (matchingLine) {
|
||||||
|
const updatedUserData = matchingLine.userData;
|
||||||
|
updatedUserData.linePoints[0][2] = newLines[0][2];
|
||||||
|
updatedUserData.linePoints[1][2] = newLines[1][2];
|
||||||
|
}
|
||||||
|
updatedLines.push(newLines);
|
||||||
|
});
|
||||||
|
|
||||||
|
lines.current = updatedLines;
|
||||||
|
localStorage.setItem("Lines", JSON.stringify(lines.current));
|
||||||
|
|
||||||
|
////////// Also remove OnlyFloorLines and update it in localstorage //////////
|
||||||
|
|
||||||
|
onlyFloorlines.current = onlyFloorlines.current.filter((floor) => {
|
||||||
|
return floor[0][0][2] !== removedLayer;
|
||||||
|
});
|
||||||
|
const meshToRemove: any = floorGroup.current?.children.find((mesh) =>
|
||||||
|
mesh.name === `Only_Floor_Line_${removedLayer}`
|
||||||
|
);
|
||||||
|
if (meshToRemove) {
|
||||||
|
(<any>meshToRemove.material).dispose();
|
||||||
|
(<any>meshToRemove.geometry).dispose();
|
||||||
|
floorGroup.current?.remove(meshToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success("Layer Removed!");
|
||||||
|
setRemovedLayer(null);
|
||||||
|
}
|
||||||
|
export default DeleteLayer;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function Layer2DVisibility(
|
||||||
|
activeLayer: Types.Number,
|
||||||
|
floorPlanGroup: Types.RefGroup,
|
||||||
|
floorPlanGroupLine: Types.RefGroup,
|
||||||
|
floorPlanGroupPoint: Types.RefGroup,
|
||||||
|
currentLayerPoint: Types.RefMeshArray,
|
||||||
|
dragPointControls: Types.RefDragControl
|
||||||
|
): void {
|
||||||
|
|
||||||
|
if (floorPlanGroup.current && dragPointControls.current) {
|
||||||
|
currentLayerPoint.current = [];
|
||||||
|
floorPlanGroupLine.current.children.forEach((line) => {
|
||||||
|
const linePoints = line.userData.linePoints;
|
||||||
|
|
||||||
|
const point1 = floorPlanGroupPoint.current.getObjectByProperty('uuid', linePoints[0][1]) as Types.Mesh;
|
||||||
|
const point2 = floorPlanGroupPoint.current.getObjectByProperty('uuid', linePoints[1][1]) as Types.Mesh;
|
||||||
|
|
||||||
|
if (linePoints[0][2] !== activeLayer && linePoints[1][2] !== activeLayer) {
|
||||||
|
point1.visible = false;
|
||||||
|
point2.visible = false;
|
||||||
|
line.visible = false;
|
||||||
|
} else {
|
||||||
|
point1.visible = true;
|
||||||
|
point2.visible = true;
|
||||||
|
line.visible = true;
|
||||||
|
currentLayerPoint.current.push(point1, point2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dragPointControls.current!.objects = currentLayerPoint.current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layer2DVisibility;
|
||||||
24
app/src/modules/builder/geomentries/lines/addLineToScene.ts
Normal file
24
app/src/modules/builder/geomentries/lines/addLineToScene.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import * as THREE from "three";
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function addLineToScene(
|
||||||
|
start: Types.Vector3,
|
||||||
|
end: Types.Vector3,
|
||||||
|
colour: Types.Color,
|
||||||
|
userData: Types.UserData,
|
||||||
|
floorPlanGroupLine: Types.RefGroup
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// A function that creates and adds lines based on the start, end, and colour from the params, Also adds the userData in the mesh userData //////////
|
||||||
|
|
||||||
|
const path = new THREE.CatmullRomCurve3([start, end]);
|
||||||
|
const geometry = new THREE.TubeGeometry(path, CONSTANTS.lineConfig.tubularSegments, CONSTANTS.lineConfig.radius, CONSTANTS.lineConfig.radialSegments, false);
|
||||||
|
const material = new THREE.MeshBasicMaterial({ color: colour });
|
||||||
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
floorPlanGroupLine.current.add(mesh);
|
||||||
|
|
||||||
|
mesh.userData.linePoints = userData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default addLineToScene;
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import * as THREE from "three";
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function createAndMoveReferenceLine(
|
||||||
|
point: Types.Vector3,
|
||||||
|
cursorPosition: Types.Vector3,
|
||||||
|
isSnapped: Types.RefBoolean,
|
||||||
|
ispreSnapped: Types.RefBoolean,
|
||||||
|
line: Types.RefLine,
|
||||||
|
setRefTextUpdate: Types.NumberIncrementState,
|
||||||
|
floorPlanGroup: Types.RefGroup,
|
||||||
|
ReferenceLineMesh: Types.RefMesh,
|
||||||
|
LineCreated: Types.RefBoolean,
|
||||||
|
Tube: Types.RefTubeGeometry,
|
||||||
|
anglesnappedPoint: Types.RefVector3,
|
||||||
|
isAngleSnapped: Types.RefBoolean
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Creating new and maintaining the old reference line and also snap the reference line based on its angle //////////
|
||||||
|
|
||||||
|
const startPoint = point;
|
||||||
|
|
||||||
|
const dx = cursorPosition.x - startPoint.x;
|
||||||
|
const dz = cursorPosition.z - startPoint.z;
|
||||||
|
let angle = Math.atan2(dz, dx);
|
||||||
|
|
||||||
|
angle = (angle * 180) / Math.PI;
|
||||||
|
angle = (angle + 360) % 360;
|
||||||
|
|
||||||
|
const snapAngles = [0, 90, 180, 270, 360];
|
||||||
|
const snapThreshold = 2.5;
|
||||||
|
|
||||||
|
const closestSnapAngle = snapAngles.reduce((prev, curr) =>
|
||||||
|
Math.abs(curr - angle) < Math.abs(prev - angle) ? curr : prev
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isSnapped.current && !ispreSnapped.current && line.current.length > 0) {
|
||||||
|
if (Math.abs(closestSnapAngle - angle) <= snapThreshold) {
|
||||||
|
const snappedAngleRad = (closestSnapAngle * Math.PI) / 180;
|
||||||
|
const distance = Math.sqrt(dx * dx + dz * dz);
|
||||||
|
const snappedX = startPoint.x + distance * Math.cos(snappedAngleRad);
|
||||||
|
const snappedZ = startPoint.z + distance * Math.sin(snappedAngleRad);
|
||||||
|
|
||||||
|
if (
|
||||||
|
cursorPosition.distanceTo(
|
||||||
|
new THREE.Vector3(snappedX, 0.01, snappedZ)
|
||||||
|
) < 2
|
||||||
|
) {
|
||||||
|
cursorPosition.set(snappedX, 0.01, snappedZ);
|
||||||
|
isAngleSnapped.current = true;
|
||||||
|
anglesnappedPoint.current = new THREE.Vector3(
|
||||||
|
snappedX,
|
||||||
|
0.01,
|
||||||
|
snappedZ
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
isAngleSnapped.current = false;
|
||||||
|
anglesnappedPoint.current = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isAngleSnapped.current = false;
|
||||||
|
anglesnappedPoint.current = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isAngleSnapped.current = false;
|
||||||
|
anglesnappedPoint.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!LineCreated.current) {
|
||||||
|
setRefTextUpdate((prevUpdate) => prevUpdate - 1);
|
||||||
|
const path = new THREE.LineCurve3(startPoint, cursorPosition);
|
||||||
|
Tube.current = new THREE.TubeGeometry(path, CONSTANTS.lineConfig.tubularSegments, CONSTANTS.lineConfig.radius, CONSTANTS.lineConfig.radialSegments, false);
|
||||||
|
const material = new THREE.MeshBasicMaterial({ color: CONSTANTS.lineConfig.helperColor });
|
||||||
|
ReferenceLineMesh.current = new THREE.Mesh(Tube.current, material);
|
||||||
|
ReferenceLineMesh.current.name = CONSTANTS.lineConfig.referenceName;
|
||||||
|
ReferenceLineMesh.current.userData = {
|
||||||
|
linePoints: { startPoint, cursorPosition },
|
||||||
|
};
|
||||||
|
floorPlanGroup.current?.add(ReferenceLineMesh.current);
|
||||||
|
LineCreated.current = true;
|
||||||
|
} else {
|
||||||
|
if (ReferenceLineMesh.current) {
|
||||||
|
const path = new THREE.LineCurve3(startPoint, new THREE.Vector3(cursorPosition.x, 0.01, cursorPosition.z));
|
||||||
|
Tube.current = new THREE.TubeGeometry(path, CONSTANTS.lineConfig.tubularSegments, CONSTANTS.lineConfig.radius, CONSTANTS.lineConfig.radialSegments, false);
|
||||||
|
|
||||||
|
if (ReferenceLineMesh.current) {
|
||||||
|
ReferenceLineMesh.current.userData = {
|
||||||
|
linePoints: { startPoint, cursorPosition },
|
||||||
|
};
|
||||||
|
ReferenceLineMesh.current.geometry.dispose();
|
||||||
|
ReferenceLineMesh.current.geometry = Tube.current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createAndMoveReferenceLine;
|
||||||
88
app/src/modules/builder/geomentries/lines/deleteLine.ts
Normal file
88
app/src/modules/builder/geomentries/lines/deleteLine.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { Socket } from "socket.io-client";
|
||||||
|
// import { deleteLineApi } from "../../../../services/factoryBuilder/lines/deleteLineApi";
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
function deleteLine(
|
||||||
|
hoveredDeletableLine: Types.RefMesh,
|
||||||
|
onlyFloorlines: Types.RefOnlyFloorLines,
|
||||||
|
lines: Types.RefLines,
|
||||||
|
floorPlanGroupLine: Types.RefGroup,
|
||||||
|
floorPlanGroupPoint: Types.RefGroup,
|
||||||
|
setDeletedLines: any,
|
||||||
|
socket: Socket<any>
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Deleting a line and the points if they are not connected to any other line //////////
|
||||||
|
|
||||||
|
if (!hoveredDeletableLine.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const linePoints = hoveredDeletableLine.current.userData.linePoints;
|
||||||
|
const connectedpoints = [linePoints[0][1], linePoints[1][1]];
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// deleteLineApi(
|
||||||
|
// organization,
|
||||||
|
// [
|
||||||
|
// { "uuid": linePoints[0][1] },
|
||||||
|
// { "uuid": linePoints[1][1] }
|
||||||
|
// ]
|
||||||
|
// )
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
organization: organization,
|
||||||
|
line: [
|
||||||
|
{ "uuid": linePoints[0][1] },
|
||||||
|
{ "uuid": linePoints[1][1] }
|
||||||
|
],
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:Line:delete', data);
|
||||||
|
|
||||||
|
|
||||||
|
onlyFloorlines.current = onlyFloorlines.current.map(floorline =>
|
||||||
|
floorline.filter(line => line[0][1] !== connectedpoints[0] && line[1][1] !== connectedpoints[1])
|
||||||
|
).filter(floorline => floorline.length > 0);
|
||||||
|
|
||||||
|
lines.current = lines.current.filter(item => item !== linePoints);
|
||||||
|
(<any>hoveredDeletableLine.current.material).dispose();
|
||||||
|
(<any>hoveredDeletableLine.current.geometry).dispose();
|
||||||
|
floorPlanGroupLine.current.remove(hoveredDeletableLine.current);
|
||||||
|
setDeletedLines([linePoints]);
|
||||||
|
|
||||||
|
connectedpoints.forEach((pointUUID) => {
|
||||||
|
let isConnected = false;
|
||||||
|
floorPlanGroupLine.current.children.forEach((line) => {
|
||||||
|
const linePoints = line.userData.linePoints;
|
||||||
|
const uuid1 = linePoints[0][1];
|
||||||
|
const uuid2 = linePoints[1][1];
|
||||||
|
if (uuid1 === pointUUID || uuid2 === pointUUID) {
|
||||||
|
isConnected = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isConnected) {
|
||||||
|
floorPlanGroupPoint.current.children.forEach((point: any) => {
|
||||||
|
if (point.uuid === pointUUID) {
|
||||||
|
(<any>point.material).dispose();
|
||||||
|
(<any>point.geometry).dispose();
|
||||||
|
floorPlanGroupPoint.current.remove(point);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success("Line Removed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deleteLine;
|
||||||
90
app/src/modules/builder/geomentries/lines/distanceText.tsx
Normal file
90
app/src/modules/builder/geomentries/lines/distanceText.tsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { getLines } from "../../../../services/factoryBuilder/lines/getLinesApi";
|
||||||
|
import * as THREE from "three";
|
||||||
|
import { useActiveLayer, useDeletedLines, useNewLines, useToggleView } from "../../../../store/store";
|
||||||
|
import objectLinesToArray from "./lineConvertions/objectLinesToArray";
|
||||||
|
import { Html } from "@react-three/drei";
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
const DistanceText = () => {
|
||||||
|
const [lines, setLines] = useState<{ distance: string; position: THREE.Vector3; userData: Types.Line; layer: string }[]>([]);
|
||||||
|
const { activeLayer } = useActiveLayer();
|
||||||
|
const { toggleView } = useToggleView();
|
||||||
|
const { newLines, setNewLines } = useNewLines();
|
||||||
|
const { deletedLines, setDeletedLines } = useDeletedLines();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
if (!email) return;
|
||||||
|
const organization = (email.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
getLines(organization).then((data) => {
|
||||||
|
data = objectLinesToArray(data);
|
||||||
|
|
||||||
|
const lines = data.filter((line: Types.Line) => line[0][2] === activeLayer)
|
||||||
|
.map((line: Types.Line) => {
|
||||||
|
const point1 = new THREE.Vector3(line[0][0].x, line[0][0].y, line[0][0].z);
|
||||||
|
const point2 = new THREE.Vector3(line[1][0].x, line[1][0].y, line[1][0].z);
|
||||||
|
const distance = point1.distanceTo(point2);
|
||||||
|
const midpoint = new THREE.Vector3().addVectors(point1, point2).divideScalar(2);
|
||||||
|
return {
|
||||||
|
distance: distance.toFixed(1),
|
||||||
|
position: midpoint,
|
||||||
|
userData: line,
|
||||||
|
layer: activeLayer,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setLines(lines)
|
||||||
|
})
|
||||||
|
}, [activeLayer])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (newLines.length > 0) {
|
||||||
|
if (newLines[0][0][2] !== activeLayer) return;
|
||||||
|
const newLinesData = newLines.map((line: Types.Line) => {
|
||||||
|
const point1 = new THREE.Vector3(line[0][0].x, line[0][0].y, line[0][0].z);
|
||||||
|
const point2 = new THREE.Vector3(line[1][0].x, line[1][0].y, line[1][0].z);
|
||||||
|
const distance = point1.distanceTo(point2);
|
||||||
|
const midpoint = new THREE.Vector3().addVectors(point1, point2).divideScalar(2);
|
||||||
|
|
||||||
|
return {
|
||||||
|
distance: distance.toFixed(1),
|
||||||
|
position: midpoint,
|
||||||
|
userData: line,
|
||||||
|
layer: activeLayer,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setLines((prevLines) => [...prevLines, ...newLinesData]);
|
||||||
|
setNewLines([]);
|
||||||
|
}
|
||||||
|
}, [newLines, activeLayer]);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if ((deletedLines as Types.Lines).length > 0) {
|
||||||
|
setLines((prevLines) =>
|
||||||
|
prevLines.filter(
|
||||||
|
(line) => !deletedLines.some((deletedLine: any) => deletedLine[0][1] === line.userData[0][1] && deletedLine[1][1] === line.userData[1][1])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
setDeletedLines([]);
|
||||||
|
}
|
||||||
|
}, [deletedLines]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{toggleView && (
|
||||||
|
<group name='Distance_Text'>
|
||||||
|
{lines.map((text) => (
|
||||||
|
<Html key={`${text.userData[0][1]}_${text.userData[1][1]}`} transform sprite userData={text.userData} scale={5} position={[text.position.x, 1, text.position.z]} style={{ pointerEvents: 'none' }} >
|
||||||
|
<div key={`${text.userData[0][1]}_${text.userData[1][1]}`} className={`Distance line-${text.userData[0][1]}_${text.userData[1][1]}_${text.layer}`} >{text.distance} m</div>
|
||||||
|
</Html>
|
||||||
|
))}
|
||||||
|
</group>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DistanceText;
|
||||||
167
app/src/modules/builder/geomentries/lines/drawWall.ts
Normal file
167
app/src/modules/builder/geomentries/lines/drawWall.ts
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
|
||||||
|
import addPointToScene from '../points/addPointToScene';
|
||||||
|
import addLineToScene from './addLineToScene';
|
||||||
|
import splitLine from './splitLine';
|
||||||
|
import removeReferenceLine from './removeReferenceLine';
|
||||||
|
import getClosestIntersection from './getClosestIntersection';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
import arrayLineToObject from './lineConvertions/arrayLineToObject';
|
||||||
|
// import { setLine } from '../../../../services/factoryBuilder/lines/setLineApi';
|
||||||
|
import { Socket } from 'socket.io-client';
|
||||||
|
|
||||||
|
async function drawWall(
|
||||||
|
raycaster: THREE.Raycaster,
|
||||||
|
plane: Types.RefMesh,
|
||||||
|
floorPlanGroupPoint: Types.RefGroup,
|
||||||
|
snappedPoint: Types.RefVector3,
|
||||||
|
isSnapped: Types.RefBoolean,
|
||||||
|
isSnappedUUID: Types.RefString,
|
||||||
|
line: Types.RefLine,
|
||||||
|
ispreSnapped: Types.RefBoolean,
|
||||||
|
anglesnappedPoint: Types.RefVector3,
|
||||||
|
isAngleSnapped: Types.RefBoolean,
|
||||||
|
lines: Types.RefLines,
|
||||||
|
floorPlanGroupLine: Types.RefGroup,
|
||||||
|
floorPlanGroup: Types.RefGroup,
|
||||||
|
ReferenceLineMesh: Types.RefMesh,
|
||||||
|
LineCreated: Types.RefBoolean,
|
||||||
|
currentLayerPoint: Types.RefMeshArray,
|
||||||
|
dragPointControls: Types.RefDragControl,
|
||||||
|
setNewLines: any,
|
||||||
|
setDeletedLines: any,
|
||||||
|
activeLayer: Types.Number,
|
||||||
|
socket: Socket<any>
|
||||||
|
): Promise<void> {
|
||||||
|
|
||||||
|
////////// Creating lines Based on the positions clicked //////////
|
||||||
|
|
||||||
|
////////// Allows the user lines that represents walls and roof, floor if forms a polygon //////////
|
||||||
|
|
||||||
|
|
||||||
|
if (!plane.current) return
|
||||||
|
let intersects = raycaster.intersectObject(plane.current, true);
|
||||||
|
|
||||||
|
let intersectsLines = raycaster.intersectObjects(floorPlanGroupLine.current.children, true);
|
||||||
|
let intersectsPoint = raycaster.intersectObjects(floorPlanGroupPoint.current.children, true);
|
||||||
|
|
||||||
|
const VisibleintersectsPoint = intersectsPoint.find(intersect => intersect.object.visible);
|
||||||
|
const visibleIntersect = intersectsLines.find(intersect => intersect.object.visible && intersect.object.name !== CONSTANTS.lineConfig.referenceName && intersect.object.userData.linePoints[0][3] === CONSTANTS.lineConfig.wallName);
|
||||||
|
|
||||||
|
if ((intersectsPoint.length === 0 || VisibleintersectsPoint === undefined) && intersectsLines.length > 0 && !isSnapped.current && !ispreSnapped.current) {
|
||||||
|
|
||||||
|
////////// Clicked on a preexisting Line //////////
|
||||||
|
|
||||||
|
if (visibleIntersect && intersects) {
|
||||||
|
let IntersectsPoint = new THREE.Vector3(intersects[0].point.x, 0.01, intersects[0].point.z);
|
||||||
|
|
||||||
|
if (isAngleSnapped.current && anglesnappedPoint.current) {
|
||||||
|
IntersectsPoint = anglesnappedPoint.current;
|
||||||
|
}
|
||||||
|
if (visibleIntersect.object instanceof THREE.Mesh) {
|
||||||
|
const ThroughPoint = (visibleIntersect.object.geometry.parameters.path).getPoints(CONSTANTS.lineConfig.lineIntersectionPoints);
|
||||||
|
let intersectionPoint = getClosestIntersection(ThroughPoint, IntersectsPoint);
|
||||||
|
|
||||||
|
if (intersectionPoint) {
|
||||||
|
|
||||||
|
const newLines = splitLine(visibleIntersect, intersectionPoint, currentLayerPoint, floorPlanGroupPoint, dragPointControls, isSnappedUUID, lines, setDeletedLines, floorPlanGroupLine, socket, CONSTANTS.pointConfig.wallOuterColor, CONSTANTS.lineConfig.wallColor, CONSTANTS.lineConfig.wallName);
|
||||||
|
setNewLines([newLines[0], newLines[1]]);
|
||||||
|
|
||||||
|
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), isSnappedUUID.current!, activeLayer, CONSTANTS.lineConfig.wallName,]);
|
||||||
|
|
||||||
|
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
|
||||||
|
const data = arrayLineToObject(line.current as Types.Line);
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// setLine(organization, data.layer!, data.line!, data.type!);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
organization: organization,
|
||||||
|
layer: data.layer,
|
||||||
|
line: data.line,
|
||||||
|
type: data.type,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:Line:create', input);
|
||||||
|
|
||||||
|
setNewLines([newLines[0], newLines[1], line.current]);
|
||||||
|
lines.current.push(line.current as Types.Line);
|
||||||
|
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.wallColor, line.current, floorPlanGroupLine);
|
||||||
|
let lastPoint = line.current[line.current.length - 1];
|
||||||
|
line.current = [lastPoint];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intersects && intersects.length > 0) {
|
||||||
|
|
||||||
|
////////// Clicked on a emply place or a point //////////
|
||||||
|
|
||||||
|
let intersectionPoint = intersects[0].point;
|
||||||
|
|
||||||
|
if (isAngleSnapped.current && line.current.length > 0 && anglesnappedPoint.current) {
|
||||||
|
intersectionPoint = anglesnappedPoint.current;
|
||||||
|
}
|
||||||
|
if (isSnapped.current && line.current.length > 0 && snappedPoint.current) {
|
||||||
|
intersectionPoint = snappedPoint.current;
|
||||||
|
}
|
||||||
|
if (ispreSnapped.current && snappedPoint.current) {
|
||||||
|
intersectionPoint = snappedPoint.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSnapped.current && !ispreSnapped.current) {
|
||||||
|
addPointToScene(intersectionPoint, CONSTANTS.pointConfig.wallOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, isSnappedUUID, CONSTANTS.lineConfig.wallName);
|
||||||
|
} else {
|
||||||
|
ispreSnapped.current = false;
|
||||||
|
isSnapped.current = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), isSnappedUUID.current!, activeLayer, CONSTANTS.lineConfig.wallName,]);
|
||||||
|
|
||||||
|
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
|
||||||
|
const data = arrayLineToObject(line.current as Types.Line);
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// setLine(organization, data.layer!, data.line!, data.type!);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
organization: organization,
|
||||||
|
layer: data.layer,
|
||||||
|
line: data.line,
|
||||||
|
type: data.type,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:Line:create', input);
|
||||||
|
|
||||||
|
setNewLines([line.current])
|
||||||
|
lines.current.push(line.current as Types.Line);
|
||||||
|
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.wallColor, line.current, floorPlanGroupLine);
|
||||||
|
let lastPoint = line.current[line.current.length - 1];
|
||||||
|
line.current = [lastPoint];
|
||||||
|
}
|
||||||
|
if (isSnapped.current) {
|
||||||
|
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default drawWall;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function getClosestIntersection(
|
||||||
|
intersects: Types.Vector3Array,
|
||||||
|
point: Types.Vector3
|
||||||
|
): Types.Vector3 | null {
|
||||||
|
|
||||||
|
////////// A function that finds which point is closest from the intersects points that is given, Used in finding which point in a line is closest when clicked on a line during drawing //////////
|
||||||
|
|
||||||
|
let closestNewPoint: THREE.Vector3 | null = null;
|
||||||
|
let minDistance = Infinity;
|
||||||
|
|
||||||
|
for (const intersect of intersects) {
|
||||||
|
const distance = point.distanceTo(intersect);
|
||||||
|
if (distance < minDistance) {
|
||||||
|
minDistance = distance;
|
||||||
|
closestNewPoint = intersect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return closestNewPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getClosestIntersection;
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as turf from '@turf/turf';
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
async function getRoomsFromLines(lines: Types.RefLines) {
|
||||||
|
const rooms: Types.Rooms = [];
|
||||||
|
|
||||||
|
if (lines.current.length > 2) {
|
||||||
|
const linesByLayer = lines.current.reduce((acc: { [key: number]: any[] }, pair) => {
|
||||||
|
const layer = pair[0][2];
|
||||||
|
if (!acc[layer]) acc[layer] = [];
|
||||||
|
acc[layer].push(pair);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
////////// Use turf.polygonize to create polygons from the line points //////////
|
||||||
|
|
||||||
|
for (const layer in linesByLayer) {
|
||||||
|
|
||||||
|
let linesInLayer = linesByLayer[layer];
|
||||||
|
linesInLayer = linesInLayer.filter(line => line[0][3] && line[1][3] === CONSTANTS.lineConfig.wallName);
|
||||||
|
const result = linesInLayer.map((pair: [THREE.Vector3, string, number, string][]) =>
|
||||||
|
pair.map((point) => ({
|
||||||
|
position: [point[0].x, point[0].z],
|
||||||
|
uuid: point[1]
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
const lineFeatures = result.map(line => turf.lineString(line.map(p => p.position)));
|
||||||
|
const polygons = turf.polygonize(turf.featureCollection(lineFeatures));
|
||||||
|
|
||||||
|
let union: any[] = [];
|
||||||
|
|
||||||
|
polygons.features.forEach((feature) => {
|
||||||
|
union.push(feature);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (union.length > 1) {
|
||||||
|
const unionResult = turf.union(turf.featureCollection(union));
|
||||||
|
if (unionResult?.geometry.type === "MultiPolygon") {
|
||||||
|
unionResult?.geometry.coordinates.forEach((poly) => {
|
||||||
|
const Coordinates = poly[0].map(([x, z]) => {
|
||||||
|
const matchingPoint = result.flat().find(r =>
|
||||||
|
r.position[0].toFixed(10) === x.toFixed(10) &&
|
||||||
|
r.position[1].toFixed(10) === z.toFixed(10)
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
position: new THREE.Vector3(x, 0, z),
|
||||||
|
uuid: matchingPoint ? matchingPoint.uuid : ''
|
||||||
|
};
|
||||||
|
});
|
||||||
|
rooms.push({ coordinates: Coordinates.reverse(), layer: parseInt(layer) });
|
||||||
|
});
|
||||||
|
} else if (unionResult?.geometry.type === "Polygon") {
|
||||||
|
const Coordinates = unionResult?.geometry.coordinates[0].map(([x, z]) => {
|
||||||
|
const matchingPoint = result.flat().find(r =>
|
||||||
|
r.position[0].toFixed(10) === x.toFixed(10) &&
|
||||||
|
r.position[1].toFixed(10) === z.toFixed(10)
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
position: new THREE.Vector3(x, 0, z),
|
||||||
|
uuid: matchingPoint ? matchingPoint.uuid : ''
|
||||||
|
};
|
||||||
|
});
|
||||||
|
rooms.push({ coordinates: Coordinates.reverse(), layer: parseInt(layer) });
|
||||||
|
}
|
||||||
|
} else if (union.length === 1) {
|
||||||
|
const Coordinates = union[0].geometry.coordinates[0].map(([x, z]: [number, number]) => {
|
||||||
|
const matchingPoint = result.flat().find(r =>
|
||||||
|
r.position[0].toFixed(10) === x.toFixed(10) &&
|
||||||
|
r.position[1].toFixed(10) === z.toFixed(10)
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
position: new THREE.Vector3(x, 0, z),
|
||||||
|
uuid: matchingPoint ? matchingPoint.uuid : ''
|
||||||
|
};
|
||||||
|
});
|
||||||
|
rooms.push({ coordinates: Coordinates, layer: parseInt(layer) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rooms;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getRoomsFromLines;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import * as Types from "../../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
export default function arrayLineToObject(array: Types.Line) {
|
||||||
|
if (!Array.isArray(array)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract common properties from the first point
|
||||||
|
const commonLayer = array[0][2];
|
||||||
|
const commonType = array[0][3];
|
||||||
|
|
||||||
|
// Map points into a structured format
|
||||||
|
const line = array.map(([position, uuid]) => ({
|
||||||
|
position,
|
||||||
|
uuid,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Create the final structured object
|
||||||
|
return {
|
||||||
|
layer: commonLayer,
|
||||||
|
type: commonType,
|
||||||
|
line,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import * as Types from "../../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
export default function arrayLinesToObject(array: Array<Types.Line>) {
|
||||||
|
if (!Array.isArray(array)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array.map((lineArray) => {
|
||||||
|
if (!Array.isArray(lineArray)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract common properties from the first point
|
||||||
|
const commonLayer = lineArray[0][2];
|
||||||
|
const commonType = lineArray[0][3];
|
||||||
|
|
||||||
|
// Map points into a structured format
|
||||||
|
const line = lineArray.map(([position, uuid]) => ({
|
||||||
|
position,
|
||||||
|
uuid,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Create the final structured object
|
||||||
|
return {
|
||||||
|
layer: commonLayer,
|
||||||
|
type: commonType,
|
||||||
|
line,
|
||||||
|
};
|
||||||
|
}).filter((item) => item !== null); // Filter out invalid entries
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
export default function objectLineToArray(structuredObject: any) {
|
||||||
|
if (!structuredObject || !structuredObject.line) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destructure common properties
|
||||||
|
const { layer, type, line } = structuredObject;
|
||||||
|
|
||||||
|
// Map points back to the original array format
|
||||||
|
return line.map(({ position, uuid }: any) => [new THREE.Vector3(position.x, position.y, position.z), uuid, layer, type]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
export default function objectLinesToArray(structuredObjects: any): any {
|
||||||
|
if (!Array.isArray(structuredObjects)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return structuredObjects.map((structuredObject) => {
|
||||||
|
if (!structuredObject || !structuredObject.line) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { layer, type, line } = structuredObject;
|
||||||
|
|
||||||
|
return line.map(({ position, uuid }: any) => {
|
||||||
|
const vector = new THREE.Vector3(position.x, position.y, position.z);
|
||||||
|
return [vector, uuid, layer, type];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import { Html } from '@react-three/drei';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { useActiveLayer } from '../../../../store/store';
|
||||||
|
|
||||||
|
const ReferenceDistanceText = ({ line }: { line: any }) => {
|
||||||
|
interface TextState {
|
||||||
|
distance: string;
|
||||||
|
position: THREE.Vector3;
|
||||||
|
userData: any;
|
||||||
|
layer: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [text, setTexts] = useState<TextState | null>(null);
|
||||||
|
const { activeLayer } = useActiveLayer();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (line) {
|
||||||
|
if (line.parent === null) {
|
||||||
|
setTexts(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const distance = line.userData.linePoints.cursorPosition.distanceTo(line.userData.linePoints.startPoint);
|
||||||
|
const midpoint = new THREE.Vector3().addVectors(line.userData.linePoints.cursorPosition, line.userData.linePoints.startPoint).divideScalar(2);
|
||||||
|
const newTexts = {
|
||||||
|
distance: distance.toFixed(1),
|
||||||
|
position: midpoint,
|
||||||
|
userData: line,
|
||||||
|
layer: activeLayer
|
||||||
|
};
|
||||||
|
setTexts(newTexts);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group name='Reference_Distance_Text'>
|
||||||
|
<mesh>
|
||||||
|
{text !== null &&
|
||||||
|
< Html transform sprite key={text.distance} userData={text.userData} scale={5} position={[text.position.x, 1, text.position.z]} style={{ pointerEvents: 'none' }}>
|
||||||
|
<div className={`Reference_Distance line-${text.userData.userData}`}>{text.distance} m</div>
|
||||||
|
</Html>
|
||||||
|
}
|
||||||
|
</mesh>
|
||||||
|
</group >
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReferenceDistanceText;
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function RemoveConnectedLines(
|
||||||
|
DeletedPointUUID: Types.String,
|
||||||
|
floorPlanGroupLine: Types.RefGroup,
|
||||||
|
floorPlanGroupPoint: Types.RefGroup,
|
||||||
|
setDeletedLines: any,
|
||||||
|
lines: Types.RefLines,
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Check if any and how many lines are connected to the deleted point //////////
|
||||||
|
|
||||||
|
const removableLines: THREE.Mesh[] = [];
|
||||||
|
const connectedpoints: string[] = [];
|
||||||
|
|
||||||
|
const removedLinePoints: [number, string, number][][] = []; // Array to hold linePoints of removed lines
|
||||||
|
|
||||||
|
floorPlanGroupLine.current.children.forEach((line) => {
|
||||||
|
const linePoints = line.userData.linePoints as [number, string, number][];
|
||||||
|
const uuid1 = linePoints[0][1];
|
||||||
|
const uuid2 = linePoints[1][1];
|
||||||
|
|
||||||
|
if (uuid1 === DeletedPointUUID || uuid2 === DeletedPointUUID) {
|
||||||
|
connectedpoints.push(uuid1 === DeletedPointUUID ? uuid2 : uuid1);
|
||||||
|
removableLines.push(line as THREE.Mesh);
|
||||||
|
removedLinePoints.push(linePoints);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (removableLines.length > 0) {
|
||||||
|
removableLines.forEach((line) => {
|
||||||
|
lines.current = lines.current.filter(item => item !== line.userData.linePoints);
|
||||||
|
(<any>line.material).dispose();
|
||||||
|
(<any>line.geometry).dispose();
|
||||||
|
floorPlanGroupLine.current.remove(line);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setDeletedLines(removedLinePoints)
|
||||||
|
|
||||||
|
////////// Check and Remove point that are no longer connected to any lines //////////
|
||||||
|
|
||||||
|
connectedpoints.forEach((pointUUID) => {
|
||||||
|
let isConnected = false;
|
||||||
|
floorPlanGroupLine.current.children.forEach((line) => {
|
||||||
|
const linePoints = line.userData.linePoints as [number, string, number][];
|
||||||
|
const uuid1 = linePoints[0][1];
|
||||||
|
const uuid2 = linePoints[1][1];
|
||||||
|
if (uuid1 === pointUUID || uuid2 === pointUUID) {
|
||||||
|
isConnected = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!isConnected) {
|
||||||
|
floorPlanGroupPoint.current.children.forEach((point: any) => {
|
||||||
|
if (point.uuid === pointUUID) {
|
||||||
|
(<any>point.material).dispose();
|
||||||
|
(<any>point.geometry).dispose();
|
||||||
|
floorPlanGroupPoint.current.remove(point);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RemoveConnectedLines;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function removeReferenceLine(
|
||||||
|
floorPlanGroup: Types.RefGroup,
|
||||||
|
ReferenceLineMesh: Types.RefMesh,
|
||||||
|
LineCreated: Types.RefBoolean,
|
||||||
|
line: Types.RefLine
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Removes Dangling reference line if the draw mode is ended or any other case //////////
|
||||||
|
|
||||||
|
line.current = [];
|
||||||
|
if (ReferenceLineMesh.current) {
|
||||||
|
(<any>ReferenceLineMesh.current.material).dispose();
|
||||||
|
(<any>ReferenceLineMesh.current.geometry).dispose();
|
||||||
|
floorPlanGroup.current.remove(ReferenceLineMesh.current);
|
||||||
|
LineCreated.current = false;
|
||||||
|
ReferenceLineMesh.current = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default removeReferenceLine;
|
||||||
124
app/src/modules/builder/geomentries/lines/splitLine.ts
Normal file
124
app/src/modules/builder/geomentries/lines/splitLine.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import addLineToScene from './addLineToScene';
|
||||||
|
import addPointToScene from '../points/addPointToScene';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
import arrayLineToObject from '../lines/lineConvertions/arrayLineToObject';
|
||||||
|
import { Socket } from 'socket.io-client';
|
||||||
|
// import { deleteLineApi } from '../../../../services/factoryBuilder/lines/deleteLineApi';
|
||||||
|
// import { setLine } from '../../../../services/factoryBuilder/lines/setLineApi';
|
||||||
|
|
||||||
|
function splitLine(
|
||||||
|
visibleIntersect: Types.IntersectionEvent,
|
||||||
|
intersectionPoint: Types.Vector3,
|
||||||
|
currentLayerPoint: Types.RefMeshArray,
|
||||||
|
floorPlanGroupPoint: Types.RefGroup,
|
||||||
|
dragPointControls: Types.RefDragControl,
|
||||||
|
isSnappedUUID: Types.RefString,
|
||||||
|
lines: Types.RefLines,
|
||||||
|
setDeletedLines: any,
|
||||||
|
floorPlanGroupLine: { current: THREE.Group },
|
||||||
|
socket: Socket<any>,
|
||||||
|
pointColor: Types.String,
|
||||||
|
lineColor: Types.String,
|
||||||
|
lineType: Types.String,
|
||||||
|
): [Types.Line, Types.Line] {
|
||||||
|
|
||||||
|
////////// Removing the clicked line and splitting it with the clicked position adding a new point and two new lines //////////
|
||||||
|
|
||||||
|
|
||||||
|
((visibleIntersect.object as any).material).dispose();
|
||||||
|
((visibleIntersect.object as any).geometry).dispose();
|
||||||
|
floorPlanGroupLine.current.remove(visibleIntersect.object);
|
||||||
|
setDeletedLines([visibleIntersect.object.userData.linePoints]);
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// deleteLineApi(
|
||||||
|
// organization,
|
||||||
|
// [
|
||||||
|
// { "uuid": visibleIntersect.object.userData.linePoints[0][1] },
|
||||||
|
// { "uuid": visibleIntersect.object.userData.linePoints[1][1] }
|
||||||
|
// ]
|
||||||
|
// )
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
organization: organization,
|
||||||
|
line: [
|
||||||
|
{ "uuid": visibleIntersect.object.userData.linePoints[0][1] },
|
||||||
|
{ "uuid": visibleIntersect.object.userData.linePoints[1][1] }
|
||||||
|
],
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:Line:delete', data);
|
||||||
|
|
||||||
|
const point = addPointToScene(intersectionPoint, pointColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, isSnappedUUID, lineType);
|
||||||
|
|
||||||
|
const oldLinePoints = visibleIntersect.object.userData.linePoints;
|
||||||
|
lines.current = lines.current.filter(item => item !== oldLinePoints);
|
||||||
|
|
||||||
|
const clickedPoint: Types.Point = [
|
||||||
|
new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z),
|
||||||
|
point.uuid,
|
||||||
|
oldLinePoints[0][2],
|
||||||
|
lineType
|
||||||
|
];
|
||||||
|
|
||||||
|
const start = oldLinePoints[0];
|
||||||
|
const end = oldLinePoints[1];
|
||||||
|
|
||||||
|
const newLine1: Types.Line = [start, clickedPoint];
|
||||||
|
const newLine2: Types.Line = [clickedPoint, end];
|
||||||
|
|
||||||
|
const line1 = arrayLineToObject(newLine1);
|
||||||
|
const line2 = arrayLineToObject(newLine2);
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// setLine(organization, line1.layer!, line1.line!, line1.type!);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const input1 = {
|
||||||
|
organization: organization,
|
||||||
|
layer: line1.layer,
|
||||||
|
line: line1.line,
|
||||||
|
type: line1.type,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:Line:create', input1);
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// setLine(organization, line2.layer!, line2.line!, line2.type!);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const input2 = {
|
||||||
|
organization: organization,
|
||||||
|
layer: line2.layer,
|
||||||
|
line: line2.line,
|
||||||
|
type: line2.type,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:Line:create', input2);
|
||||||
|
|
||||||
|
lines.current.push(newLine1, newLine2);
|
||||||
|
|
||||||
|
addLineToScene(newLine1[0][0], newLine1[1][0], lineColor, newLine1, floorPlanGroupLine);
|
||||||
|
addLineToScene(newLine2[0][0], newLine2[1][0], lineColor, newLine2, floorPlanGroupLine);
|
||||||
|
|
||||||
|
return [newLine1, newLine2];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default splitLine;
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function updateDistanceText(
|
||||||
|
scene: THREE.Scene,
|
||||||
|
floorPlanGroupLine: Types.RefGroup,
|
||||||
|
affectedLines: Types.NumberArray
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Updating the Distance Texts of the lines that are affected during drag //////////
|
||||||
|
|
||||||
|
const DistanceGroup = scene.children.find((child) => child.name === "Distance_Text") as THREE.Group;
|
||||||
|
|
||||||
|
affectedLines.forEach((lineIndex) => {
|
||||||
|
const mesh = floorPlanGroupLine.current.children[lineIndex] as THREE.Mesh;
|
||||||
|
const linePoints = mesh.userData.linePoints;
|
||||||
|
|
||||||
|
if (linePoints) {
|
||||||
|
const distance = linePoints[0][0].distanceTo(linePoints[1][0]).toFixed(1);
|
||||||
|
const position = new THREE.Vector3().addVectors(linePoints[0][0], linePoints[1][0]).divideScalar(2);
|
||||||
|
|
||||||
|
if (!DistanceGroup || !linePoints) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
DistanceGroup.children.forEach((text) => {
|
||||||
|
const textMesh = text as THREE.Mesh;
|
||||||
|
if (textMesh.userData[0][1] === linePoints[0][1] && textMesh.userData[1][1] === linePoints[1][1]) {
|
||||||
|
textMesh.position.set(position.x, 1, position.z);
|
||||||
|
const className = `Distance line-${textMesh.userData[0][1]}_${textMesh.userData[1][1]}_${linePoints[0][2]}`;
|
||||||
|
const element = document.getElementsByClassName(className)[0] as HTMLElement;
|
||||||
|
if (element) {
|
||||||
|
element.innerHTML = `${distance} m`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default updateDistanceText;
|
||||||
24
app/src/modules/builder/geomentries/lines/updateLines.ts
Normal file
24
app/src/modules/builder/geomentries/lines/updateLines.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
|
||||||
|
function updateLines(
|
||||||
|
floorPlanGroupLine: Types.RefGroup,
|
||||||
|
affectedLines: Types.NumberArray
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Updating the positions for the affected lines only based on the updated positions //////////
|
||||||
|
|
||||||
|
affectedLines.forEach((lineIndex) => {
|
||||||
|
const mesh = floorPlanGroupLine.current.children[lineIndex] as Types.Mesh;
|
||||||
|
const linePoints = mesh.userData.linePoints as Types.Line;
|
||||||
|
if (linePoints) {
|
||||||
|
const newPositions = linePoints.map(([pos]) => pos);
|
||||||
|
const newPath = new THREE.CatmullRomCurve3(newPositions);
|
||||||
|
mesh.geometry.dispose();
|
||||||
|
mesh.geometry = new THREE.TubeGeometry(newPath, CONSTANTS.lineConfig.tubularSegments, CONSTANTS.lineConfig.radius, CONSTANTS.lineConfig.radialSegments, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default updateLines;
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function updateLinesPositions(
|
||||||
|
DragedPoint: Types.Mesh | { uuid: string, position: Types.Vector3 },
|
||||||
|
lines: Types.RefLines
|
||||||
|
): Types.NumberArray {
|
||||||
|
|
||||||
|
////////// Updating the lines position based on the dragged point's position //////////
|
||||||
|
|
||||||
|
const objectUUID = DragedPoint.uuid;
|
||||||
|
const affectedLines: Types.NumberArray = [];
|
||||||
|
|
||||||
|
lines.current.forEach((line, index) => {
|
||||||
|
let lineUpdated = false;
|
||||||
|
line.forEach((point) => {
|
||||||
|
const [position, uuid] = point;
|
||||||
|
if (uuid === objectUUID) {
|
||||||
|
position.x = DragedPoint.position.x;
|
||||||
|
position.y = 0.01;
|
||||||
|
position.z = DragedPoint.position.z;
|
||||||
|
lineUpdated = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (lineUpdated) {
|
||||||
|
affectedLines.push(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return affectedLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default updateLinesPositions;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function vectorizeLinesCurrent(
|
||||||
|
lines: Types.Lines
|
||||||
|
): Types.Lines {
|
||||||
|
|
||||||
|
////////// Storing a vector3 array in localstorage makes the prototype functions go puff. This function brings back the prototype functions by creating it again //////////
|
||||||
|
|
||||||
|
return lines.map((line) => {
|
||||||
|
const p1: Types.Point = [new THREE.Vector3(line[0][0].x, line[0][0].y, line[0][0].z), line[0][1], line[0][2], line[0][3],];
|
||||||
|
const p2: Types.Point = [new THREE.Vector3(line[1][0].x, line[1][0].y, line[1][0].z), line[1][1], line[0][2], line[1][3],];
|
||||||
|
return [p1, p2];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default vectorizeLinesCurrent;
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import updateReferencePolesheight from './updateReferencePolesheight';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function addAndUpdateReferencePillar(
|
||||||
|
raycaster: THREE.Raycaster,
|
||||||
|
floorGroup: Types.RefGroup,
|
||||||
|
referencePole: Types.RefMesh
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Find Pillars position and scale based on the pointer interaction //////////
|
||||||
|
|
||||||
|
let Roofs = raycaster.intersectObjects(floorGroup.current.children, true);
|
||||||
|
const intersected = Roofs.find(intersect => intersect.object.name.includes("Roof") || intersect.object.name.includes("Floor"));
|
||||||
|
|
||||||
|
if (intersected) {
|
||||||
|
const intersectionPoint = intersected.point;
|
||||||
|
raycaster.ray.origin.copy(intersectionPoint);
|
||||||
|
raycaster.ray.direction.set(0, -1, 0);
|
||||||
|
const belowIntersections = raycaster.intersectObjects(floorGroup.current.children, true);
|
||||||
|
const validIntersections = belowIntersections.filter(intersect => intersect.object.name.includes("Floor"));
|
||||||
|
|
||||||
|
let distance: Types.Number;
|
||||||
|
|
||||||
|
if (validIntersections.length > 1) {
|
||||||
|
let valid = validIntersections.find(intersectedBelow => intersected.point.distanceTo(intersectedBelow.point) > 3);
|
||||||
|
if (valid) {
|
||||||
|
updateReferencePolesheight(intersectionPoint, valid.distance, referencePole, floorGroup);
|
||||||
|
} else {
|
||||||
|
const belowPoint = new THREE.Vector3(intersectionPoint.x, 0, intersectionPoint.z);
|
||||||
|
distance = intersected.point.distanceTo(belowPoint);
|
||||||
|
if (distance > 3) {
|
||||||
|
updateReferencePolesheight(intersectionPoint, distance, referencePole, floorGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const belowPoint = new THREE.Vector3(intersectionPoint.x, 0, intersectionPoint.z);
|
||||||
|
distance = intersected.point.distanceTo(belowPoint);
|
||||||
|
if (distance > 3) {
|
||||||
|
updateReferencePolesheight(intersectionPoint, distance, referencePole, floorGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (referencePole.current) {
|
||||||
|
(<any>referencePole.current.material).dispose();
|
||||||
|
(<any>referencePole.current.geometry).dispose();
|
||||||
|
floorGroup.current.remove(referencePole.current);
|
||||||
|
referencePole.current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default addAndUpdateReferencePillar;
|
||||||
24
app/src/modules/builder/geomentries/pillars/addPillar.ts
Normal file
24
app/src/modules/builder/geomentries/pillars/addPillar.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function addPillar(
|
||||||
|
referencePole: Types.RefMesh,
|
||||||
|
floorGroup: Types.RefGroup
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Add Pillars to the scene based on the reference. current poles position and scale //////////
|
||||||
|
|
||||||
|
if (referencePole.current) {
|
||||||
|
let pole: THREE.Mesh;
|
||||||
|
const geometry = referencePole.current.userData.geometry.clone();
|
||||||
|
const material = new THREE.MeshStandardMaterial({ color: CONSTANTS.columnConfig.defaultColor });
|
||||||
|
pole = new THREE.Mesh(geometry, material);
|
||||||
|
pole.rotateX(Math.PI / 2);
|
||||||
|
pole.name = "Pole";
|
||||||
|
pole.position.set(referencePole.current.userData.position.x, referencePole.current.userData.position.y, referencePole.current.userData.position.z);
|
||||||
|
floorGroup.current.add(pole);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default addPillar;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function DeletableHoveredPillar(
|
||||||
|
state: Types.ThreeState,
|
||||||
|
floorGroup: Types.RefGroup,
|
||||||
|
hoveredDeletablePillar: Types.RefMesh
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Altering the color of the hovered Pillar during the Deletion time //////////
|
||||||
|
|
||||||
|
const intersects = state.raycaster.intersectObjects(floorGroup.current.children, true);
|
||||||
|
const poleIntersect = intersects.find(intersect => intersect.object.name === "Pole");
|
||||||
|
|
||||||
|
if (poleIntersect) {
|
||||||
|
if (poleIntersect.object.name !== "Pole") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hoveredDeletablePillar.current) {
|
||||||
|
(hoveredDeletablePillar.current.material as THREE.MeshStandardMaterial).emissive = new THREE.Color("black");
|
||||||
|
hoveredDeletablePillar.current = undefined;
|
||||||
|
}
|
||||||
|
hoveredDeletablePillar.current = poleIntersect.object as THREE.Mesh; // Type assertion
|
||||||
|
(hoveredDeletablePillar.current.material as THREE.MeshStandardMaterial).emissive = new THREE.Color("red");
|
||||||
|
} else {
|
||||||
|
if (hoveredDeletablePillar.current) {
|
||||||
|
(hoveredDeletablePillar.current.material as THREE.MeshStandardMaterial).emissive = new THREE.Color("black");
|
||||||
|
hoveredDeletablePillar.current = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeletableHoveredPillar;
|
||||||
21
app/src/modules/builder/geomentries/pillars/deletePillar.ts
Normal file
21
app/src/modules/builder/geomentries/pillars/deletePillar.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function DeletePillar(
|
||||||
|
hoveredDeletablePillar: Types.RefMesh,
|
||||||
|
floorGroup: Types.RefGroup
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Deleting the hovered Pillar from the itemsGroup //////////
|
||||||
|
|
||||||
|
if (hoveredDeletablePillar.current) {
|
||||||
|
(<any>hoveredDeletablePillar.current.material).dispose();
|
||||||
|
(<any>hoveredDeletablePillar.current.geometry).dispose();
|
||||||
|
floorGroup.current.remove(hoveredDeletablePillar.current);
|
||||||
|
toast.success("Pillar Removed!");
|
||||||
|
hoveredDeletablePillar.current = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeletePillar;
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function updateReferencePolesheight(
|
||||||
|
intersectionPoint: Types.Vector3,
|
||||||
|
distance: Types.Number,
|
||||||
|
referencePole: Types.RefMesh,
|
||||||
|
floorGroup: Types.RefGroup
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Add a Reference Pillar and update its position and scale based on the pointer interaction //////////
|
||||||
|
|
||||||
|
if (referencePole.current) {
|
||||||
|
(<any>referencePole.current.material).dispose();
|
||||||
|
(<any>referencePole.current.geometry).dispose();
|
||||||
|
floorGroup.current.remove(referencePole.current);
|
||||||
|
referencePole.current.geometry.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
const shape = new THREE.Shape();
|
||||||
|
shape.moveTo(0.5, 0);
|
||||||
|
shape.absarc(0, 0, 0.5, 0, 2 * Math.PI, false);
|
||||||
|
|
||||||
|
const extrudeSettings = {
|
||||||
|
depth: distance,
|
||||||
|
bevelEnabled: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
||||||
|
const material = new THREE.MeshBasicMaterial({ color: "green", transparent: true, opacity: 0.5 });
|
||||||
|
referencePole.current = new THREE.Mesh(geometry, material);
|
||||||
|
referencePole.current.rotateX(Math.PI / 2);
|
||||||
|
referencePole.current.position.set(intersectionPoint.x, intersectionPoint.y - 0.01, intersectionPoint.z);
|
||||||
|
referencePole.current.userData = { geometry: geometry, distance: distance, position: { x: intersectionPoint.x, y: intersectionPoint.y - 0.01, z: intersectionPoint.z } };
|
||||||
|
|
||||||
|
floorGroup.current.add(referencePole.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default updateReferencePolesheight;
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function addPointToScene(
|
||||||
|
position: Types.Vector3,
|
||||||
|
colour: Types.Color,
|
||||||
|
currentLayerPoint: Types.RefMeshArray,
|
||||||
|
floorPlanGroupPoint: Types.RefGroup,
|
||||||
|
dragPointControls: Types.RefDragControl | undefined,
|
||||||
|
uuid: Types.RefString | undefined,
|
||||||
|
Type: Types.String
|
||||||
|
): Types.Mesh {
|
||||||
|
|
||||||
|
////////// A function that creates and adds a cube (point) with an outline based on the position and colour given as params, It also updates the drag controls objects and sets the box uuid in uuid.current //////////
|
||||||
|
|
||||||
|
const geometry = new THREE.BoxGeometry(...CONSTANTS.pointConfig.boxScale);
|
||||||
|
const material = new THREE.ShaderMaterial({
|
||||||
|
uniforms: {
|
||||||
|
uColor: { value: new THREE.Color(colour) }, // Blue color for the border
|
||||||
|
uInnerColor: { value: new THREE.Color(CONSTANTS.pointConfig.defaultInnerColor) }, // White color for the inner square
|
||||||
|
},
|
||||||
|
vertexShader: `
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
fragmentShader: `
|
||||||
|
varying vec2 vUv;
|
||||||
|
uniform vec3 uColor;
|
||||||
|
uniform vec3 uInnerColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Define the size of the white square as a proportion of the face
|
||||||
|
float borderThickness = 0.2; // Adjust this value for border thickness
|
||||||
|
if (vUv.x > borderThickness && vUv.x < 1.0 - borderThickness &&
|
||||||
|
vUv.y > borderThickness && vUv.y < 1.0 - borderThickness) {
|
||||||
|
gl_FragColor = vec4(uInnerColor, 1.0); // White inner square
|
||||||
|
} else {
|
||||||
|
gl_FragColor = vec4(uColor, 1.0); // Blue border
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
const point = new THREE.Mesh(geometry, material);
|
||||||
|
point.name = "point";
|
||||||
|
point.userData = { type: Type, color: colour };
|
||||||
|
point.position.set(position.x, 0.01, position.z);
|
||||||
|
|
||||||
|
currentLayerPoint.current.push(point);
|
||||||
|
floorPlanGroupPoint.current.add(point);
|
||||||
|
if (uuid) {
|
||||||
|
uuid.current = point.uuid;
|
||||||
|
}
|
||||||
|
if (dragPointControls) {
|
||||||
|
dragPointControls.current!.objects = currentLayerPoint.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default addPointToScene;
|
||||||
57
app/src/modules/builder/geomentries/points/deletePoint.ts
Normal file
57
app/src/modules/builder/geomentries/points/deletePoint.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import RemoveConnectedLines from "../lines/removeConnectedLines";
|
||||||
|
// import { deletePointApi } from "../../../../services/factoryBuilder/lines/deletePointApi";
|
||||||
|
import { Socket } from "socket.io-client";
|
||||||
|
|
||||||
|
function deletePoint(
|
||||||
|
hoveredDeletablePoint: Types.RefMesh,
|
||||||
|
onlyFloorlines: Types.RefOnlyFloorLines,
|
||||||
|
floorPlanGroupPoint: Types.RefGroup,
|
||||||
|
floorPlanGroupLine: Types.RefGroup,
|
||||||
|
lines: Types.RefLines,
|
||||||
|
setDeletedLines: any,
|
||||||
|
socket: Socket<any>
|
||||||
|
): void {
|
||||||
|
////////// Deleting a Point and the lines that are connected to it //////////
|
||||||
|
|
||||||
|
if (!hoveredDeletablePoint.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(<any>hoveredDeletablePoint.current.material).dispose();
|
||||||
|
(<any>hoveredDeletablePoint.current.geometry).dispose();
|
||||||
|
floorPlanGroupPoint.current.remove(hoveredDeletablePoint.current);
|
||||||
|
const DeletedPointUUID = hoveredDeletablePoint.current.uuid;
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// deletePointApi(organization, DeletedPointUUID);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
organization: organization,
|
||||||
|
uuid: DeletedPointUUID,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:Line:delete:point', data);
|
||||||
|
|
||||||
|
////////// Update onlyFloorlines.current to remove references to the deleted point //////////
|
||||||
|
|
||||||
|
onlyFloorlines.current = onlyFloorlines.current.map(floorline =>
|
||||||
|
floorline.filter(line => line[0][1] !== DeletedPointUUID && line[1][1] !== DeletedPointUUID)
|
||||||
|
).filter(floorline => floorline.length > 0);
|
||||||
|
|
||||||
|
RemoveConnectedLines(DeletedPointUUID, floorPlanGroupLine, floorPlanGroupPoint, setDeletedLines, lines);
|
||||||
|
|
||||||
|
toast.success("Point Removed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deletePoint;
|
||||||
44
app/src/modules/builder/geomentries/points/dragPoint.ts
Normal file
44
app/src/modules/builder/geomentries/points/dragPoint.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import * as THREE from "three";
|
||||||
|
import * as Types from "../../../../types/world/worldTypes"
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
|
||||||
|
import updateLinesPositions from "../lines/updateLinesPositions";
|
||||||
|
import updateLines from "../lines/updateLines";
|
||||||
|
import updateDistanceText from "../lines/updateDistanceText";
|
||||||
|
import updateFloorLines from "../floors/updateFloorLines";
|
||||||
|
|
||||||
|
function DragPoint(
|
||||||
|
event: Types.IntersectionEvent,
|
||||||
|
floorPlanGroupPoint: Types.RefGroup,
|
||||||
|
floorPlanGroupLine: Types.RefGroup,
|
||||||
|
scene: THREE.Scene,
|
||||||
|
lines: Types.RefLines,
|
||||||
|
onlyFloorlines: Types.RefOnlyFloorLines
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Calling the line updation of the affected lines and Snapping of the point during the drag //////////
|
||||||
|
|
||||||
|
const snapThreshold = CONSTANTS.pointConfig.snappingThreshold;
|
||||||
|
const DragedPoint = event.object as Types.Mesh;
|
||||||
|
|
||||||
|
floorPlanGroupPoint.current.children.forEach((point) => {
|
||||||
|
let canSnap =
|
||||||
|
((DragedPoint.userData.type === CONSTANTS.lineConfig.wallName) && (point.userData.type === CONSTANTS.lineConfig.wallName || point.userData.type === CONSTANTS.lineConfig.floorName)) ||
|
||||||
|
((DragedPoint.userData.type === CONSTANTS.lineConfig.floorName) && (point.userData.type === CONSTANTS.lineConfig.wallName || point.userData.type === CONSTANTS.lineConfig.floorName)) ||
|
||||||
|
((DragedPoint.userData.type === CONSTANTS.lineConfig.aisleName) && point.userData.type === CONSTANTS.lineConfig.aisleName);
|
||||||
|
if (canSnap && point.uuid !== DragedPoint.uuid && point.visible) {
|
||||||
|
const distance = DragedPoint.position.distanceTo(point.position);
|
||||||
|
if (distance < snapThreshold) {
|
||||||
|
DragedPoint.position.copy(point.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const affectedLines = updateLinesPositions(DragedPoint, lines);
|
||||||
|
|
||||||
|
updateLines(floorPlanGroupLine, affectedLines);
|
||||||
|
updateDistanceText(scene, floorPlanGroupLine, affectedLines);
|
||||||
|
updateFloorLines(onlyFloorlines, DragedPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DragPoint;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function removeSoloPoint(
|
||||||
|
line: Types.RefLine,
|
||||||
|
floorPlanGroupLine: Types.RefGroup,
|
||||||
|
floorPlanGroupPoint: Types.RefGroup
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Remove the point if there is only one point and if it is not connected to any other line and also the reference line //////////
|
||||||
|
|
||||||
|
if (line.current[0]) {
|
||||||
|
const pointUUID = line.current[0][1];
|
||||||
|
let isConnected = false;
|
||||||
|
|
||||||
|
floorPlanGroupLine.current.children.forEach((line) => {
|
||||||
|
const linePoints = line.userData.linePoints;
|
||||||
|
const uuid1 = linePoints[0][1];
|
||||||
|
const uuid2 = linePoints[1][1];
|
||||||
|
if (uuid1 === pointUUID || uuid2 === pointUUID) {
|
||||||
|
isConnected = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isConnected) {
|
||||||
|
floorPlanGroupPoint.current.children.forEach((point: any) => {
|
||||||
|
if (point.uuid === pointUUID) {
|
||||||
|
(<any>point.material).dispose();
|
||||||
|
(<any>point.geometry).dispose();
|
||||||
|
floorPlanGroupPoint.current.remove(point);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
line.current = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default removeSoloPoint;
|
||||||
32
app/src/modules/builder/geomentries/roofs/addRoofToScene.ts
Normal file
32
app/src/modules/builder/geomentries/roofs/addRoofToScene.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function addRoofToScene(
|
||||||
|
shape: Types.Shape,
|
||||||
|
floor: Types.Number,
|
||||||
|
userData: Types.UserData,
|
||||||
|
floorGroup: Types.RefGroup
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Creating a Polygon roof from the shape of the Polygon floor //////////
|
||||||
|
|
||||||
|
const extrudeSettings: THREE.ExtrudeGeometryOptions = {
|
||||||
|
depth: CONSTANTS.roofConfig.height,
|
||||||
|
bevelEnabled: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
||||||
|
const material = new THREE.MeshStandardMaterial({ color: CONSTANTS.roofConfig.defaultColor, side: THREE.DoubleSide, transparent: true, depthWrite: false });
|
||||||
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
mesh.position.y = CONSTANTS.wallConfig.height + floor;
|
||||||
|
mesh.castShadow = true;
|
||||||
|
mesh.receiveShadow = true;
|
||||||
|
mesh.rotateX(Math.PI / 2);
|
||||||
|
mesh.userData.uuids = userData;
|
||||||
|
mesh.name = `Roof_Layer_${(floor / CONSTANTS.wallConfig.height) + 1}`;
|
||||||
|
|
||||||
|
floorGroup.current.add(mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default addRoofToScene;
|
||||||
47
app/src/modules/builder/geomentries/roofs/hideRoof.ts
Normal file
47
app/src/modules/builder/geomentries/roofs/hideRoof.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function hideRoof(
|
||||||
|
visibility: Types.Boolean,
|
||||||
|
floorGroup: Types.RefGroup,
|
||||||
|
camera: THREE.Camera
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Toggles the visibility of the roof based on the camera position and the Roof visibility button on UI //////////
|
||||||
|
|
||||||
|
const v = new THREE.Vector3();
|
||||||
|
const u = new THREE.Vector3();
|
||||||
|
|
||||||
|
if (visibility === true && floorGroup.current) {
|
||||||
|
for (const child of floorGroup.current.children) {
|
||||||
|
if (child.name.includes("Roof")) {
|
||||||
|
const roofChild = child as Types.Mesh;
|
||||||
|
roofChild.getWorldDirection(v);
|
||||||
|
camera?.getWorldDirection(u);
|
||||||
|
if (roofChild.material) {
|
||||||
|
const materials = Array.isArray(roofChild.material) ? roofChild.material : [roofChild.material];
|
||||||
|
materials.forEach(material => {
|
||||||
|
material.visible = v.dot(u) < 0.25;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (floorGroup.current) {
|
||||||
|
for (const child of floorGroup.current.children) {
|
||||||
|
if (child.name.includes("Roof")) {
|
||||||
|
const roofChild = child as Types.Mesh;
|
||||||
|
if (roofChild.material) {
|
||||||
|
const materials = Array.isArray(roofChild.material) ? roofChild.material : [roofChild.material];
|
||||||
|
materials.forEach(material => {
|
||||||
|
material.visible = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default hideRoof;
|
||||||
108
app/src/modules/builder/geomentries/walls/addWallItems.ts
Normal file
108
app/src/modules/builder/geomentries/walls/addWallItems.ts
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
// import { setWallItem } from '../../../../services/factoryBuilder/assest/wallAsset/setWallItemApi';
|
||||||
|
import { Socket } from 'socket.io-client';
|
||||||
|
|
||||||
|
async function AddWallItems(
|
||||||
|
selected: Types.String,
|
||||||
|
raycaster: THREE.Raycaster,
|
||||||
|
CSGGroup: Types.RefMesh,
|
||||||
|
AssetConfigurations: Types.AssetConfigurations,
|
||||||
|
setWallItems: Types.setWallItemSetState,
|
||||||
|
socket: Socket<any>
|
||||||
|
): Promise<void> {
|
||||||
|
|
||||||
|
////////// Load Wall GLtf's and set the positions, rotation, type etc. in state and store in localstorage //////////
|
||||||
|
|
||||||
|
let intersects = raycaster?.intersectObject(CSGGroup.current!, true);
|
||||||
|
const wallRaycastIntersection = intersects?.find((child) => child.object.name.includes("WallRaycastReference"));
|
||||||
|
|
||||||
|
if (wallRaycastIntersection) {
|
||||||
|
const intersectionPoint = wallRaycastIntersection;
|
||||||
|
const loader = new GLTFLoader();
|
||||||
|
loader.load(AssetConfigurations[selected].modelUrl, async (gltf) => {
|
||||||
|
const model = gltf.scene;
|
||||||
|
model.userData = { wall: intersectionPoint.object.parent };
|
||||||
|
model.children[0].children.forEach((child) => {
|
||||||
|
if (child.name !== "CSG_REF") {
|
||||||
|
child.castShadow = true;
|
||||||
|
child.receiveShadow = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const config = AssetConfigurations[selected];
|
||||||
|
let positionY = typeof config.positionY === 'function' ? config.positionY(intersectionPoint) : config.positionY;
|
||||||
|
if (positionY === 0) {
|
||||||
|
positionY = Math.floor(intersectionPoint.point.y / CONSTANTS.wallConfig.height) * CONSTANTS.wallConfig.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newWallItem = {
|
||||||
|
type: config.type,
|
||||||
|
model: model,
|
||||||
|
modelname: selected,
|
||||||
|
scale: config.scale,
|
||||||
|
csgscale: config.csgscale,
|
||||||
|
csgposition: config.csgposition,
|
||||||
|
position: [intersectionPoint.point.x, positionY, intersectionPoint.point.z] as [number, number, number],
|
||||||
|
quaternion: intersectionPoint.object.quaternion.clone() as Types.QuaternionType
|
||||||
|
};
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// await setWallItem(
|
||||||
|
// organization,
|
||||||
|
// model.uuid,
|
||||||
|
// newWallItem.modelname,
|
||||||
|
// newWallItem.type!,
|
||||||
|
// newWallItem.csgposition!,
|
||||||
|
// newWallItem.csgscale!,
|
||||||
|
// newWallItem.position,
|
||||||
|
// newWallItem.quaternion,
|
||||||
|
// newWallItem.scale!,
|
||||||
|
// )
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
organization: organization,
|
||||||
|
modeluuid: model.uuid,
|
||||||
|
modelname: newWallItem.modelname,
|
||||||
|
type: newWallItem.type!,
|
||||||
|
csgposition: newWallItem.csgposition!,
|
||||||
|
csgscale: newWallItem.csgscale!,
|
||||||
|
position: newWallItem.position,
|
||||||
|
quaternion: newWallItem.quaternion,
|
||||||
|
scale: newWallItem.scale!,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:wallItems:set', data);
|
||||||
|
|
||||||
|
setWallItems((prevItems) => {
|
||||||
|
const updatedItems = [...prevItems, newWallItem];
|
||||||
|
|
||||||
|
const WallItemsForStorage = updatedItems.map(item => {
|
||||||
|
const { model, ...rest } = item;
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
modeluuid: model?.uuid,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
localStorage.setItem("WallItems", JSON.stringify(WallItemsForStorage));
|
||||||
|
toast.success("Model Added!");
|
||||||
|
|
||||||
|
return updatedItems;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddWallItems;
|
||||||
59
app/src/modules/builder/geomentries/walls/deleteWallItems.ts
Normal file
59
app/src/modules/builder/geomentries/walls/deleteWallItems.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
// import { deleteWallItem } from '../../../../services/factoryBuilder/assest/wallAsset/deleteWallItemApi';
|
||||||
|
import { Socket } from 'socket.io-client';
|
||||||
|
|
||||||
|
function DeleteWallItems(
|
||||||
|
hoveredDeletableWallItem: Types.RefMesh,
|
||||||
|
setWallItems: Types.setWallItemSetState,
|
||||||
|
wallItems: Types.wallItems,
|
||||||
|
socket: Socket<any>
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Deleting the hovered Wall GLTF from thewallItems and also update it in the localstorage //////////
|
||||||
|
|
||||||
|
if (hoveredDeletableWallItem.current && hoveredDeletableWallItem.current.parent) {
|
||||||
|
setWallItems([]);
|
||||||
|
let WallItemsRef = wallItems;
|
||||||
|
const removedItem = WallItemsRef.find((item) => item.model?.uuid === hoveredDeletableWallItem.current?.parent?.uuid);
|
||||||
|
const Items = WallItemsRef.filter((item) => item.model?.uuid !== hoveredDeletableWallItem.current?.parent?.uuid);
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
WallItemsRef = Items;
|
||||||
|
setWallItems(WallItemsRef);
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// await deleteWallItem(organization, removedItem?.model?.uuid!, removedItem?.modelname!)
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
organization: organization,
|
||||||
|
modeluuid: removedItem?.model?.uuid!,
|
||||||
|
modelname: removedItem?.modelname!,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:wallItems:delete', data);
|
||||||
|
|
||||||
|
const WallItemsForStorage = WallItemsRef.map(item => {
|
||||||
|
const { model, ...rest } = item;
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
modeluuid: model?.uuid,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
localStorage.setItem("WallItems", JSON.stringify(WallItemsForStorage));
|
||||||
|
toast.success("Model Removed!");
|
||||||
|
hoveredDeletableWallItem.current = null;
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeleteWallItems;
|
||||||
45
app/src/modules/builder/geomentries/walls/hideWalls.ts
Normal file
45
app/src/modules/builder/geomentries/walls/hideWalls.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function hideWalls(
|
||||||
|
visibility: Types.Boolean,
|
||||||
|
scene: THREE.Scene,
|
||||||
|
camera: THREE.Camera
|
||||||
|
): void {
|
||||||
|
|
||||||
|
////////// Altering the visibility of the Walls when the world direction of the wall is facing the camera //////////
|
||||||
|
|
||||||
|
const v = new THREE.Vector3();
|
||||||
|
const u = new THREE.Vector3();
|
||||||
|
|
||||||
|
if (visibility === true) {
|
||||||
|
for (const children of scene.children) {
|
||||||
|
if (children.name === "Walls" && children.children[0]?.children.length > 0) {
|
||||||
|
children.children[0].children.forEach((child: any) => {
|
||||||
|
if (child.children[0]?.userData.WallType === "RoomWall") {
|
||||||
|
child.children[0].getWorldDirection(v);
|
||||||
|
camera.getWorldDirection(u);
|
||||||
|
if (child.children[0].material) {
|
||||||
|
child.children[0].material.visible = (2 * v.dot(u)) >= -0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const children of scene.children) {
|
||||||
|
if (children.name === "Walls" && children.children[0]?.children.length > 0) {
|
||||||
|
children.children[0].children.forEach((child: any) => {
|
||||||
|
if (child.children[0]?.userData.WallType === "RoomWall") {
|
||||||
|
if (child.children[0].material) {
|
||||||
|
child.children[0].material.visible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default hideWalls;
|
||||||
129
app/src/modules/builder/geomentries/walls/loadWalls.ts
Normal file
129
app/src/modules/builder/geomentries/walls/loadWalls.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as turf from '@turf/turf';
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
import getRoomsFromLines from '../lines/getRoomsFromLines';
|
||||||
|
|
||||||
|
async function loadWalls(
|
||||||
|
lines: Types.RefLines,
|
||||||
|
setWalls: any,
|
||||||
|
): Promise<void> {
|
||||||
|
////////// Removes the old walls if any, Checks if there is any overlapping in lines if any updates it , starts function that creates floor and roof //////////
|
||||||
|
|
||||||
|
const Walls: Types.Walls = [];
|
||||||
|
const Rooms: Types.Rooms = [];
|
||||||
|
|
||||||
|
localStorage.setItem("Lines", JSON.stringify(lines.current));
|
||||||
|
|
||||||
|
if (lines.current.length > 1) {
|
||||||
|
|
||||||
|
////////// Add Walls that are forming a room //////////
|
||||||
|
|
||||||
|
const wallSet = new Set<string>();
|
||||||
|
|
||||||
|
const rooms: Types.Rooms = await getRoomsFromLines(lines);
|
||||||
|
Rooms.push(...rooms);
|
||||||
|
|
||||||
|
Rooms.forEach(({ coordinates: room, layer }) => {
|
||||||
|
for (let i = 0; i < room.length - 1; i++) {
|
||||||
|
const uuid1 = room[i].uuid;
|
||||||
|
const uuid2 = room[(i + 1) % room.length].uuid;
|
||||||
|
const wallId = `${uuid1}_${uuid2}`;
|
||||||
|
|
||||||
|
if (!wallSet.has(wallId)) {
|
||||||
|
const p1 = room[i].position;
|
||||||
|
const p2 = room[(i + 1) % room.length].position;
|
||||||
|
|
||||||
|
const shape = new THREE.Shape();
|
||||||
|
shape.moveTo(0, 0);
|
||||||
|
shape.lineTo(0, CONSTANTS.wallConfig.height);
|
||||||
|
shape.lineTo(p2.distanceTo(p1), CONSTANTS.wallConfig.height);
|
||||||
|
shape.lineTo(p2.distanceTo(p1), 0);
|
||||||
|
shape.lineTo(0, 0);
|
||||||
|
|
||||||
|
const extrudeSettings = {
|
||||||
|
depth: CONSTANTS.wallConfig.width,
|
||||||
|
bevelEnabled: false
|
||||||
|
};
|
||||||
|
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
||||||
|
const angle = Math.atan2(p2.z - p1.z, p2.x - p1.x);
|
||||||
|
Walls.push([geometry, [0, -angle, 0], [p1.x, (layer - 1) * CONSTANTS.wallConfig.height, p1.z], "RoomWall", layer]);
|
||||||
|
|
||||||
|
wallSet.add(wallId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
////////// Add Walls that are not forming any room //////////
|
||||||
|
|
||||||
|
lines.current.forEach(line => {
|
||||||
|
if (line[0][3] && line[1][3] !== CONSTANTS.lineConfig.wallName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [uuid1, uuid2] = line.map(point => point[1]);
|
||||||
|
let isInRoom = false;
|
||||||
|
const lineLayer = line[0][2];
|
||||||
|
|
||||||
|
for (let room of Rooms) {
|
||||||
|
const roomLayer = room.layer;
|
||||||
|
if (roomLayer !== lineLayer) continue;
|
||||||
|
for (let i = 0; i < room.coordinates.length - 1; i++) {
|
||||||
|
const roomUuid1 = room.coordinates[i].uuid;
|
||||||
|
const roomUuid2 = room.coordinates[(i + 1) % room.coordinates.length].uuid;
|
||||||
|
if (
|
||||||
|
(uuid1 === roomUuid1 && uuid2 === roomUuid2) ||
|
||||||
|
(uuid1 === roomUuid2 && uuid2 === roomUuid1)
|
||||||
|
) {
|
||||||
|
isInRoom = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isInRoom) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isInRoom) {
|
||||||
|
const p1 = new THREE.Vector3(line[0][0].x, 0, line[0][0].z);
|
||||||
|
const p2 = new THREE.Vector3(line[1][0].x, 0, line[1][0].z);
|
||||||
|
|
||||||
|
let isCollinear = false;
|
||||||
|
for (let room of Rooms) {
|
||||||
|
if (room.layer !== lineLayer) continue;
|
||||||
|
for (let i = 0; i < room.coordinates.length - 1; i++) {
|
||||||
|
const roomP1 = room.coordinates[i].position;
|
||||||
|
const roomP2 = room.coordinates[(i + 1) % room.coordinates.length].position;
|
||||||
|
const lineFeature = turf.lineString([[p1.x, p1.z], [p2.x, p2.z]]);
|
||||||
|
const roomFeature = turf.lineString([[roomP1.x, roomP1.z], [roomP2.x, roomP2.z]]);
|
||||||
|
if (turf.booleanOverlap(lineFeature, roomFeature)) {
|
||||||
|
isCollinear = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isCollinear) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCollinear) {
|
||||||
|
const shape = new THREE.Shape();
|
||||||
|
shape.moveTo(0, 0);
|
||||||
|
shape.lineTo(0, CONSTANTS.wallConfig.height);
|
||||||
|
shape.lineTo(p2.distanceTo(p1), CONSTANTS.wallConfig.height);
|
||||||
|
shape.lineTo(p2.distanceTo(p1), 0);
|
||||||
|
shape.lineTo(0, 0);
|
||||||
|
|
||||||
|
const extrudeSettings = {
|
||||||
|
depth: CONSTANTS.wallConfig.width,
|
||||||
|
bevelEnabled: false
|
||||||
|
};
|
||||||
|
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
||||||
|
const angle = Math.atan2(p2.z - p1.z, p2.x - p1.x);
|
||||||
|
Walls.push([geometry, [0, -angle, 0], [p1.x, (lineLayer - 1) * CONSTANTS.wallConfig.height, p1.z], "SegmentWall", lineLayer]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setWalls(Walls);
|
||||||
|
}else{
|
||||||
|
setWalls([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default loadWalls;
|
||||||
50
app/src/modules/builder/geomentries/zones/addZonesToScene.ts
Normal file
50
app/src/modules/builder/geomentries/zones/addZonesToScene.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as Types from '../../../../types/world/worldTypes';
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
|
||||||
|
const baseMaterial = 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 uColor;
|
||||||
|
void main(){
|
||||||
|
float alpha = 1.0 - vUv.y;
|
||||||
|
gl_FragColor = vec4(uColor, alpha);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
uniforms: {
|
||||||
|
uColor: { value: new THREE.Color(CONSTANTS.zoneConfig.defaultColor) },
|
||||||
|
},
|
||||||
|
transparent: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function addZonesToScene(
|
||||||
|
line: Types.Line,
|
||||||
|
floorGroupZone: Types.RefGroup,
|
||||||
|
color: THREE.Color
|
||||||
|
) {
|
||||||
|
const point1 = line[0][0];
|
||||||
|
const point2 = line[1][0];
|
||||||
|
|
||||||
|
const length = (new THREE.Vector3(point2.x, point2.y, point2.z)).distanceTo(new THREE.Vector3(point1.x, point1.y, point1.z));
|
||||||
|
const angle = Math.atan2(point2.z - point1.z, point2.x - point1.x);
|
||||||
|
|
||||||
|
const geometry = new THREE.PlaneGeometry(length, 10);
|
||||||
|
|
||||||
|
const material = baseMaterial.clone();
|
||||||
|
material.uniforms.uColor.value.set(color.r, color.g, color.b);
|
||||||
|
|
||||||
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
|
||||||
|
mesh.position.set((point1.x + point2.x) / 2, ((line[0][2] - 1) * CONSTANTS.wallConfig.height) + 5, (point1.z + point2.z) / 2);
|
||||||
|
mesh.rotation.y = -angle;
|
||||||
|
|
||||||
|
floorGroupZone.current.add(mesh);
|
||||||
|
}
|
||||||
19
app/src/modules/builder/geomentries/zones/loadZones.ts
Normal file
19
app/src/modules/builder/geomentries/zones/loadZones.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import * as Types from '../../../../types/world/worldTypes';
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||||
|
import addZonesToScene from './addZonesToScene';
|
||||||
|
|
||||||
|
export default function loadZones(
|
||||||
|
lines: Types.RefLines,
|
||||||
|
floorGroupZone: Types.RefGroup
|
||||||
|
) {
|
||||||
|
if (!floorGroupZone.current) return
|
||||||
|
floorGroupZone.current.children = [];
|
||||||
|
const zones = lines.current.filter((line) => line[0][3] && line[1][3] === CONSTANTS.lineConfig.zoneName);
|
||||||
|
|
||||||
|
if (zones.length > 0) {
|
||||||
|
zones.forEach((zone: Types.Line) => {
|
||||||
|
addZonesToScene(zone, floorGroupZone, new THREE.Color(CONSTANTS.zoneConfig.color))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
101
app/src/modules/builder/groups/floorGroup.tsx
Normal file
101
app/src/modules/builder/groups/floorGroup.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import { useFrame, useThree } from "@react-three/fiber";
|
||||||
|
import { useAddAction, useDeleteModels, useRoofVisibility, useToggleView, useWallVisibility, useUpdateScene } from "../../../store/store";
|
||||||
|
import hideRoof from "../geomentries/roofs/hideRoof";
|
||||||
|
import hideWalls from "../geomentries/walls/hideWalls";
|
||||||
|
import addAndUpdateReferencePillar from "../geomentries/pillars/addAndUpdateReferencePillar";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import addPillar from "../geomentries/pillars/addPillar";
|
||||||
|
import DeletePillar from "../geomentries/pillars/deletePillar";
|
||||||
|
import DeletableHoveredPillar from "../geomentries/pillars/deletableHoveredPillar";
|
||||||
|
import loadFloor from "../geomentries/floors/loadFloor";
|
||||||
|
|
||||||
|
const FloorGroup = ({ floorGroup, lines, referencePole, hoveredDeletablePillar }: any) => {
|
||||||
|
const state = useThree();
|
||||||
|
const { roofVisibility, setRoofVisibility } = useRoofVisibility();
|
||||||
|
const { wallVisibility, setWallVisibility } = useWallVisibility();
|
||||||
|
const { toggleView, setToggleView } = useToggleView();
|
||||||
|
const { scene, camera, pointer, raycaster, gl } = useThree();
|
||||||
|
const { addAction, setAddAction } = useAddAction();
|
||||||
|
const { deleteModels, setDeleteModels } = useDeleteModels();
|
||||||
|
const { updateScene, setUpdateScene } = useUpdateScene();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (updateScene) {
|
||||||
|
loadFloor(lines, floorGroup);
|
||||||
|
setUpdateScene(false);
|
||||||
|
}
|
||||||
|
}, [updateScene])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!addAction) {
|
||||||
|
if (referencePole.current) {
|
||||||
|
(referencePole.current as any).material.dispose();
|
||||||
|
(referencePole.current.geometry as any).dispose();
|
||||||
|
floorGroup.current.remove(referencePole.current);
|
||||||
|
referencePole.current = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [addAction]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const canvasElement = gl.domElement;
|
||||||
|
let drag = false;
|
||||||
|
let isLeftMouseDown = false;
|
||||||
|
|
||||||
|
const onMouseDown = (evt: any) => {
|
||||||
|
if (evt.button === 0) {
|
||||||
|
isLeftMouseDown = true;
|
||||||
|
drag = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseUp = (evt: any) => {
|
||||||
|
if (evt.button === 0) {
|
||||||
|
isLeftMouseDown = false;
|
||||||
|
if (!drag) {
|
||||||
|
if (addAction === "pillar") {
|
||||||
|
addPillar(referencePole, floorGroup);
|
||||||
|
}
|
||||||
|
if (deleteModels) {
|
||||||
|
DeletePillar(hoveredDeletablePillar, floorGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseMove = () => {
|
||||||
|
if (isLeftMouseDown) {
|
||||||
|
drag = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||||
|
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||||
|
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||||
|
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||||
|
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||||
|
};
|
||||||
|
}, [deleteModels, addAction])
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
hideRoof(roofVisibility, floorGroup, camera);
|
||||||
|
hideWalls(wallVisibility, scene, camera);
|
||||||
|
|
||||||
|
if (addAction === "pillar") {
|
||||||
|
addAndUpdateReferencePillar(raycaster, floorGroup, referencePole);
|
||||||
|
}
|
||||||
|
if (deleteModels) {
|
||||||
|
DeletableHoveredPillar(state, floorGroup, hoveredDeletablePillar);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group ref={floorGroup} visible={!toggleView} name="floorGroup">
|
||||||
|
</group>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FloorGroup;
|
||||||
245
app/src/modules/builder/groups/floorGroupAisle.tsx
Normal file
245
app/src/modules/builder/groups/floorGroupAisle.tsx
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as Types from '../../../types/world/worldTypes';
|
||||||
|
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||||
|
import { useThree } from "@react-three/fiber";
|
||||||
|
import { useToggleView, useActiveLayer, useSocketStore, useDeletePointOrLine, useMovePoint, useUpdateScene, useNewLines, useToolMode } from "../../../store/store";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import removeSoloPoint from "../geomentries/points/removeSoloPoint";
|
||||||
|
import removeReferenceLine from "../geomentries/lines/removeReferenceLine";
|
||||||
|
import getClosestIntersection from "../geomentries/lines/getClosestIntersection";
|
||||||
|
import addPointToScene from "../geomentries/points/addPointToScene";
|
||||||
|
import arrayLineToObject from '../geomentries/lines/lineConvertions/arrayLineToObject';
|
||||||
|
import addLineToScene from "../geomentries/lines/addLineToScene";
|
||||||
|
import loadAisles from '../geomentries/aisles/loadAisles';
|
||||||
|
|
||||||
|
|
||||||
|
const FloorGroupAilse = ({ floorGroupAisle, plane, floorPlanGroupLine, floorPlanGroupPoint, line, lines, currentLayerPoint, dragPointControls, floorPlanGroup, ReferenceLineMesh, LineCreated, isSnapped, ispreSnapped, snappedPoint, isSnappedUUID, isAngleSnapped, anglesnappedPoint }: any) => {
|
||||||
|
const { toggleView, setToggleView } = useToggleView();
|
||||||
|
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
|
||||||
|
const { toolMode, setToolMode } = useToolMode();
|
||||||
|
const { movePoint, setMovePoint } = useMovePoint();
|
||||||
|
const { socket } = useSocketStore();
|
||||||
|
const { activeLayer } = useActiveLayer();
|
||||||
|
const { gl, raycaster, camera, pointer } = useThree();
|
||||||
|
const { updateScene, setUpdateScene } = useUpdateScene();
|
||||||
|
const { newLines, setNewLines } = useNewLines();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (updateScene) {
|
||||||
|
loadAisles(lines, floorGroupAisle);
|
||||||
|
setUpdateScene(false);
|
||||||
|
}
|
||||||
|
}, [updateScene])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (toolMode === "Aisle") {
|
||||||
|
setDeletePointOrLine(false);
|
||||||
|
setMovePoint(false);
|
||||||
|
} else {
|
||||||
|
removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint);
|
||||||
|
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
|
||||||
|
}
|
||||||
|
}, [toolMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
const canvasElement = gl.domElement;
|
||||||
|
|
||||||
|
let drag = false;
|
||||||
|
let isLeftMouseDown = false;
|
||||||
|
|
||||||
|
const onMouseDown = (evt: any) => {
|
||||||
|
if (evt.button === 0) {
|
||||||
|
isLeftMouseDown = true;
|
||||||
|
drag = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseUp = (evt: any) => {
|
||||||
|
if (evt.button === 0) {
|
||||||
|
isLeftMouseDown = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMouseMove = () => {
|
||||||
|
if (isLeftMouseDown) {
|
||||||
|
drag = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onContextMenu = (e: any) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (toolMode === "Aisle") {
|
||||||
|
removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint);
|
||||||
|
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseClick = (evt: any) => {
|
||||||
|
if (!plane.current || drag) return;
|
||||||
|
|
||||||
|
const intersects = raycaster.intersectObject(plane.current, true);
|
||||||
|
let intersectionPoint = intersects[0].point;
|
||||||
|
const points = floorPlanGroupPoint.current?.children ?? [];
|
||||||
|
const intersectsPoint = raycaster.intersectObjects(points, true).find(intersect => intersect.object.visible);
|
||||||
|
let intersectsLines: any = raycaster.intersectObjects(floorPlanGroupLine.current.children, true);
|
||||||
|
|
||||||
|
|
||||||
|
if (intersectsLines.length > 0 && intersects && intersects.length > 0 && !intersectsPoint) {
|
||||||
|
const lineType = intersectsLines[0].object.userData.linePoints[0][3];
|
||||||
|
if (lineType === CONSTANTS.lineConfig.aisleName) {
|
||||||
|
// console.log("intersected a aisle line");
|
||||||
|
const ThroughPoint = (intersectsLines[0].object.geometry.parameters.path).getPoints(CONSTANTS.lineConfig.lineIntersectionPoints);
|
||||||
|
let intersection = getClosestIntersection(ThroughPoint, intersectionPoint);
|
||||||
|
if (!intersection) return;
|
||||||
|
const point = addPointToScene(intersection, CONSTANTS.pointConfig.aisleOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.aisleName);
|
||||||
|
(line.current as Types.Line).push([new THREE.Vector3(intersection.x, 0.01, intersection.z), point.uuid, activeLayer, CONSTANTS.lineConfig.aisleName,]);
|
||||||
|
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
|
||||||
|
lines.current.push(line.current as Types.Line);
|
||||||
|
|
||||||
|
const data = arrayLineToObject(line.current as Types.Line);
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// setLine(organization, data.layer!, data.line!, data.type!);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
organization: organization,
|
||||||
|
layer: data.layer,
|
||||||
|
line: data.line,
|
||||||
|
type: data.type,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:Line:create', input);
|
||||||
|
|
||||||
|
setNewLines([line.current]);
|
||||||
|
|
||||||
|
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.pointConfig.aisleOuterColor, line.current, floorPlanGroupLine);
|
||||||
|
let lastPoint = line.current[line.current.length - 1];
|
||||||
|
line.current = [lastPoint];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (intersectsPoint && intersects && intersects.length > 0) {
|
||||||
|
if (intersectsPoint.object.userData.type === CONSTANTS.lineConfig.aisleName) {
|
||||||
|
// console.log("intersected a aisle point");
|
||||||
|
intersectionPoint = intersectsPoint.object.position;
|
||||||
|
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), intersectsPoint.object.uuid, activeLayer, CONSTANTS.lineConfig.aisleName,]);
|
||||||
|
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
|
||||||
|
lines.current.push(line.current as Types.Line);
|
||||||
|
|
||||||
|
const data = arrayLineToObject(line.current as Types.Line);
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// setLine(organization, data.layer!, data.line!, data.type!);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
organization: organization,
|
||||||
|
layer: data.layer,
|
||||||
|
line: data.line,
|
||||||
|
type: data.type,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:Line:create', input);
|
||||||
|
|
||||||
|
setNewLines([line.current]);
|
||||||
|
|
||||||
|
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.pointConfig.aisleOuterColor, line.current, floorPlanGroupLine);
|
||||||
|
let lastPoint = line.current[line.current.length - 1];
|
||||||
|
line.current = [lastPoint];
|
||||||
|
ispreSnapped.current = false;
|
||||||
|
isSnapped.current = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (intersects && intersects.length > 0) {
|
||||||
|
// console.log("intersected a empty area");
|
||||||
|
let uuid: string = "";
|
||||||
|
if (isAngleSnapped.current && anglesnappedPoint.current && line.current.length > 0) {
|
||||||
|
intersectionPoint = anglesnappedPoint.current;
|
||||||
|
const point = addPointToScene(intersectionPoint, CONSTANTS.pointConfig.aisleOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.aisleName);
|
||||||
|
uuid = point.uuid;
|
||||||
|
} else if (isSnapped.current && snappedPoint.current && line.current.length > 0) {
|
||||||
|
intersectionPoint = snappedPoint.current;
|
||||||
|
uuid = isSnappedUUID.current!;
|
||||||
|
} else if (ispreSnapped.current && snappedPoint.current) {
|
||||||
|
intersectionPoint = snappedPoint.current;
|
||||||
|
uuid = isSnappedUUID.current!;
|
||||||
|
} else {
|
||||||
|
const point = addPointToScene(intersectionPoint, CONSTANTS.pointConfig.aisleOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.aisleName);
|
||||||
|
uuid = point.uuid;
|
||||||
|
}
|
||||||
|
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), uuid, activeLayer, CONSTANTS.lineConfig.aisleName,]);
|
||||||
|
|
||||||
|
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
|
||||||
|
lines.current.push(line.current as Types.Line);
|
||||||
|
|
||||||
|
const data = arrayLineToObject(line.current as Types.Line);
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// setLine(organization, data.layer!, data.line!, data.type!);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
organization: organization,
|
||||||
|
layer: data.layer,
|
||||||
|
line: data.line,
|
||||||
|
type: data.type,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:Line:create', input);
|
||||||
|
|
||||||
|
setNewLines([line.current]);
|
||||||
|
|
||||||
|
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.pointConfig.aisleOuterColor, line.current, floorPlanGroupLine);
|
||||||
|
let lastPoint = line.current[line.current.length - 1];
|
||||||
|
line.current = [lastPoint];
|
||||||
|
ispreSnapped.current = false;
|
||||||
|
isSnapped.current = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (toolMode === 'Aisle') {
|
||||||
|
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||||
|
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||||
|
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||||
|
canvasElement.addEventListener("click", onMouseClick);
|
||||||
|
canvasElement.addEventListener("contextmenu", onContextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||||
|
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||||
|
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||||
|
canvasElement.removeEventListener("click", onMouseClick);
|
||||||
|
canvasElement.removeEventListener("contextmenu", onContextMenu);
|
||||||
|
};
|
||||||
|
}, [toolMode])
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group ref={floorGroupAisle} visible={!toggleView} name="floorGroupAisle">
|
||||||
|
</group>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FloorGroupAilse;
|
||||||
292
app/src/modules/builder/groups/floorItemsGroup.tsx
Normal file
292
app/src/modules/builder/groups/floorItemsGroup.tsx
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
import { useFrame, useThree } from "@react-three/fiber";
|
||||||
|
import { useActiveTool, useCamMode, useDeletableFloorItem, useDeleteModels, useFloorItems, useRenderDistance, useselectedFloorItem, useSelectedItem, useSocketStore, useToggleView, useTransformMode } from "../../../store/store";
|
||||||
|
import assetVisibility from "../geomentries/assets/assetVisibility";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import * as THREE from "three";
|
||||||
|
import * as Types from "../../../types/world/worldTypes";
|
||||||
|
import assetManager, { cancelOngoingTasks } from "../geomentries/assets/assetManager";
|
||||||
|
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||||
|
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
|
||||||
|
import DeletableHoveredFloorItems from "../geomentries/assets/deletableHoveredFloorItems";
|
||||||
|
import DeleteFloorItems from "../geomentries/assets/deleteFloorItems";
|
||||||
|
import loadInitialFloorItems from "../../scene/IntialLoad/loadInitialFloorItems";
|
||||||
|
import addAssetModel from "../geomentries/assets/addAssetModel";
|
||||||
|
// import { getFloorItems } from "../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi";
|
||||||
|
// import { retrieveGLTF } from "../../../utils/indexDB/idbUtils";
|
||||||
|
const assetManagerWorker = new Worker(new URL('../../../services/factoryBuilder/webWorkers/assetManagerWorker.js', import.meta.url));
|
||||||
|
// const gltfLoaderWorker = new Worker(new URL('../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js', import.meta.url));
|
||||||
|
|
||||||
|
const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject, floorGroup, tempLoader, isTempLoader, plane }: any) => {
|
||||||
|
const state: Types.ThreeState = useThree();
|
||||||
|
const { raycaster, camera, controls, pointer }: any = state;
|
||||||
|
const { renderDistance, setRenderDistance } = useRenderDistance();
|
||||||
|
const { toggleView, setToggleView } = useToggleView();
|
||||||
|
const { floorItems, setFloorItems } = useFloorItems();
|
||||||
|
const { camMode, setCamMode } = useCamMode();
|
||||||
|
const { deleteModels, setDeleteModels } = useDeleteModels();
|
||||||
|
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
|
||||||
|
const { transformMode, setTransformMode } = useTransformMode();
|
||||||
|
const { selectedFloorItem, setselectedFloorItem } = useselectedFloorItem();
|
||||||
|
const { activeTool, setActiveTool } = useActiveTool();
|
||||||
|
const { selectedItem, setSelectedItem } = useSelectedItem();
|
||||||
|
const { socket } = useSocketStore();
|
||||||
|
|
||||||
|
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(() => {
|
||||||
|
// Load initial floor items
|
||||||
|
|
||||||
|
// const email = localStorage.getItem('email');
|
||||||
|
// const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
// getFloorItems(organization).then((data) => {
|
||||||
|
// gltfLoaderWorker.postMessage({ FloorItems: data })
|
||||||
|
// })
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// } else if (event.data.message === "done") {
|
||||||
|
// loadInitialFloorItems(itemsGroup, setFloorItems);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
loadInitialFloorItems(itemsGroup, setFloorItems);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
assetManagerWorker.onmessage = async (event) => {
|
||||||
|
cancelOngoingTasks(); // Cancel the ongoing process
|
||||||
|
await assetManager(event.data, itemsGroup, loader);
|
||||||
|
};
|
||||||
|
}, [assetManagerWorker]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (toggleView) return
|
||||||
|
|
||||||
|
const uuids: string[] = [];
|
||||||
|
itemsGroup.current?.children.forEach((child: any) => {
|
||||||
|
uuids.push(child.uuid);
|
||||||
|
});
|
||||||
|
const cameraPosition = state.camera.position;
|
||||||
|
|
||||||
|
assetManagerWorker.postMessage({ floorItems, cameraPosition, uuids, renderDistance });
|
||||||
|
}, [camMode, renderDistance]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const controls: any = state.controls;
|
||||||
|
const camera: any = state.camera;
|
||||||
|
|
||||||
|
if (controls) {
|
||||||
|
let intervalId: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
const handleChange = () => {
|
||||||
|
if (toggleView) return
|
||||||
|
|
||||||
|
const uuids: string[] = [];
|
||||||
|
itemsGroup.current?.children.forEach((child: any) => {
|
||||||
|
uuids.push(child.uuid);
|
||||||
|
});
|
||||||
|
const cameraPosition = camera.position;
|
||||||
|
|
||||||
|
assetManagerWorker.postMessage({ floorItems, cameraPosition, uuids, renderDistance });
|
||||||
|
};
|
||||||
|
|
||||||
|
const startInterval = () => {
|
||||||
|
if (!intervalId) {
|
||||||
|
intervalId = setInterval(handleChange, 50);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopInterval = () => {
|
||||||
|
handleChange();
|
||||||
|
if (intervalId) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
intervalId = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
controls.addEventListener('rest', handleChange);
|
||||||
|
controls.addEventListener('rest', stopInterval);
|
||||||
|
controls.addEventListener('control', startInterval);
|
||||||
|
controls.addEventListener('controlend', stopInterval);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
controls.removeEventListener('rest', handleChange);
|
||||||
|
controls.removeEventListener('rest', stopInterval);
|
||||||
|
controls.removeEventListener('control', startInterval);
|
||||||
|
controls.removeEventListener('controlend', stopInterval);
|
||||||
|
if (intervalId) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [state.controls, floorItems, toggleView, renderDistance]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const canvasElement = state.gl.domElement;
|
||||||
|
let drag = false;
|
||||||
|
let isLeftMouseDown = false;
|
||||||
|
|
||||||
|
const onMouseDown = (evt: any) => {
|
||||||
|
if (evt.button === 0) {
|
||||||
|
isLeftMouseDown = true;
|
||||||
|
drag = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseMove = () => {
|
||||||
|
if (isLeftMouseDown) {
|
||||||
|
drag = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseUp = async (evt: any) => {
|
||||||
|
if (controls) {
|
||||||
|
(controls as any).enabled = true;
|
||||||
|
}
|
||||||
|
if (evt.button === 0) {
|
||||||
|
isLeftMouseDown = false;
|
||||||
|
if (drag) return;
|
||||||
|
|
||||||
|
if (deleteModels) {
|
||||||
|
DeleteFloorItems(itemsGroup, hoveredDeletableFloorItem, setFloorItems, socket);
|
||||||
|
}
|
||||||
|
const Mode = transformMode;
|
||||||
|
|
||||||
|
if (Mode !== null || activeTool === "Cursor") {
|
||||||
|
if (!itemsGroup.current) return;
|
||||||
|
let intersects = raycaster.intersectObjects(itemsGroup.current.children, true);
|
||||||
|
if (intersects.length > 0 && intersects[0]?.object?.parent?.parent?.position && intersects[0]?.object?.parent?.parent?.scale && intersects[0]?.object?.parent?.parent?.rotation) {
|
||||||
|
// let currentObject = intersects[0].object;
|
||||||
|
|
||||||
|
// while (currentObject) {
|
||||||
|
// if (currentObject.name === "Scene") {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// currentObject = currentObject.parent as THREE.Object3D;
|
||||||
|
// }
|
||||||
|
// if (currentObject) {
|
||||||
|
// AttachedObject.current = currentObject as any;
|
||||||
|
// setselectedFloorItem(AttachedObject.current!);
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
const target = controls.getTarget(new THREE.Vector3());
|
||||||
|
await controls.setTarget(target.x, 0, target.z, true);
|
||||||
|
setselectedFloorItem(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDblClick = async (evt: any) => {
|
||||||
|
if (evt.button === 0) {
|
||||||
|
isLeftMouseDown = false;
|
||||||
|
if (drag) return;
|
||||||
|
|
||||||
|
const Mode = transformMode;
|
||||||
|
|
||||||
|
if (Mode !== null || activeTool === "Cursor") {
|
||||||
|
if (!itemsGroup.current) return;
|
||||||
|
let intersects = raycaster.intersectObjects(itemsGroup.current.children, true);
|
||||||
|
if (intersects.length > 0 && intersects[0]?.object?.parent?.parent?.position && intersects[0]?.object?.parent?.parent?.scale && intersects[0]?.object?.parent?.parent?.rotation) {
|
||||||
|
let currentObject = intersects[0].object;
|
||||||
|
|
||||||
|
while (currentObject) {
|
||||||
|
if (currentObject.name === "Scene") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
currentObject = currentObject.parent as THREE.Object3D;
|
||||||
|
}
|
||||||
|
if (currentObject) {
|
||||||
|
AttachedObject.current = currentObject as any;
|
||||||
|
// controls.fitToSphere(AttachedObject.current!, true);
|
||||||
|
|
||||||
|
const bbox = new THREE.Box3().setFromObject(AttachedObject.current);
|
||||||
|
const size = bbox.getSize(new THREE.Vector3());
|
||||||
|
const center = bbox.getCenter(new THREE.Vector3());
|
||||||
|
|
||||||
|
const front = new THREE.Vector3(0, 0, 1);
|
||||||
|
AttachedObject.current.localToWorld(front);
|
||||||
|
front.sub(AttachedObject.current.position).normalize();
|
||||||
|
|
||||||
|
const distance = Math.max(size.x, size.y, size.z) * 2;
|
||||||
|
const newPosition = center.clone().addScaledVector(front, distance);
|
||||||
|
|
||||||
|
controls.setPosition(newPosition.x, newPosition.y, newPosition.z, true);
|
||||||
|
controls.setTarget(center.x, center.y, center.z, true);
|
||||||
|
controls.fitToBox(AttachedObject.current!, true, { cover: true, paddingTop: 5, paddingLeft: 5, paddingBottom: 5, paddingRight: 5 });
|
||||||
|
|
||||||
|
setselectedFloorItem(AttachedObject.current!);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const target = controls.getTarget(new THREE.Vector3());
|
||||||
|
await controls.setTarget(target.x, 0, target.z, true);
|
||||||
|
setselectedFloorItem(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDrop = (event: any) => {
|
||||||
|
|
||||||
|
if (!event.dataTransfer?.files[0]) return;
|
||||||
|
|
||||||
|
if (selectedItem.id !== "" && event.dataTransfer?.files[0]) {
|
||||||
|
addAssetModel(raycaster, state.camera, state.pointer, floorGroup, setFloorItems, itemsGroup, isTempLoader, tempLoader, socket, selectedItem, setSelectedItem, plane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDragOver = (event: any) => {
|
||||||
|
event.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||||
|
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||||
|
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||||
|
canvasElement.addEventListener("dblclick", onDblClick);
|
||||||
|
canvasElement.addEventListener("drop", onDrop);
|
||||||
|
canvasElement.addEventListener("dragover", onDragOver);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||||
|
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||||
|
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||||
|
canvasElement.removeEventListener("dblclick", onDblClick);
|
||||||
|
canvasElement.removeEventListener("drop", onDrop);
|
||||||
|
canvasElement.removeEventListener("dragover", onDragOver);
|
||||||
|
};
|
||||||
|
}, [deleteModels, transformMode, controls, selectedItem, state.camera, state.pointer, activeTool]);
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
if (controls)
|
||||||
|
assetVisibility(itemsGroup, state.camera.position, renderDistance);
|
||||||
|
if (deleteModels) {
|
||||||
|
DeletableHoveredFloorItems(state, itemsGroup, hoveredDeletableFloorItem, setDeletableFloorItem);
|
||||||
|
} else if (!deleteModels) {
|
||||||
|
if (hoveredDeletableFloorItem.current) {
|
||||||
|
hoveredDeletableFloorItem.current = undefined;
|
||||||
|
setDeletableFloorItem(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group ref={itemsGroup} name="itemsGroup">
|
||||||
|
</group>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FloorItemsGroup;
|
||||||
197
app/src/modules/builder/groups/floorPlanGroup.tsx
Normal file
197
app/src/modules/builder/groups/floorPlanGroup.tsx
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import * as Types from '../../../types/world/worldTypes';
|
||||||
|
import { useActiveLayer, useDeletedLines, useDeletePointOrLine, useToolMode, useMovePoint, useNewLines, useRemovedLayer, useSocketStore, useToggleView, useUpdateScene } from "../../../store/store";
|
||||||
|
import Layer2DVisibility from "../geomentries/layers/layer2DVisibility";
|
||||||
|
import { useFrame, useThree } from "@react-three/fiber";
|
||||||
|
import DeletableLineorPoint from "../functions/deletableLineOrPoint";
|
||||||
|
import removeSoloPoint from "../geomentries/points/removeSoloPoint";
|
||||||
|
import removeReferenceLine from "../geomentries/lines/removeReferenceLine";
|
||||||
|
import DeleteLayer from "../geomentries/layers/deleteLayer";
|
||||||
|
import { getLines } from "../../../services/factoryBuilder/lines/getLinesApi";
|
||||||
|
import objectLinesToArray from "../geomentries/lines/lineConvertions/objectLinesToArray";
|
||||||
|
import loadInitialPoint from "../../scene/IntialLoad/loadInitialPoint";
|
||||||
|
import loadInitialLine from "../../scene/IntialLoad/loadInitialLine";
|
||||||
|
import deletePoint from "../geomentries/points/deletePoint";
|
||||||
|
import deleteLine from "../geomentries/lines/deleteLine";
|
||||||
|
import drawWall from "../geomentries/lines/drawWall";
|
||||||
|
import drawOnlyFloor from "../geomentries/floors/drawOnlyFloor";
|
||||||
|
import addDragControl from "../eventDeclaration/dragControlDeclaration";
|
||||||
|
|
||||||
|
|
||||||
|
const FloorPlanGroup = ({ floorPlanGroup, floorPlanGroupLine, floorPlanGroupPoint, floorGroup, currentLayerPoint, dragPointControls, hoveredDeletablePoint, hoveredDeletableLine, plane, line, lines, onlyFloorline, onlyFloorlines, ReferenceLineMesh, LineCreated, isSnapped, ispreSnapped, snappedPoint, isSnappedUUID, isAngleSnapped, anglesnappedPoint }: any) => {
|
||||||
|
const state = useThree();
|
||||||
|
const { scene, camera, gl, raycaster, controls } = state;
|
||||||
|
const { activeLayer, setActiveLayer } = useActiveLayer();
|
||||||
|
const { toggleView, setToggleView } = useToggleView();
|
||||||
|
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
|
||||||
|
const { toolMode, setToolMode } = useToolMode();
|
||||||
|
const { movePoint, setMovePoint } = useMovePoint();
|
||||||
|
const { removedLayer, setRemovedLayer } = useRemovedLayer();
|
||||||
|
const { updateScene, setUpdateScene } = useUpdateScene();
|
||||||
|
const { newLines, setNewLines } = useNewLines();
|
||||||
|
const { deletedLines, setDeletedLines } = useDeletedLines();
|
||||||
|
const { socket } = useSocketStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
addDragControl(dragPointControls, currentLayerPoint, state, floorPlanGroupPoint, floorPlanGroupLine, lines, onlyFloorlines, socket);
|
||||||
|
}, [state]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
// Load data from localStorage if available
|
||||||
|
getLines(organization).then((data) => {
|
||||||
|
|
||||||
|
const Lines: Types.Lines = objectLinesToArray(data);
|
||||||
|
|
||||||
|
// const data = localStorage.getItem("Lines");
|
||||||
|
|
||||||
|
if (Lines) {
|
||||||
|
lines.current = Lines;
|
||||||
|
loadInitialPoint(lines, floorPlanGroupPoint, currentLayerPoint, dragPointControls);
|
||||||
|
loadInitialLine(floorPlanGroupLine, lines);
|
||||||
|
setUpdateScene(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!toggleView) {
|
||||||
|
removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint);
|
||||||
|
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
|
||||||
|
}
|
||||||
|
}, [toggleView]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (toolMode === "Wall" || toolMode === "Floor") {
|
||||||
|
setDeletePointOrLine(false);
|
||||||
|
setMovePoint(false);
|
||||||
|
} else {
|
||||||
|
removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint);
|
||||||
|
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
|
||||||
|
}
|
||||||
|
}, [toolMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (movePoint) {
|
||||||
|
setToolMode(null);
|
||||||
|
setDeletePointOrLine(false);
|
||||||
|
if (dragPointControls.current) {
|
||||||
|
dragPointControls.current.enabled = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (dragPointControls.current) {
|
||||||
|
dragPointControls.current.enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [movePoint, toolMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (deletePointOrLine) {
|
||||||
|
setToolMode(null);
|
||||||
|
setMovePoint(false);
|
||||||
|
}
|
||||||
|
}, [deletePointOrLine]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
Layer2DVisibility(activeLayer, floorPlanGroup, floorPlanGroupLine, floorPlanGroupPoint, currentLayerPoint, dragPointControls);
|
||||||
|
}, [activeLayer]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (removedLayer !== null) {
|
||||||
|
DeleteLayer(removedLayer, lines, floorPlanGroupLine, floorPlanGroupPoint, onlyFloorlines, floorGroup, setDeletedLines, setRemovedLayer, socket);
|
||||||
|
}
|
||||||
|
}, [removedLayer]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
const canvasElement = gl.domElement;
|
||||||
|
|
||||||
|
let drag = false;
|
||||||
|
let isLeftMouseDown = false;
|
||||||
|
|
||||||
|
const onMouseDown = (evt: any) => {
|
||||||
|
if (evt.button === 0) {
|
||||||
|
isLeftMouseDown = true;
|
||||||
|
drag = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseUp = (evt: any) => {
|
||||||
|
if (evt.button === 0) {
|
||||||
|
isLeftMouseDown = false;
|
||||||
|
}
|
||||||
|
if (controls) {
|
||||||
|
(controls as any).enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMouseMove = () => {
|
||||||
|
if (isLeftMouseDown) {
|
||||||
|
drag = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onContextMenu = (e: any) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (toolMode === "Wall" || toolMode === "Floor") {
|
||||||
|
removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint);
|
||||||
|
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseClick = (evt: any) => {
|
||||||
|
if (!plane.current || drag) return;
|
||||||
|
|
||||||
|
if (deletePointOrLine) {
|
||||||
|
if (hoveredDeletablePoint.current !== null) {
|
||||||
|
deletePoint(hoveredDeletablePoint, onlyFloorlines, floorPlanGroupPoint, floorPlanGroupLine, lines, setDeletedLines, socket);
|
||||||
|
}
|
||||||
|
if (hoveredDeletableLine.current !== null) {
|
||||||
|
deleteLine(hoveredDeletableLine, onlyFloorlines, lines, floorPlanGroupLine, floorPlanGroupPoint, setDeletedLines, socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toolMode === "Wall") {
|
||||||
|
drawWall(raycaster, plane, floorPlanGroupPoint, snappedPoint, isSnapped, isSnappedUUID, line, ispreSnapped, anglesnappedPoint, isAngleSnapped, lines, floorPlanGroupLine, floorPlanGroup, ReferenceLineMesh, LineCreated, currentLayerPoint, dragPointControls, setNewLines, setDeletedLines, activeLayer, socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toolMode === "Floor") {
|
||||||
|
drawOnlyFloor(raycaster, state, camera, plane, floorPlanGroupPoint, snappedPoint, isSnapped, isSnappedUUID, line, ispreSnapped, anglesnappedPoint, isAngleSnapped, onlyFloorline, onlyFloorlines, lines, floorPlanGroupLine, floorPlanGroup, ReferenceLineMesh, LineCreated, currentLayerPoint, dragPointControls, setNewLines, setDeletedLines, activeLayer, socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deletePointOrLine || toolMode === "Wall" || toolMode === "Floor") {
|
||||||
|
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||||
|
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||||
|
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||||
|
canvasElement.addEventListener("click", onMouseClick);
|
||||||
|
canvasElement.addEventListener("contextmenu", onContextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||||
|
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||||
|
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||||
|
canvasElement.removeEventListener("click", onMouseClick);
|
||||||
|
canvasElement.removeEventListener("contextmenu", onContextMenu);
|
||||||
|
};
|
||||||
|
}, [deletePointOrLine, toolMode, activeLayer])
|
||||||
|
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
if (deletePointOrLine) {
|
||||||
|
DeletableLineorPoint(state, plane, floorPlanGroupLine, floorPlanGroupPoint, hoveredDeletableLine, hoveredDeletablePoint);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group ref={floorPlanGroup} visible={toggleView} name="floorPlanGroup">
|
||||||
|
<group ref={floorPlanGroupLine} name="floorPlanGroupLine"></group>
|
||||||
|
<group ref={floorPlanGroupPoint} name="floorPlanGroupPoint"></group>
|
||||||
|
</group>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FloorPlanGroup;
|
||||||
289
app/src/modules/builder/groups/wallItemsGroup.tsx
Normal file
289
app/src/modules/builder/groups/wallItemsGroup.tsx
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useDeleteModels, useDeletePointOrLine, useObjectPosition, useObjectRotation, useObjectScale, useSelectedWallItem, useSocketStore, useWallItems } from "../../../store/store";
|
||||||
|
import { Csg } from "../csg/csg";
|
||||||
|
import * as Types from "../../../types/world/worldTypes";
|
||||||
|
import * as CONSTANTS from "../../../types/world/worldConstants";
|
||||||
|
import * as THREE from "three";
|
||||||
|
import { useThree } from "@react-three/fiber";
|
||||||
|
import handleMeshMissed from "../eventFunctions/handleMeshMissed";
|
||||||
|
import DeleteWallItems from "../geomentries/walls/deleteWallItems";
|
||||||
|
import loadInitialWallItems from "../../scene/IntialLoad/loadInitialWallItems";
|
||||||
|
import AddWallItems from "../geomentries/walls/addWallItems";
|
||||||
|
|
||||||
|
|
||||||
|
const WallItemsGroup = ({ currentWallItem, AssetConfigurations, hoveredDeletableWallItem, selectedItemsIndex, setSelectedItemsIndex, CSGGroup }: any) => {
|
||||||
|
const { deleteModels, setDeleteModels } = useDeleteModels();
|
||||||
|
const { wallItems, setWallItems } = useWallItems();
|
||||||
|
const { objectPosition, setObjectPosition } = useObjectPosition();
|
||||||
|
const { objectScale, setObjectScale } = useObjectScale();
|
||||||
|
const { objectRotation, setObjectRotation } = useObjectRotation();
|
||||||
|
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
|
||||||
|
const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem();
|
||||||
|
const { socket } = useSocketStore();
|
||||||
|
const state = useThree();
|
||||||
|
const { pointer, camera, raycaster } = state;
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Load Wall Items from the backend
|
||||||
|
loadInitialWallItems(setWallItems, AssetConfigurations);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
////////// Update the Scale value changes in thewallItems State //////////
|
||||||
|
|
||||||
|
////////// Update the Position value changes in the selected item //////////
|
||||||
|
|
||||||
|
////////// Update the Rotation value changes in the selected item //////////
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (objectScale.x && objectScale.y && objectScale.z) {
|
||||||
|
let ScaledWallItems: Types.wallItems = [];
|
||||||
|
wallItems.forEach((items: any) => {
|
||||||
|
if (items.model?.uuid === currentWallItem.current?.parent?.uuid) {
|
||||||
|
items.scale = [objectScale.x, objectScale.y, objectScale.z];
|
||||||
|
}
|
||||||
|
ScaledWallItems.push(items);
|
||||||
|
});
|
||||||
|
setWallItems(ScaledWallItems);
|
||||||
|
}
|
||||||
|
}, [objectScale]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (objectPosition.x && objectPosition.y && objectPosition.z) {
|
||||||
|
let ScaledWallItems: Types.wallItems = [];
|
||||||
|
wallItems.forEach((items: any) => {
|
||||||
|
if (items.model?.uuid === currentWallItem.current?.parent?.uuid) {
|
||||||
|
items.position = [objectPosition.x, objectPosition.y, objectPosition.z];
|
||||||
|
}
|
||||||
|
ScaledWallItems.push(items);
|
||||||
|
});
|
||||||
|
setWallItems(ScaledWallItems);
|
||||||
|
}
|
||||||
|
}, [objectPosition]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (objectRotation.x && objectRotation.y && objectRotation.z) {
|
||||||
|
let ScaledWallItems: Types.wallItems = [];
|
||||||
|
wallItems.forEach((items: any) => {
|
||||||
|
if (items.model?.uuid === currentWallItem.current?.parent?.uuid) {
|
||||||
|
const radiansX = objectRotation.x * (Math.PI / 180);
|
||||||
|
const radiansY = objectRotation.y * (Math.PI / 180);
|
||||||
|
const radiansZ = objectRotation.z * (Math.PI / 180);
|
||||||
|
const quaternion = new THREE.Quaternion().setFromEuler(
|
||||||
|
new THREE.Euler(radiansX, radiansY, radiansZ)
|
||||||
|
);
|
||||||
|
items.quaternion = [quaternion.x, quaternion.y, quaternion.z, quaternion.w];
|
||||||
|
}
|
||||||
|
ScaledWallItems.push(items);
|
||||||
|
});
|
||||||
|
setWallItems(ScaledWallItems);
|
||||||
|
}
|
||||||
|
}, [objectRotation]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const canvasElement = state.gl.domElement;
|
||||||
|
function handlePointerMove(e: any) {
|
||||||
|
if (selectedItemsIndex !== null && !deletePointOrLine && e.buttons === 1) {
|
||||||
|
const Raycaster = state.raycaster;
|
||||||
|
const intersects = Raycaster.intersectObjects(CSGGroup.current?.children[0].children!, true);
|
||||||
|
const Object = intersects.find((child) => child.object.name.includes("WallRaycastReference"));
|
||||||
|
|
||||||
|
if (Object) {
|
||||||
|
(state.controls as any)!.enabled = false;
|
||||||
|
setWallItems((prevItems: any) => {
|
||||||
|
const updatedItems = [...prevItems];
|
||||||
|
let position: [number, number, number] = [0, 0, 0];
|
||||||
|
|
||||||
|
if (updatedItems[selectedItemsIndex].type === "Fixed-Move") {
|
||||||
|
position = [Object!.point.x, Math.floor(Object!.point.y / CONSTANTS.wallConfig.height) * CONSTANTS.wallConfig.height, Object!.point.z];
|
||||||
|
} else if (updatedItems[selectedItemsIndex].type === "Free-Move") {
|
||||||
|
position = [Object!.point.x, Object!.point.y, Object!.point.z];
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
setObjectPosition(new THREE.Vector3(...position));
|
||||||
|
setObjectRotation({
|
||||||
|
x: THREE.MathUtils.radToDeg(Object!.object.rotation.x),
|
||||||
|
y: THREE.MathUtils.radToDeg(Object!.object.rotation.y),
|
||||||
|
z: THREE.MathUtils.radToDeg(Object!.object.rotation.z),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
updatedItems[selectedItemsIndex] = {
|
||||||
|
...updatedItems[selectedItemsIndex],
|
||||||
|
position: position,
|
||||||
|
quaternion: Object!.object.quaternion.clone() as Types.QuaternionType,
|
||||||
|
};
|
||||||
|
|
||||||
|
return updatedItems;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePointerUp() {
|
||||||
|
const Raycaster = state.raycaster;
|
||||||
|
const intersects = Raycaster.intersectObjects(CSGGroup.current?.children[0].children!, true);
|
||||||
|
const Object = intersects.find((child) => child.object.name.includes("WallRaycastReference"));
|
||||||
|
if (Object) {
|
||||||
|
if (selectedItemsIndex !== null) {
|
||||||
|
let currentItem: any = null;
|
||||||
|
setWallItems((prevItems: any) => {
|
||||||
|
const updatedItems = [...prevItems];
|
||||||
|
const WallItemsForStorage = updatedItems.map((item) => {
|
||||||
|
const { model, ...rest } = item;
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
modeluuid: model?.uuid,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
currentItem = updatedItems[selectedItemsIndex];
|
||||||
|
localStorage.setItem("WallItems", JSON.stringify(WallItemsForStorage));
|
||||||
|
return updatedItems;
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// await setWallItem(
|
||||||
|
// organization,
|
||||||
|
// currentItem?.model?.uuid,
|
||||||
|
// currentItem.modelname,
|
||||||
|
// currentItem.type!,
|
||||||
|
// currentItem.csgposition!,
|
||||||
|
// currentItem.csgscale!,
|
||||||
|
// currentItem.position,
|
||||||
|
// currentItem.quaternion,
|
||||||
|
// currentItem.scale!,
|
||||||
|
// )
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
organization: organization,
|
||||||
|
modeluuid: currentItem.model?.uuid!,
|
||||||
|
modelname: currentItem.modelname!,
|
||||||
|
type: currentItem.type!,
|
||||||
|
csgposition: currentItem.csgposition!,
|
||||||
|
csgscale: currentItem.csgscale!,
|
||||||
|
position: currentItem.position!,
|
||||||
|
quaternion: currentItem.quaternion,
|
||||||
|
scale: currentItem.scale!,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:wallItems:set', data);
|
||||||
|
}, 0);
|
||||||
|
(state.controls as any)!.enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canvasElement.addEventListener("pointermove", handlePointerMove);
|
||||||
|
canvasElement.addEventListener("pointerup", handlePointerUp);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
canvasElement.removeEventListener("pointermove", handlePointerMove);
|
||||||
|
canvasElement.removeEventListener("pointerup", handlePointerUp);
|
||||||
|
};
|
||||||
|
}, [selectedItemsIndex]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const canvasElement = state.gl.domElement;
|
||||||
|
let drag = false;
|
||||||
|
let isLeftMouseDown = false;
|
||||||
|
|
||||||
|
const onMouseDown = (evt: any) => {
|
||||||
|
if (evt.button === 0) {
|
||||||
|
isLeftMouseDown = true;
|
||||||
|
drag = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseUp = (evt: any) => {
|
||||||
|
if (evt.button === 0) {
|
||||||
|
isLeftMouseDown = false;
|
||||||
|
if (!drag && deleteModels) {
|
||||||
|
DeleteWallItems(hoveredDeletableWallItem, setWallItems, wallItems, socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseMove = () => {
|
||||||
|
if (isLeftMouseDown) {
|
||||||
|
drag = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDrop = (event: any) => {
|
||||||
|
|
||||||
|
if (!event.dataTransfer?.files[0]) return
|
||||||
|
pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
|
||||||
|
pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
||||||
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
|
||||||
|
if (AssetConfigurations[(event.dataTransfer.files[0].name.split('.'))[0]]) {
|
||||||
|
const selected = (event.dataTransfer.files[0].name.split('.'))[0];
|
||||||
|
|
||||||
|
if (AssetConfigurations[selected]?.type) {
|
||||||
|
AddWallItems(selected, raycaster, CSGGroup, AssetConfigurations, setWallItems, socket);
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDragOver = (event: any) => {
|
||||||
|
event.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||||
|
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||||
|
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||||
|
canvasElement.addEventListener("drop", onDrop);
|
||||||
|
canvasElement.addEventListener("dragover", onDragOver);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||||
|
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||||
|
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||||
|
canvasElement.removeEventListener("drop", onDrop);
|
||||||
|
canvasElement.removeEventListener("dragover", onDragOver);
|
||||||
|
};
|
||||||
|
}, [deleteModels, wallItems])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (deleteModels) {
|
||||||
|
handleMeshMissed(currentWallItem, setSelectedWallItem, setSelectedItemsIndex);
|
||||||
|
setSelectedWallItem(null);
|
||||||
|
setSelectedItemsIndex(null);
|
||||||
|
}
|
||||||
|
}, [deleteModels])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{wallItems.map((item: Types.WallItem, index: number) => (
|
||||||
|
<group
|
||||||
|
key={index}
|
||||||
|
position={item.position}
|
||||||
|
quaternion={item.quaternion}
|
||||||
|
scale={item.scale}
|
||||||
|
>
|
||||||
|
<Csg
|
||||||
|
position={item.csgposition!}
|
||||||
|
scale={item.csgscale!}
|
||||||
|
model={item.model!}
|
||||||
|
hoveredDeletableWallItem={hoveredDeletableWallItem}
|
||||||
|
/>
|
||||||
|
</group>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WallItemsGroup;
|
||||||
56
app/src/modules/builder/groups/wallsAndWallItems.tsx
Normal file
56
app/src/modules/builder/groups/wallsAndWallItems.tsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { Geometry } from "@react-three/csg";
|
||||||
|
import { useDeleteModels, useSelectedWallItem, useToggleView, useTransformMode, useWallItems, useWalls } from "../../../store/store";
|
||||||
|
import handleMeshDown from "../eventFunctions/handleMeshDown";
|
||||||
|
import handleMeshMissed from "../eventFunctions/handleMeshMissed";
|
||||||
|
import WallsMesh from "./wallsMesh";
|
||||||
|
import WallItemsGroup from "./wallItemsGroup";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
|
||||||
|
const WallsAndWallItems = ({ CSGGroup, AssetConfigurations, setSelectedItemsIndex, selectedItemsIndex, currentWallItem, csg, lines, hoveredDeletableWallItem }: any) => {
|
||||||
|
const { walls, setWalls } = useWalls();
|
||||||
|
const { wallItems, setWallItems } = useWallItems();
|
||||||
|
const { toggleView, setToggleView } = useToggleView();
|
||||||
|
const { deleteModels, setDeleteModels } = useDeleteModels();
|
||||||
|
const { transformMode, setTransformMode } = useTransformMode();
|
||||||
|
const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (transformMode === null) {
|
||||||
|
if (!deleteModels) {
|
||||||
|
handleMeshMissed(currentWallItem, setSelectedWallItem, setSelectedItemsIndex);
|
||||||
|
setSelectedWallItem(null);
|
||||||
|
setSelectedItemsIndex(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [transformMode])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<mesh
|
||||||
|
ref={CSGGroup as any}
|
||||||
|
name="Walls"
|
||||||
|
key={walls.length}
|
||||||
|
receiveShadow
|
||||||
|
visible={!toggleView}
|
||||||
|
onClick={(event) => {
|
||||||
|
if (!deleteModels && transformMode !== null) {
|
||||||
|
handleMeshDown(event, currentWallItem, setSelectedWallItem, setSelectedItemsIndex, wallItems, toggleView);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onPointerMissed={() => {
|
||||||
|
if (!deleteModels) {
|
||||||
|
handleMeshMissed(currentWallItem, setSelectedWallItem, setSelectedItemsIndex);
|
||||||
|
setSelectedWallItem(null);
|
||||||
|
setSelectedItemsIndex(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Geometry ref={csg as any} computeVertexNormals useGroups>
|
||||||
|
<WallsMesh lines={lines} />
|
||||||
|
<WallItemsGroup currentWallItem={currentWallItem} AssetConfigurations={AssetConfigurations} hoveredDeletableWallItem={hoveredDeletableWallItem} selectedItemsIndex={selectedItemsIndex} setSelectedItemsIndex={setSelectedItemsIndex} CSGGroup={CSGGroup} />
|
||||||
|
</Geometry>
|
||||||
|
</mesh>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WallsAndWallItems;
|
||||||
65
app/src/modules/builder/groups/wallsMesh.tsx
Normal file
65
app/src/modules/builder/groups/wallsMesh.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
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 { useUpdateScene, useWalls } from '../../../store/store';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { getLines } from '../../../services/factoryBuilder/lines/getLinesApi';
|
||||||
|
import objectLinesToArray from '../geomentries/lines/lineConvertions/objectLinesToArray';
|
||||||
|
import loadWalls from '../geomentries/walls/loadWalls';
|
||||||
|
|
||||||
|
const WallsMesh = ({ lines }: any) => {
|
||||||
|
const { walls, setWalls } = useWalls();
|
||||||
|
const { updateScene, setUpdateScene } = useUpdateScene();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (updateScene) {
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
getLines(organization).then((data) => {
|
||||||
|
const Lines: Types.Lines = objectLinesToArray(data);
|
||||||
|
localStorage.setItem("Lines", JSON.stringify(Lines));
|
||||||
|
|
||||||
|
if (Lines) {
|
||||||
|
loadWalls(lines, setWalls);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setUpdateScene(false);
|
||||||
|
}
|
||||||
|
}, [updateScene])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{walls.map((wall: Types.Wall, index: number) => (
|
||||||
|
<mesh key={index}>
|
||||||
|
<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}
|
||||||
|
/>
|
||||||
|
</Base>
|
||||||
|
<mesh
|
||||||
|
castShadow
|
||||||
|
geometry={wall[0]}
|
||||||
|
rotation={wall[1]}
|
||||||
|
position={wall[2]}
|
||||||
|
name={`WallRaycastReference_${index + 1}`}
|
||||||
|
>
|
||||||
|
<MeshDiscardMaterial />
|
||||||
|
</mesh>
|
||||||
|
</mesh>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WallsMesh;
|
||||||
466
app/src/modules/builder/groups/zoneGroup.tsx
Normal file
466
app/src/modules/builder/groups/zoneGroup.tsx
Normal file
@@ -0,0 +1,466 @@
|
|||||||
|
import React, { useState, useEffect, useMemo, useRef } from "react";
|
||||||
|
import { Line, Sphere } from "@react-three/drei";
|
||||||
|
import { useThree, useFrame } from "@react-three/fiber";
|
||||||
|
import * as THREE from "three";
|
||||||
|
import { useActiveLayer, useDeleteModels, useDeletePointOrLine, useMovePoint, useSocketStore, useToggleView, useToolMode, useRemovedLayer, useZones, useZonePoints } from "../../../store/store";
|
||||||
|
// import { setZonesApi } from "../../../services/factoryBuilder/zones/setZonesApi";
|
||||||
|
// import { deleteZonesApi } from "../../../services/factoryBuilder/zones/deleteZoneApi";
|
||||||
|
import { getZonesApi } from "../../../services/factoryBuilder/zones/getZonesApi";
|
||||||
|
|
||||||
|
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||||
|
|
||||||
|
const ZoneGroup: React.FC = () => {
|
||||||
|
const { camera, pointer, gl, raycaster, scene, controls } = useThree();
|
||||||
|
const [startPoint, setStartPoint] = useState<THREE.Vector3 | null>(null);
|
||||||
|
const [endPoint, setEndPoint] = useState<THREE.Vector3 | null>(null);
|
||||||
|
const { zones, setZones } = useZones();
|
||||||
|
const { zonePoints, setZonePoints } = useZonePoints();
|
||||||
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
const [draggedSphere, setDraggedSphere] = useState<THREE.Vector3 | null>(null);
|
||||||
|
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
||||||
|
const { toggleView } = useToggleView();
|
||||||
|
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
|
||||||
|
const { removedLayer, setRemovedLayer } = useRemovedLayer();
|
||||||
|
const { toolMode, setToolMode } = useToolMode();
|
||||||
|
const { movePoint, setMovePoint } = useMovePoint();
|
||||||
|
const { deleteModels, setDeleteModels } = useDeleteModels();
|
||||||
|
const { activeLayer, setActiveLayer } = useActiveLayer();
|
||||||
|
const { socket } = useSocketStore();
|
||||||
|
|
||||||
|
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 uColor;
|
||||||
|
void main(){
|
||||||
|
float alpha = 1.0 - vUv.y;
|
||||||
|
gl_FragColor = vec4(uColor, alpha);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
uniforms: {
|
||||||
|
uColor: { value: new THREE.Color(CONSTANTS.zoneConfig.color) },
|
||||||
|
},
|
||||||
|
transparent: true,
|
||||||
|
}), []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchZones = async () => {
|
||||||
|
const email = localStorage.getItem('email');
|
||||||
|
if (!email) return;
|
||||||
|
|
||||||
|
const organization = email.split("@")[1].split(".")[0];
|
||||||
|
const data = await getZonesApi(organization);
|
||||||
|
|
||||||
|
if (data.data && data.data.length > 0) {
|
||||||
|
const fetchedZones = data.data.map((zone: any) => ({
|
||||||
|
zoneId: zone.zoneId,
|
||||||
|
zoneName: zone.zoneName,
|
||||||
|
points: zone.points,
|
||||||
|
layer: zone.layer
|
||||||
|
}));
|
||||||
|
|
||||||
|
setZones(fetchedZones);
|
||||||
|
|
||||||
|
const fetchedPoints = data.data.flatMap((zone: any) =>
|
||||||
|
zone.points.slice(0, 4).map((point: [number, number, number]) => new THREE.Vector3(...point))
|
||||||
|
);
|
||||||
|
|
||||||
|
setZonePoints(fetchedPoints);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchZones();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
localStorage.setItem('zones', zones);
|
||||||
|
|
||||||
|
}, [zones])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (removedLayer) {
|
||||||
|
const updatedZones = zones.filter((zone: any) => zone.layer !== removedLayer);
|
||||||
|
setZones(updatedZones);
|
||||||
|
|
||||||
|
const updatedzonePoints = zonePoints.filter((_: any, index: any) => {
|
||||||
|
const zoneIndex = Math.floor(index / 4);
|
||||||
|
return zones[zoneIndex]?.layer !== removedLayer;
|
||||||
|
});
|
||||||
|
setZonePoints(updatedzonePoints);
|
||||||
|
|
||||||
|
zones.filter((zone: any) => zone.layer === removedLayer).forEach((zone: any) => {
|
||||||
|
deleteZoneFromBackend(zone.zoneId);
|
||||||
|
});
|
||||||
|
|
||||||
|
setRemovedLayer(null);
|
||||||
|
}
|
||||||
|
}, [removedLayer]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (toolMode !== "Zone") {
|
||||||
|
setStartPoint(null);
|
||||||
|
setEndPoint(null);
|
||||||
|
} else {
|
||||||
|
setDeletePointOrLine(false);
|
||||||
|
setMovePoint(false);
|
||||||
|
setDeleteModels(false);
|
||||||
|
}
|
||||||
|
if (!toggleView) {
|
||||||
|
setStartPoint(null);
|
||||||
|
setEndPoint(null);
|
||||||
|
}
|
||||||
|
}, [toolMode, toggleView]);
|
||||||
|
|
||||||
|
|
||||||
|
const addZoneToBackend = async (zone: { zoneId: string; zoneName: string; points: [number, number, number][]; layer: string }) => {
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email');
|
||||||
|
const userId = localStorage.getItem('userId');
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
userId: userId,
|
||||||
|
organization: organization,
|
||||||
|
zoneData: {
|
||||||
|
zoneName: zone.zoneName,
|
||||||
|
zoneId: zone.zoneId,
|
||||||
|
points: zone.points,
|
||||||
|
layer: zone.layer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v2:zone:set', input);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateZoneToBackend = async (zone: { zoneId: string; zoneName: string; points: [number, number, number][]; layer: string }) => {
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email');
|
||||||
|
const userId = localStorage.getItem('userId');
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
userId: userId,
|
||||||
|
organization: organization,
|
||||||
|
zoneData: {
|
||||||
|
zoneName: zone.zoneName,
|
||||||
|
zoneId: zone.zoneId,
|
||||||
|
points: zone.points,
|
||||||
|
layer: zone.layer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v2:zone:set', input);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteZoneFromBackend = async (zoneId: string) => {
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email');
|
||||||
|
const userId = localStorage.getItem('userId');
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
userId: userId,
|
||||||
|
organization: organization,
|
||||||
|
zoneId: zoneId
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v2:zone:delete', input);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteZone = (zoneId: string) => {
|
||||||
|
const updatedZones = zones.filter((zone: any) => zone.zoneId !== zoneId);
|
||||||
|
setZones(updatedZones);
|
||||||
|
|
||||||
|
const zoneIndex = zones.findIndex((zone: any) => zone.zoneId === zoneId);
|
||||||
|
if (zoneIndex !== -1) {
|
||||||
|
const zonePointsToRemove = zonePoints.slice(zoneIndex * 4, zoneIndex * 4 + 4);
|
||||||
|
zonePointsToRemove.forEach((point: any) => groupsRef.current.remove(point));
|
||||||
|
const updatedzonePoints = zonePoints.filter((_: any, index: any) => index < zoneIndex * 4 || index >= zoneIndex * 4 + 4);
|
||||||
|
setZonePoints(updatedzonePoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteZoneFromBackend(zoneId);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!camera || !toggleView) return;
|
||||||
|
const canvasElement = gl.domElement;
|
||||||
|
|
||||||
|
let drag = false;
|
||||||
|
let isLeftMouseDown = false;
|
||||||
|
|
||||||
|
const onMouseDown = (evt: any) => {
|
||||||
|
if (evt.button === 0) {
|
||||||
|
isLeftMouseDown = true;
|
||||||
|
drag = false;
|
||||||
|
|
||||||
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
const intersects = raycaster.intersectObjects(groupsRef.current.children, true);
|
||||||
|
|
||||||
|
if (intersects.length > 0 && movePoint) {
|
||||||
|
const clickedObject = intersects[0].object;
|
||||||
|
const sphereIndex = zonePoints.findIndex((point: any) => point.equals(clickedObject.position));
|
||||||
|
if (sphereIndex !== -1) {
|
||||||
|
(controls as any).enabled = false;
|
||||||
|
setDraggedSphere(zonePoints[sphereIndex]);
|
||||||
|
setIsDragging(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseUp = (evt: any) => {
|
||||||
|
if (evt.button === 0 && !drag && !isDragging && !deletePointOrLine) {
|
||||||
|
isLeftMouseDown = false;
|
||||||
|
|
||||||
|
if (!startPoint && !movePoint) {
|
||||||
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
const intersectionPoint = new THREE.Vector3();
|
||||||
|
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||||
|
if (point) {
|
||||||
|
setStartPoint(point);
|
||||||
|
setEndPoint(null);
|
||||||
|
}
|
||||||
|
} else if (startPoint && !movePoint) {
|
||||||
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
const intersectionPoint = new THREE.Vector3();
|
||||||
|
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||||
|
if (!point) return;
|
||||||
|
|
||||||
|
const points = [
|
||||||
|
[startPoint.x, 0.15, startPoint.z],
|
||||||
|
[point.x, 0.15, startPoint.z],
|
||||||
|
[point.x, 0.15, point.z],
|
||||||
|
[startPoint.x, 0.15, point.z],
|
||||||
|
[startPoint.x, 0.15, startPoint.z],
|
||||||
|
] as [number, number, number][];
|
||||||
|
|
||||||
|
const zoneName = `Zone ${zones.length + 1}`;
|
||||||
|
const zoneId = THREE.MathUtils.generateUUID();
|
||||||
|
const newZone = {
|
||||||
|
zoneId,
|
||||||
|
zoneName,
|
||||||
|
points: points,
|
||||||
|
layer: activeLayer
|
||||||
|
};
|
||||||
|
|
||||||
|
const newZones = [...zones, newZone];
|
||||||
|
|
||||||
|
setZones(newZones);
|
||||||
|
|
||||||
|
const newzonePoints = [
|
||||||
|
new THREE.Vector3(startPoint.x, 0.15, startPoint.z),
|
||||||
|
new THREE.Vector3(point.x, 0.15, startPoint.z),
|
||||||
|
new THREE.Vector3(point.x, 0.15, point.z),
|
||||||
|
new THREE.Vector3(startPoint.x, 0.15, point.z),
|
||||||
|
];
|
||||||
|
|
||||||
|
const updatedZonePoints = [...zonePoints, ...newzonePoints];
|
||||||
|
setZonePoints(updatedZonePoints);
|
||||||
|
|
||||||
|
addZoneToBackend(newZone);
|
||||||
|
|
||||||
|
setStartPoint(null);
|
||||||
|
setEndPoint(null);
|
||||||
|
}
|
||||||
|
} else if (evt.button === 0 && !drag && !isDragging && deletePointOrLine) {
|
||||||
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
const intersects = raycaster.intersectObjects(groupsRef.current.children, true);
|
||||||
|
|
||||||
|
if (intersects.length > 0) {
|
||||||
|
const clickedObject = intersects[0].object;
|
||||||
|
|
||||||
|
const sphereIndex = zonePoints.findIndex((point: any) => point.equals(clickedObject.position));
|
||||||
|
if (sphereIndex !== -1) {
|
||||||
|
const zoneIndex = Math.floor(sphereIndex / 4);
|
||||||
|
const zoneId = zones[zoneIndex].zoneId;
|
||||||
|
handleDeleteZone(zoneId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evt.button === 0) {
|
||||||
|
if (isDragging && draggedSphere) {
|
||||||
|
setIsDragging(false);
|
||||||
|
setDraggedSphere(null);
|
||||||
|
|
||||||
|
const sphereIndex = zonePoints.findIndex((point: any) => point === draggedSphere);
|
||||||
|
if (sphereIndex !== -1) {
|
||||||
|
const zoneIndex = Math.floor(sphereIndex / 4);
|
||||||
|
|
||||||
|
if (zoneIndex !== -1 && zones[zoneIndex]) {
|
||||||
|
updateZoneToBackend(zones[zoneIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseMove = () => {
|
||||||
|
if (isLeftMouseDown) {
|
||||||
|
drag = true;
|
||||||
|
}
|
||||||
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
const intersects = raycaster.intersectObjects(groupsRef.current.children, true);
|
||||||
|
|
||||||
|
if (intersects.length > 0 && intersects[0].object.name.includes('point')) {
|
||||||
|
gl.domElement.style.cursor = movePoint ? "pointer" : "default";
|
||||||
|
} else {
|
||||||
|
gl.domElement.style.cursor = "default";
|
||||||
|
}
|
||||||
|
if (isDragging && draggedSphere) {
|
||||||
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
const intersectionPoint = new THREE.Vector3();
|
||||||
|
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||||
|
if (point) {
|
||||||
|
draggedSphere.set(point.x, 0.15, point.z);
|
||||||
|
|
||||||
|
const sphereIndex = zonePoints.findIndex((point: any) => point === draggedSphere);
|
||||||
|
if (sphereIndex !== -1) {
|
||||||
|
const zoneIndex = Math.floor(sphereIndex / 4);
|
||||||
|
const cornerIndex = sphereIndex % 4;
|
||||||
|
|
||||||
|
const updatedZones = zones.map((zone: any, index: number) => {
|
||||||
|
if (index === zoneIndex) {
|
||||||
|
const updatedPoints = [...zone.points];
|
||||||
|
updatedPoints[cornerIndex] = [point.x, 0.15, point.z];
|
||||||
|
updatedPoints[4] = updatedPoints[0];
|
||||||
|
return { ...zone, points: updatedPoints };
|
||||||
|
}
|
||||||
|
return zone;
|
||||||
|
});
|
||||||
|
|
||||||
|
setZones(updatedZones);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onContext = (event: any) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setStartPoint(null);
|
||||||
|
setEndPoint(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (toolMode === 'Zone' || deletePointOrLine || movePoint) {
|
||||||
|
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||||
|
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||||
|
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||||
|
canvasElement.addEventListener("contextmenu", onContext);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||||
|
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||||
|
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||||
|
canvasElement.removeEventListener("contextmenu", onContext);
|
||||||
|
};
|
||||||
|
}, [gl, camera, startPoint, toggleView, scene, toolMode, zones, isDragging, deletePointOrLine, zonePoints, draggedSphere, movePoint, activeLayer]);
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
if (!startPoint) return;
|
||||||
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
const intersectionPoint = new THREE.Vector3();
|
||||||
|
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||||
|
if (point) {
|
||||||
|
setEndPoint(point);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<group ref={groupsRef} name='zoneGroup' >
|
||||||
|
<group name="zones" visible={!toggleView}>
|
||||||
|
{zones
|
||||||
|
.map((zone: any) => (
|
||||||
|
<group key={zone.zoneId} name={zone.zoneName}>
|
||||||
|
{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.wallConfig.height;
|
||||||
|
|
||||||
|
const midpoint = new THREE.Vector3((point1.x + point2.x) / 2, (CONSTANTS.wallConfig.height / 2) + ((zone.layer - 1) * CONSTANTS.wallConfig.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>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</group>
|
||||||
|
))}
|
||||||
|
</group>
|
||||||
|
<group name='zoneLines' visible={toggleView}>
|
||||||
|
{zones
|
||||||
|
.filter((zone: any) => zone.layer === activeLayer)
|
||||||
|
.map((zone: any) => (
|
||||||
|
<Line
|
||||||
|
key={zone.zoneId}
|
||||||
|
points={zone.points}
|
||||||
|
color="#007BFF"
|
||||||
|
lineWidth={3}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (deletePointOrLine) {
|
||||||
|
handleDeleteZone(zone.zoneId);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</group>
|
||||||
|
<group name="zonePoints" visible={toggleView}>
|
||||||
|
{zones.filter((zone: any) => zone.layer === activeLayer).flatMap((zone: any) => (
|
||||||
|
zone.points.slice(0, 4).map((point: any, pointIndex: number) => (
|
||||||
|
<Sphere
|
||||||
|
key={`${zone.zoneId}-point-${pointIndex}`}
|
||||||
|
position={new THREE.Vector3(...point)}
|
||||||
|
args={[0.3, 16, 16]}
|
||||||
|
name={`point-${zone.zoneId}-${pointIndex}`}
|
||||||
|
>
|
||||||
|
<meshBasicMaterial color="red" />
|
||||||
|
</Sphere>
|
||||||
|
))
|
||||||
|
))}
|
||||||
|
</group>
|
||||||
|
<group name="tempGroup" visible={toggleView}>
|
||||||
|
{startPoint && endPoint && (
|
||||||
|
<Line
|
||||||
|
points={[
|
||||||
|
[startPoint.x, 0.15, startPoint.z],
|
||||||
|
[endPoint.x, 0.15, startPoint.z],
|
||||||
|
[endPoint.x, 0.15, endPoint.z],
|
||||||
|
[startPoint.x, 0.15, endPoint.z],
|
||||||
|
[startPoint.x, 0.15, startPoint.z],
|
||||||
|
]}
|
||||||
|
color="#C164FF"
|
||||||
|
lineWidth={3}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ZoneGroup;
|
||||||
245
app/src/modules/builder/groups/zoneGroup1.tsx
Normal file
245
app/src/modules/builder/groups/zoneGroup1.tsx
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import * as Types from '../../../types/world/worldTypes';
|
||||||
|
import * as CONSTANTS from "../../../types/world/worldConstants";
|
||||||
|
import { useActiveLayer, useSocketStore, useDeleteModels, useDeletePointOrLine, useMovePoint, useToggleView, useUpdateScene, useNewLines, useToolMode } from "../../../store/store";
|
||||||
|
import { useThree } from "@react-three/fiber";
|
||||||
|
import arrayLineToObject from "../geomentries/lines/lineConvertions/arrayLineToObject";
|
||||||
|
import addPointToScene from "../geomentries/points/addPointToScene";
|
||||||
|
import addLineToScene from "../geomentries/lines/addLineToScene";
|
||||||
|
import removeSoloPoint from "../geomentries/points/removeSoloPoint";
|
||||||
|
import removeReferenceLine from "../geomentries/lines/removeReferenceLine";
|
||||||
|
import getClosestIntersection from "../geomentries/lines/getClosestIntersection";
|
||||||
|
import loadZones from "../geomentries/zones/loadZones";
|
||||||
|
|
||||||
|
const ZoneGroup = ({ zoneGroup, plane, floorPlanGroupLine, floorPlanGroupPoint, line, lines, currentLayerPoint, dragPointControls, floorPlanGroup, ReferenceLineMesh, LineCreated, isSnapped, ispreSnapped, snappedPoint, isSnappedUUID, isAngleSnapped, anglesnappedPoint }: any) => {
|
||||||
|
const { toggleView, setToggleView } = useToggleView();
|
||||||
|
const { deleteModels, setDeleteModels } = useDeleteModels();
|
||||||
|
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
|
||||||
|
const { toolMode, setToolMode } = useToolMode();
|
||||||
|
const { movePoint, setMovePoint } = useMovePoint();
|
||||||
|
const { socket } = useSocketStore();
|
||||||
|
const { activeLayer } = useActiveLayer();
|
||||||
|
const { gl, raycaster, camera, pointer } = useThree();
|
||||||
|
const { updateScene, setUpdateScene } = useUpdateScene();
|
||||||
|
const { newLines, setNewLines } = useNewLines();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (updateScene) {
|
||||||
|
loadZones(lines, zoneGroup);
|
||||||
|
setUpdateScene(false);
|
||||||
|
}
|
||||||
|
}, [updateScene])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (toolMode === "Zone") {
|
||||||
|
setDeletePointOrLine(false);
|
||||||
|
setMovePoint(false);
|
||||||
|
setDeleteModels(false);
|
||||||
|
} else {
|
||||||
|
removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint);
|
||||||
|
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
|
||||||
|
}
|
||||||
|
}, [toolMode])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
const canvasElement = gl.domElement;
|
||||||
|
|
||||||
|
let drag = false;
|
||||||
|
let isLeftMouseDown = false;
|
||||||
|
|
||||||
|
const onMouseDown = (evt: any) => {
|
||||||
|
if (evt.button === 0) {
|
||||||
|
isLeftMouseDown = true;
|
||||||
|
drag = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseUp = (evt: any) => {
|
||||||
|
if (evt.button === 0) {
|
||||||
|
isLeftMouseDown = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMouseMove = () => {
|
||||||
|
if (isLeftMouseDown) {
|
||||||
|
drag = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onContextMenu = (e: any) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (toolMode === "Zone") {
|
||||||
|
removeSoloPoint(line, floorPlanGroupLine, floorPlanGroupPoint);
|
||||||
|
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseClick = (evt: any) => {
|
||||||
|
if (!plane.current || drag) return;
|
||||||
|
const intersects = raycaster.intersectObject(plane.current, true);
|
||||||
|
let intersectionPoint = intersects[0].point;
|
||||||
|
const points = floorPlanGroupPoint.current?.children ?? [];
|
||||||
|
const intersectsPoint = raycaster.intersectObjects(points, true).find(intersect => intersect.object.visible);
|
||||||
|
let intersectsLines: any = raycaster.intersectObjects(floorPlanGroupLine.current.children, true);
|
||||||
|
|
||||||
|
if (intersectsLines.length > 0 && intersects && intersects.length > 0 && !intersectsPoint) {
|
||||||
|
const lineType = intersectsLines[0].object.userData.linePoints[0][3];
|
||||||
|
if (lineType === CONSTANTS.lineConfig.zoneName) {
|
||||||
|
// console.log("intersected a zone line");
|
||||||
|
|
||||||
|
const ThroughPoint = (intersectsLines[0].object.geometry.parameters.path).getPoints(300);
|
||||||
|
let intersection = getClosestIntersection(ThroughPoint, intersectionPoint);
|
||||||
|
if (!intersection) return;
|
||||||
|
const point = addPointToScene(intersection, CONSTANTS.pointConfig.zoneOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.zoneName);
|
||||||
|
(line.current as Types.Line).push([new THREE.Vector3(intersection.x, 0.01, intersection.z), point.uuid, activeLayer, CONSTANTS.lineConfig.zoneName,]);
|
||||||
|
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
|
||||||
|
lines.current.push(line.current as Types.Line);
|
||||||
|
|
||||||
|
const data = arrayLineToObject(line.current as Types.Line);
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// setLine(organization, data.layer!, data.line!, data.type!);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
organization: organization,
|
||||||
|
layer: data.layer,
|
||||||
|
line: data.line,
|
||||||
|
type: data.type,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:Line:create', input);
|
||||||
|
|
||||||
|
setNewLines([line.current]);
|
||||||
|
|
||||||
|
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.zoneColor, line.current, floorPlanGroupLine);
|
||||||
|
let lastPoint = line.current[line.current.length - 1];
|
||||||
|
line.current = [lastPoint];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (intersectsPoint && intersects && intersects.length > 0) {
|
||||||
|
if (intersectsPoint.object.userData.type === CONSTANTS.lineConfig.zoneName) {
|
||||||
|
// console.log("intersected a zone point");
|
||||||
|
|
||||||
|
intersectionPoint = intersectsPoint.object.position;
|
||||||
|
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), intersectsPoint.object.uuid, activeLayer, CONSTANTS.lineConfig.zoneName]);
|
||||||
|
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
|
||||||
|
lines.current.push(line.current as Types.Line);
|
||||||
|
|
||||||
|
const data = arrayLineToObject(line.current as Types.Line);
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// setLine(organization, data.layer!, data.line!, data.type!);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
organization: organization,
|
||||||
|
layer: data.layer,
|
||||||
|
line: data.line,
|
||||||
|
type: data.type,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:Line:create', input);
|
||||||
|
|
||||||
|
setNewLines([line.current]);
|
||||||
|
|
||||||
|
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.zoneColor, line.current, floorPlanGroupLine);
|
||||||
|
let lastPoint = line.current[line.current.length - 1];
|
||||||
|
line.current = [lastPoint];
|
||||||
|
ispreSnapped.current = false;
|
||||||
|
isSnapped.current = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (intersects && intersects.length > 0) {
|
||||||
|
// console.log("intersected a empty area");
|
||||||
|
|
||||||
|
let uuid: string = "";
|
||||||
|
if (isAngleSnapped.current && anglesnappedPoint.current && line.current.length > 0) {
|
||||||
|
intersectionPoint = anglesnappedPoint.current;
|
||||||
|
const point = addPointToScene(intersectionPoint, CONSTANTS.pointConfig.zoneOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.zoneName);
|
||||||
|
uuid = point.uuid;
|
||||||
|
} else if (isSnapped.current && snappedPoint.current && line.current.length > 0) {
|
||||||
|
intersectionPoint = snappedPoint.current;
|
||||||
|
uuid = isSnappedUUID.current!;
|
||||||
|
} else if (ispreSnapped.current && snappedPoint.current) {
|
||||||
|
intersectionPoint = snappedPoint.current;
|
||||||
|
uuid = isSnappedUUID.current!;
|
||||||
|
} else {
|
||||||
|
const point = addPointToScene(intersectionPoint, CONSTANTS.pointConfig.zoneOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, undefined, CONSTANTS.lineConfig.zoneName);
|
||||||
|
uuid = point.uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), uuid, activeLayer, CONSTANTS.lineConfig.zoneName]);
|
||||||
|
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
|
||||||
|
lines.current.push(line.current as Types.Line);
|
||||||
|
|
||||||
|
const data = arrayLineToObject(line.current as Types.Line);
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// setLine(organization, data.layer!, data.line!, data.type!);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
organization: organization,
|
||||||
|
layer: data.layer,
|
||||||
|
line: data.line,
|
||||||
|
type: data.type,
|
||||||
|
socketId: socket.id
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:Line:create', input);
|
||||||
|
|
||||||
|
setNewLines([line.current]);
|
||||||
|
|
||||||
|
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.zoneColor, line.current, floorPlanGroupLine);
|
||||||
|
let lastPoint = line.current[line.current.length - 1];
|
||||||
|
line.current = [lastPoint];
|
||||||
|
ispreSnapped.current = false;
|
||||||
|
isSnapped.current = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toolMode === 'Zone') {
|
||||||
|
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||||
|
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||||
|
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||||
|
canvasElement.addEventListener("click", onMouseClick);
|
||||||
|
canvasElement.addEventListener("contextmenu", onContextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||||
|
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||||
|
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||||
|
canvasElement.removeEventListener("click", onMouseClick);
|
||||||
|
canvasElement.removeEventListener("contextmenu", onContextMenu);
|
||||||
|
};
|
||||||
|
}, [toolMode])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group ref={zoneGroup} visible={!toggleView} name="zoneGroup">
|
||||||
|
</group>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ZoneGroup;
|
||||||
142
app/src/modules/collaboration/collabCams.tsx
Normal file
142
app/src/modules/collaboration/collabCams.tsx
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { useFrame } from '@react-three/fiber';
|
||||||
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||||
|
import camModel from '../../assets/gltf-glb/camera face 2.gltf';
|
||||||
|
import getActiveUsersData from '../../services/factoryBuilder/collab/getActiveUsers';
|
||||||
|
import { useActiveUsers, useSocketStore } from '../../store/store';
|
||||||
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { Text, Html } from '@react-three/drei';
|
||||||
|
import CollabUserIcon from './collabUserIcon';
|
||||||
|
import image from '../../assets/image/userImage.png';
|
||||||
|
|
||||||
|
|
||||||
|
const CamModelsGroup = () => {
|
||||||
|
let navigate = useNavigate();
|
||||||
|
const groupRef = useRef<THREE.Group>(null);
|
||||||
|
const email = localStorage.getItem('email');
|
||||||
|
const { activeUsers, setActiveUsers } = useActiveUsers();
|
||||||
|
const { socket } = useSocketStore();
|
||||||
|
const loader = new GLTFLoader();
|
||||||
|
const dracoLoader = new DRACOLoader();
|
||||||
|
const [cams, setCams] = useState<any[]>([]);
|
||||||
|
const [models, setModels] = useState<Record<string, { targetPosition: THREE.Vector3; targetRotation: THREE.Euler }>>({});
|
||||||
|
|
||||||
|
dracoLoader.setDecoderPath('three/examples/jsm/libs/draco/gltf/');
|
||||||
|
loader.setDRACOLoader(dracoLoader);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!email) {
|
||||||
|
navigate('/');
|
||||||
|
}
|
||||||
|
if (!socket) return;
|
||||||
|
const organization = email!.split('@')[1].split('.')[0];
|
||||||
|
|
||||||
|
socket.on('userConnectRespones', (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;
|
||||||
|
setCams((prev) => [...prev, newModel]);
|
||||||
|
setActiveUsers([...activeUsers, data.data.userData]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('userDisConnectRespones', (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(activeUsers.filter((user: any) => user._id !== data.data.userData._id));
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('cameraUpdateResponse', (data: any) => {
|
||||||
|
if (!groupRef.current || socket.id === data.socketId || organization !== data.organization) return;
|
||||||
|
|
||||||
|
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),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.off('userConnectRespones');
|
||||||
|
socket.off('userDisConnectRespones');
|
||||||
|
socket.off('cameraUpdateResponse');
|
||||||
|
};
|
||||||
|
}, [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;
|
||||||
|
const organization = email!.split('@')[1].split('.')[0];
|
||||||
|
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;
|
||||||
|
setActiveUsers([...activeUsers, cam.userData]);
|
||||||
|
return newModel;
|
||||||
|
});
|
||||||
|
setCams((prev) => [...prev, ...newCams]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group ref={groupRef} name="Cam-Model-Group">
|
||||||
|
{cams.map((cam, index) => (
|
||||||
|
<primitive key={index} object={cam} >
|
||||||
|
<Html
|
||||||
|
as="div"
|
||||||
|
center
|
||||||
|
zIndexRange={[1, 0]}
|
||||||
|
sprite
|
||||||
|
style={{
|
||||||
|
color: "white",
|
||||||
|
textAlign: "center",
|
||||||
|
fontFamily: "Arial, sans-serif",
|
||||||
|
}}
|
||||||
|
position={[-0.015, 0, 0.7]}>
|
||||||
|
<CollabUserIcon color={"#ff0000"} userImage={image} userName={cam.userData.userName} />
|
||||||
|
</Html>
|
||||||
|
</primitive>
|
||||||
|
))}
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CamModelsGroup;
|
||||||
53
app/src/modules/collaboration/collabUserIcon.tsx
Normal file
53
app/src/modules/collaboration/collabUserIcon.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface CollabUserIconProps {
|
||||||
|
color: string;
|
||||||
|
userImage: string;
|
||||||
|
userName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CollabUserIcon: React.FC<CollabUserIconProps> = ({
|
||||||
|
color,
|
||||||
|
userImage,
|
||||||
|
userName,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "6px",
|
||||||
|
// transform:"translate(-20%, 0%)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={userImage}
|
||||||
|
alt={userName}
|
||||||
|
style={{
|
||||||
|
width: "30px",
|
||||||
|
height: "30px",
|
||||||
|
outline: `2px solid ${color}`,
|
||||||
|
borderRadius: "50%",
|
||||||
|
objectFit: 'cover'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
padding: "3px 5px",
|
||||||
|
backgroundColor: color,
|
||||||
|
borderRadius: "6px",
|
||||||
|
color: "white",
|
||||||
|
fontSize: "14px",
|
||||||
|
fontWeight: 400
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{userName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CollabUserIcon;
|
||||||
777
app/src/modules/collaboration/socketResponses.dev.tsx
Normal file
777
app/src/modules/collaboration/socketResponses.dev.tsx
Normal file
@@ -0,0 +1,777 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||||
|
import gsap from 'gsap';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useSocketStore, useActiveLayer, useWallItems, useFloorItems, useLayers, useUpdateScene, useWalls, useDeletedLines, useNewLines, useZonePoints, useZones } from "../../store/store";
|
||||||
|
|
||||||
|
import * as Types from "../../types/world/worldTypes";
|
||||||
|
import * as CONSTANTS from '../../types/world/worldConstants';
|
||||||
|
import TempLoader from "../builder/geomentries/assets/tempLoader";
|
||||||
|
|
||||||
|
// import { setFloorItemApi } from "../../services/factoryBuilder/assest/floorAsset/setFloorItemApi";
|
||||||
|
import objectLineToArray from "../builder/geomentries/lines/lineConvertions/objectLineToArray";
|
||||||
|
import addLineToScene from "../builder/geomentries/lines/addLineToScene";
|
||||||
|
import updateLinesPositions from "../builder/geomentries/lines/updateLinesPositions";
|
||||||
|
import updateLines from "../builder/geomentries/lines/updateLines";
|
||||||
|
import updateDistanceText from "../builder/geomentries/lines/updateDistanceText";
|
||||||
|
import updateFloorLines from "../builder/geomentries/floors/updateFloorLines";
|
||||||
|
import loadWalls from "../builder/geomentries/walls/loadWalls";
|
||||||
|
import RemoveConnectedLines from "../builder/geomentries/lines/removeConnectedLines";
|
||||||
|
import Layer2DVisibility from "../builder/geomentries/layers/layer2DVisibility";
|
||||||
|
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
|
||||||
|
import { retrieveGLTF, storeGLTF } from "../../utils/indexDB/idbUtils";
|
||||||
|
import { getZonesApi } from "../../services/factoryBuilder/zones/getZonesApi";
|
||||||
|
|
||||||
|
|
||||||
|
export default function SocketResponses({
|
||||||
|
floorPlanGroup,
|
||||||
|
lines,
|
||||||
|
floorGroup,
|
||||||
|
floorGroupAisle,
|
||||||
|
scene,
|
||||||
|
onlyFloorlines,
|
||||||
|
AssetConfigurations,
|
||||||
|
itemsGroup,
|
||||||
|
isTempLoader,
|
||||||
|
tempLoader,
|
||||||
|
currentLayerPoint,
|
||||||
|
floorPlanGroupPoint,
|
||||||
|
floorPlanGroupLine,
|
||||||
|
zoneGroup,
|
||||||
|
dragPointControls
|
||||||
|
}: any) {
|
||||||
|
|
||||||
|
const { socket } = useSocketStore();
|
||||||
|
const { activeLayer, setActiveLayer } = useActiveLayer();
|
||||||
|
const { wallItems, setWallItems } = useWallItems();
|
||||||
|
const { layers, setLayers } = useLayers();
|
||||||
|
const { floorItems, setFloorItems } = useFloorItems();
|
||||||
|
const { updateScene, setUpdateScene } = useUpdateScene();
|
||||||
|
const { walls, setWalls } = useWalls();
|
||||||
|
const { deletedLines, setDeletedLines } = useDeletedLines();
|
||||||
|
const { newLines, setNewLines } = useNewLines();
|
||||||
|
const { zones, setZones } = useZones();
|
||||||
|
const { zonePoints, setZonePoints } = useZonePoints();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
if (!socket) return
|
||||||
|
|
||||||
|
socket.on('cameraCreateResponse', (data: any) => {
|
||||||
|
// console.log('data: ', data);
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('userConnectRespones', (data: any) => {
|
||||||
|
// console.log('data: ', data);
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('userDisConnectRespones', (data: any) => {
|
||||||
|
// console.log('data: ', data);
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('cameraUpdateResponse', (data: any) => {
|
||||||
|
// console.log('data: ', data);
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('EnvironmentUpdateResponse', (data: any) => {
|
||||||
|
// console.log('data: ', data);
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('FloorItemsUpdateResponse', async (data: any) => {
|
||||||
|
if (socket.id === data.socketId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (organization !== data.organization) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (data.message === "flooritem created") {
|
||||||
|
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);
|
||||||
|
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
isTempLoader.current = true;
|
||||||
|
const cachedModel = THREE.Cache.get(data.data.modelname);
|
||||||
|
let url;
|
||||||
|
|
||||||
|
if (cachedModel) {
|
||||||
|
// console.log(`Getting ${data.data.modelname} from cache`);
|
||||||
|
url = URL.createObjectURL(cachedModel);
|
||||||
|
} else {
|
||||||
|
const indexedDBModel = await retrieveGLTF(data.data.modelname);
|
||||||
|
if (indexedDBModel) {
|
||||||
|
// console.log(`Getting ${data.data.modelname} from IndexedDB`);
|
||||||
|
url = URL.createObjectURL(indexedDBModel);
|
||||||
|
} else {
|
||||||
|
// console.log(`Getting ${data.data.modelname} from Backend`);
|
||||||
|
url = `${url_Backend_dwinzo}/api/v1/AssetFile/${data.data.modelfileID}`;
|
||||||
|
const modelBlob = await fetch(url).then((res) => res.blob());
|
||||||
|
await storeGLTF(data.data.modelfileID, modelBlob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadModel(url);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching asset model:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadModel(url: string) {
|
||||||
|
loader.load(url, (gltf) => {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
THREE.Cache.remove(url);
|
||||||
|
const model = gltf.scene;
|
||||||
|
model.uuid = data.data.modeluuid;
|
||||||
|
model.userData = { name: data.data.modelname, modelId: data.data.modelFileID };
|
||||||
|
model.position.set(...data.data.position as [number, number, number]);
|
||||||
|
model.rotation.set(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z);
|
||||||
|
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
|
||||||
|
|
||||||
|
model.traverse((child: any) => {
|
||||||
|
if (child.isMesh) {
|
||||||
|
// Clone the material to ensure changes are independent
|
||||||
|
// child.material = child.material.clone();
|
||||||
|
|
||||||
|
child.castShadow = true;
|
||||||
|
child.receiveShadow = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
itemsGroup.current.add(model);
|
||||||
|
|
||||||
|
if (tempLoader.current) {
|
||||||
|
tempLoader.current.material.dispose();
|
||||||
|
tempLoader.current.geometry.dispose();
|
||||||
|
itemsGroup.current.remove(tempLoader.current);
|
||||||
|
tempLoader.current = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newFloorItem: Types.FloorItemType = {
|
||||||
|
modeluuid: data.data.modeluuid,
|
||||||
|
modelname: data.data.modelname,
|
||||||
|
modelfileID: data.data.modelfileID,
|
||||||
|
position: [...data.data.position as [number, number, number]],
|
||||||
|
rotation: {
|
||||||
|
x: model.rotation.x,
|
||||||
|
y: model.rotation.y,
|
||||||
|
z: model.rotation.z,
|
||||||
|
},
|
||||||
|
isLocked: data.data.isLocked,
|
||||||
|
isVisible: data.data.isVisible,
|
||||||
|
};
|
||||||
|
|
||||||
|
setFloorItems((prevItems: any) => {
|
||||||
|
const updatedItems = [...(prevItems || []), newFloorItem];
|
||||||
|
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
|
||||||
|
return updatedItems;
|
||||||
|
});
|
||||||
|
|
||||||
|
gsap.to(model.position, { y: data.data.position[1], duration: 1.5, ease: 'power2.out' });
|
||||||
|
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: 'power2.out', onComplete: () => { toast.success("Model Added!") } });
|
||||||
|
|
||||||
|
THREE.Cache.add(data.data.modelname, gltf);
|
||||||
|
}, () => {
|
||||||
|
TempLoader(new THREE.Vector3(...data.data.position), isTempLoader, tempLoader, itemsGroup);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (data.message === "flooritems updated") {
|
||||||
|
itemsGroup.current.children.forEach((item: THREE.Group) => {
|
||||||
|
if (item.uuid === data.data.modeluuid) {
|
||||||
|
item.position.set(...data.data.position as [number, number, number]);
|
||||||
|
item.rotation.set(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
setFloorItems((prevItems: Types.FloorItems) => {
|
||||||
|
if (!prevItems) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let updatedItem: any = null;
|
||||||
|
const updatedItems = prevItems.map((item) => {
|
||||||
|
if (item.modeluuid === data.data.modeluuid) {
|
||||||
|
updatedItem = {
|
||||||
|
...item,
|
||||||
|
position: [...data.data.position] as [number, number, number],
|
||||||
|
rotation: { x: data.data.rotation.x, y: data.data.rotation.y, z: data.data.rotation.z, },
|
||||||
|
};
|
||||||
|
return updatedItem;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
return updatedItems;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('FloorItemsDeleteResponse', (data: any) => {
|
||||||
|
if (socket.id === data.socketId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (organization !== data.organization) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (data.message === "flooritem deleted") {
|
||||||
|
const deletedUUID = data.data.modeluuid;
|
||||||
|
let items = JSON.parse(localStorage.getItem("FloorItems")!);
|
||||||
|
|
||||||
|
const updatedItems = items.filter(
|
||||||
|
(item: { modeluuid: string }) => item.modeluuid !== deletedUUID
|
||||||
|
);
|
||||||
|
|
||||||
|
const storedItems = JSON.parse(localStorage.getItem("FloorItems") || '[]');
|
||||||
|
const updatedStoredItems = storedItems.filter((item: { modeluuid: string }) => item.modeluuid !== deletedUUID);
|
||||||
|
localStorage.setItem("FloorItems", JSON.stringify(updatedStoredItems));
|
||||||
|
|
||||||
|
itemsGroup.current.children.forEach((item: any) => {
|
||||||
|
if (item.uuid === deletedUUID) {
|
||||||
|
itemsGroup.current.remove(item);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setFloorItems(updatedItems);
|
||||||
|
toast.success("Model Removed!");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('Line:response:update', (data: any) => {
|
||||||
|
if (socket.id === data.socketId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (organization !== data.organization) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (data.message === "line updated") {
|
||||||
|
const DraggedUUID = data.data.uuid;
|
||||||
|
const DraggedPosition = new THREE.Vector3(data.data.position.x, data.data.position.y, data.data.position.z);
|
||||||
|
|
||||||
|
const point = floorPlanGroupPoint.current.getObjectByProperty('uuid', DraggedUUID);
|
||||||
|
point.position.set(DraggedPosition.x, DraggedPosition.y, DraggedPosition.z);
|
||||||
|
const affectedLines = updateLinesPositions({ uuid: DraggedUUID, position: DraggedPosition }, lines);
|
||||||
|
|
||||||
|
updateLines(floorPlanGroupLine, affectedLines);
|
||||||
|
updateDistanceText(scene, floorPlanGroupLine, affectedLines);
|
||||||
|
updateFloorLines(onlyFloorlines, { uuid: DraggedUUID, position: DraggedPosition });
|
||||||
|
|
||||||
|
loadWalls(lines, setWalls);
|
||||||
|
setUpdateScene(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('Line:response:delete', (data: any) => {
|
||||||
|
if (socket.id === data.socketId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (organization !== data.organization) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (data.message === "line deleted") {
|
||||||
|
const line = objectLineToArray(data.data);
|
||||||
|
const linePoints = line;
|
||||||
|
const connectedpoints = [linePoints[0][1], linePoints[1][1]];
|
||||||
|
|
||||||
|
|
||||||
|
onlyFloorlines.current = onlyFloorlines.current.map((floorline: any) =>
|
||||||
|
floorline.filter((line: any) => line[0][1] !== connectedpoints[0] && line[1][1] !== connectedpoints[1])
|
||||||
|
).filter((floorline: any) => floorline.length > 0);
|
||||||
|
|
||||||
|
const removedLine = lines.current.find((item: any) => (item[0][1] === linePoints[0][1] && item[1][1] === linePoints[1][1] || (item[0][1] === linePoints[1][1] && item[1][1] === linePoints[0][1])));
|
||||||
|
lines.current = lines.current.filter((item: any) => item !== removedLine);
|
||||||
|
|
||||||
|
floorPlanGroupLine.current.children.forEach((line: any) => {
|
||||||
|
const linePoints = line.userData.linePoints as [number, string, number][];
|
||||||
|
const uuid1 = linePoints[0][1];
|
||||||
|
const uuid2 = linePoints[1][1];
|
||||||
|
|
||||||
|
if ((uuid1 === connectedpoints[0] && uuid2 === connectedpoints[1] || (uuid1 === connectedpoints[1] && uuid2 === connectedpoints[0]))) {
|
||||||
|
line.material.dispose();
|
||||||
|
line.geometry.dispose();
|
||||||
|
floorPlanGroupLine.current.remove(line);
|
||||||
|
setDeletedLines([line.userData.linePoints])
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connectedpoints.forEach((pointUUID) => {
|
||||||
|
let isConnected = false;
|
||||||
|
floorPlanGroupLine.current.children.forEach((line: any) => {
|
||||||
|
const linePoints = line.userData.linePoints;
|
||||||
|
const uuid1 = linePoints[0][1];
|
||||||
|
const uuid2 = linePoints[1][1];
|
||||||
|
if (uuid1 === pointUUID || uuid2 === pointUUID) {
|
||||||
|
isConnected = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isConnected) {
|
||||||
|
floorPlanGroupPoint.current.children.forEach((point: any) => {
|
||||||
|
if (point.uuid === pointUUID) {
|
||||||
|
point.material.dispose();
|
||||||
|
point.geometry.dispose();
|
||||||
|
floorPlanGroupPoint.current.remove(point);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
loadWalls(lines, setWalls);
|
||||||
|
setUpdateScene(true);
|
||||||
|
|
||||||
|
toast.success("Line Removed!");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('Line:response:delete:point', (data: any) => {
|
||||||
|
if (socket.id === data.socketId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (organization !== data.organization) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (data.message === "point deleted") {
|
||||||
|
const point = floorPlanGroupPoint.current?.getObjectByProperty('uuid', data.data);
|
||||||
|
point.material.dispose();
|
||||||
|
point.geometry.dispose();
|
||||||
|
floorPlanGroupPoint.current.remove(point);
|
||||||
|
|
||||||
|
onlyFloorlines.current = onlyFloorlines.current.map((floorline: any) =>
|
||||||
|
floorline.filter((line: any) => line[0][1] !== data.data && line[1][1] !== data.data)
|
||||||
|
).filter((floorline: any) => floorline.length > 0);
|
||||||
|
|
||||||
|
RemoveConnectedLines(data.data, floorPlanGroupLine, floorPlanGroupPoint, setDeletedLines, lines);
|
||||||
|
|
||||||
|
loadWalls(lines, setWalls);
|
||||||
|
setUpdateScene(true);
|
||||||
|
|
||||||
|
toast.success("Point Removed!");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('Line:response:delete:layer', async (data: any) => {
|
||||||
|
if (socket.id === data.socketId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (organization !== data.organization) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (data.message === "layer deleted") {
|
||||||
|
setActiveLayer(1)
|
||||||
|
const removedLayer = data.data;
|
||||||
|
const removedLines: Types.Lines = lines.current.filter((line: any) => line[0][2] === removedLayer);
|
||||||
|
|
||||||
|
////////// Remove Points and lines from the removed layer //////////
|
||||||
|
|
||||||
|
removedLines.forEach(async (line) => {
|
||||||
|
line.forEach(async (removedPoint) => {
|
||||||
|
const removableLines: THREE.Mesh[] = [];
|
||||||
|
const connectedpoints: string[] = [];
|
||||||
|
|
||||||
|
floorPlanGroupLine.current.children.forEach((line: any) => {
|
||||||
|
const linePoints = line.userData.linePoints as [number, string, number][];
|
||||||
|
const uuid1 = linePoints[0][1];
|
||||||
|
const uuid2 = linePoints[1][1];
|
||||||
|
|
||||||
|
if (uuid1 === removedPoint[1] || uuid2 === removedPoint[1]) {
|
||||||
|
connectedpoints.push(uuid1 === removedPoint[1] ? uuid2 : uuid1);
|
||||||
|
removableLines.push(line as THREE.Mesh);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (removableLines.length > 0) {
|
||||||
|
removableLines.forEach((line: any) => {
|
||||||
|
lines.current = lines.current.filter((item: any) => JSON.stringify(item) !== JSON.stringify(line.userData.linePoints));
|
||||||
|
line.material.dispose();
|
||||||
|
line.geometry.dispose();
|
||||||
|
floorPlanGroupLine.current.remove(line);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const point = floorPlanGroupPoint.current.getObjectByProperty('uuid', removedPoint[1]);
|
||||||
|
if (point) {
|
||||||
|
point.material.dispose();
|
||||||
|
point.geometry.dispose();
|
||||||
|
floorPlanGroupPoint.current.remove(point)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
////////// Update the remaining lines layer values in the userData and in lines.current //////////
|
||||||
|
|
||||||
|
let remaining = lines.current.filter((line: any) => line[0][2] !== removedLayer);
|
||||||
|
let updatedLines: Types.Lines = [];
|
||||||
|
remaining.forEach((line: any) => {
|
||||||
|
let newLines = JSON.parse(JSON.stringify(line));
|
||||||
|
if (newLines[0][2] > removedLayer) {
|
||||||
|
newLines[0][2] -= 1;
|
||||||
|
newLines[1][2] -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchingLine = floorPlanGroupLine.current.children.find((l: any) => l.userData.linePoints[0][1] === line[0][1] && l.userData.linePoints[1][1] === line[1][1]);
|
||||||
|
if (matchingLine) {
|
||||||
|
const updatedUserData = JSON.parse(JSON.stringify(matchingLine.userData));
|
||||||
|
updatedUserData.linePoints[0][2] = newLines[0][2];
|
||||||
|
updatedUserData.linePoints[1][2] = newLines[1][2];
|
||||||
|
matchingLine.userData = updatedUserData;
|
||||||
|
}
|
||||||
|
updatedLines.push(newLines);
|
||||||
|
});
|
||||||
|
|
||||||
|
lines.current = updatedLines;
|
||||||
|
localStorage.setItem("Lines", JSON.stringify(lines.current));
|
||||||
|
|
||||||
|
////////// Also remove OnlyFloorLines and update it in localstorage //////////
|
||||||
|
|
||||||
|
onlyFloorlines.current = onlyFloorlines.current.filter((floor: any) => {
|
||||||
|
return floor[0][0][2] !== removedLayer;
|
||||||
|
});
|
||||||
|
const meshToRemove = floorGroup.current?.children.find((mesh: any) =>
|
||||||
|
mesh.name === `Only_Floor_Line_${removedLayer}`
|
||||||
|
);
|
||||||
|
if (meshToRemove) {
|
||||||
|
meshToRemove.geometry.dispose();
|
||||||
|
meshToRemove.material.dispose();
|
||||||
|
floorGroup.current?.remove(meshToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
const zonesData = await getZonesApi(organization);
|
||||||
|
const highestLayer = Math.max(
|
||||||
|
1,
|
||||||
|
lines.current.reduce((maxLayer: number, segment: any) => Math.max(maxLayer, segment.layer || 0), 0),
|
||||||
|
zonesData.data.reduce((maxLayer: number, zone: any) => Math.max(maxLayer, zone.layer || 0), 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
setLayers(highestLayer);
|
||||||
|
|
||||||
|
loadWalls(lines, setWalls);
|
||||||
|
setUpdateScene(true);
|
||||||
|
|
||||||
|
toast.success("Layer Removed!");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [socket])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!socket) return
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
socket.on('wallItemsDeleteResponse', (data: any) => {
|
||||||
|
if (socket.id === data.socketId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (organization !== data.organization) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (data.message === "wallitem deleted") {
|
||||||
|
const deletedUUID = data.data.modeluuid;
|
||||||
|
let WallItemsRef = wallItems;
|
||||||
|
const Items = WallItemsRef.filter((item: any) => item.model?.uuid !== deletedUUID);
|
||||||
|
|
||||||
|
setWallItems([]);
|
||||||
|
setTimeout(async () => {
|
||||||
|
WallItemsRef = Items;
|
||||||
|
setWallItems(WallItemsRef);
|
||||||
|
const WallItemsForStorage = WallItemsRef.map((item: any) => {
|
||||||
|
const { model, ...rest } = item;
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
modeluuid: model?.uuid,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
localStorage.setItem("WallItems", JSON.stringify(WallItemsForStorage));
|
||||||
|
toast.success("Model Removed!");
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('wallItemsUpdateResponse', (data: any) => {
|
||||||
|
if (socket.id === data.socketId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (organization !== data.organization) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (data.message === "wallIitem created") {
|
||||||
|
const loader = new GLTFLoader();
|
||||||
|
loader.load(AssetConfigurations[data.data.modelname].modelUrl, async (gltf) => {
|
||||||
|
const model = gltf.scene;
|
||||||
|
model.uuid = data.data.modeluuid;
|
||||||
|
model.children[0].children.forEach((child) => {
|
||||||
|
if (child.name !== "CSG_REF") {
|
||||||
|
child.castShadow = true;
|
||||||
|
child.receiveShadow = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const newWallItem = {
|
||||||
|
type: data.data.type,
|
||||||
|
model: model,
|
||||||
|
modelname: data.data.modelname,
|
||||||
|
scale: data.data.scale,
|
||||||
|
csgscale: data.data.csgscale,
|
||||||
|
csgposition: data.data.csgposition,
|
||||||
|
position: data.data.position,
|
||||||
|
quaternion: data.data.quaternion
|
||||||
|
};
|
||||||
|
|
||||||
|
setWallItems((prevItems: any) => {
|
||||||
|
const updatedItems = [...prevItems, newWallItem];
|
||||||
|
|
||||||
|
const WallItemsForStorage = updatedItems.map(item => {
|
||||||
|
const { model, ...rest } = item;
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
modeluuid: model?.uuid,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
localStorage.setItem("WallItems", JSON.stringify(WallItemsForStorage));
|
||||||
|
toast.success("Model Added!");
|
||||||
|
|
||||||
|
return updatedItems;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (data.message === "wallIitem updated") {
|
||||||
|
const updatedUUID = data.data.modeluuid;
|
||||||
|
|
||||||
|
setWallItems((prevItems: any) => {
|
||||||
|
const updatedItems = prevItems.map((item: any) => {
|
||||||
|
if (item.model.uuid === updatedUUID) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
position: data.data.position,
|
||||||
|
quaternion: data.data.quaternion,
|
||||||
|
scale: data.data.scale,
|
||||||
|
csgscale: data.data.csgscale,
|
||||||
|
csgposition: data.data.csgposition,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
const WallItemsForStorage = updatedItems.map((item: any) => {
|
||||||
|
const { model, ...rest } = item;
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
modeluuid: model?.uuid,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
localStorage.setItem("WallItems", JSON.stringify(WallItemsForStorage));
|
||||||
|
toast.success("Model Updated!");
|
||||||
|
|
||||||
|
return updatedItems;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.off('wallItemsDeleteResponse');
|
||||||
|
socket.off('wallItemsUpdateResponse');
|
||||||
|
};
|
||||||
|
}, [wallItems])
|
||||||
|
|
||||||
|
function getPointColor(lineType: string | undefined): string {
|
||||||
|
switch (lineType) {
|
||||||
|
case CONSTANTS.lineConfig.wallName: return CONSTANTS.pointConfig.wallOuterColor;
|
||||||
|
case CONSTANTS.lineConfig.floorName: return CONSTANTS.pointConfig.floorOuterColor;
|
||||||
|
case CONSTANTS.lineConfig.aisleName: return CONSTANTS.pointConfig.aisleOuterColor;
|
||||||
|
default: return CONSTANTS.pointConfig.defaultOuterColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLineColor(lineType: string | undefined): string {
|
||||||
|
switch (lineType) {
|
||||||
|
case CONSTANTS.lineConfig.wallName: return CONSTANTS.lineConfig.wallColor;
|
||||||
|
case CONSTANTS.lineConfig.floorName: return CONSTANTS.lineConfig.floorColor;
|
||||||
|
case CONSTANTS.lineConfig.aisleName: return CONSTANTS.lineConfig.aisleColor;
|
||||||
|
default: return CONSTANTS.lineConfig.defaultColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!socket) return
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
socket.on('Line:response:create', async (data: any) => {
|
||||||
|
if (socket.id === data.socketId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (organization !== data.organization) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (data.message === "line create") {
|
||||||
|
const line: Types.Line = objectLineToArray(data.data);
|
||||||
|
const type = line[0][3];
|
||||||
|
const pointColour = getPointColor(type);
|
||||||
|
const lineColour = getLineColor(type);
|
||||||
|
setNewLines([line])
|
||||||
|
|
||||||
|
line.forEach((line) => {
|
||||||
|
const existingPoint = floorPlanGroupPoint.current?.getObjectByProperty('uuid', line[1]);
|
||||||
|
if (existingPoint) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const geometry = new THREE.BoxGeometry(...CONSTANTS.pointConfig.boxScale);
|
||||||
|
const material = new THREE.ShaderMaterial({
|
||||||
|
uniforms: {
|
||||||
|
uColor: { value: new THREE.Color(pointColour) }, // Blue color for the border
|
||||||
|
uInnerColor: { value: new THREE.Color(CONSTANTS.pointConfig.defaultInnerColor) }, // White color for the inner square
|
||||||
|
},
|
||||||
|
vertexShader: `
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
fragmentShader: `
|
||||||
|
varying vec2 vUv;
|
||||||
|
uniform vec3 uColor;
|
||||||
|
uniform vec3 uInnerColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Define the size of the white square as a proportion of the face
|
||||||
|
float borderThickness = 0.2; // Adjust this value for border thickness
|
||||||
|
if (vUv.x > borderThickness && vUv.x < 1.0 - borderThickness &&
|
||||||
|
vUv.y > borderThickness && vUv.y < 1.0 - borderThickness) {
|
||||||
|
gl_FragColor = vec4(uInnerColor, 1.0); // White inner square
|
||||||
|
} else {
|
||||||
|
gl_FragColor = vec4(uColor, 1.0); // Blue border
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
const point = new THREE.Mesh(geometry, material);
|
||||||
|
point.name = "point";
|
||||||
|
point.uuid = line[1];
|
||||||
|
point.userData = { type: type, color: pointColour };
|
||||||
|
point.position.set(line[0].x, line[0].y, line[0].z);
|
||||||
|
currentLayerPoint.current.push(point);
|
||||||
|
|
||||||
|
floorPlanGroupPoint.current?.add(point);
|
||||||
|
})
|
||||||
|
if (dragPointControls.current) {
|
||||||
|
dragPointControls.current!.objects = currentLayerPoint.current;
|
||||||
|
}
|
||||||
|
addLineToScene(
|
||||||
|
new THREE.Vector3(line[0][0].x, line[0][0].y, line[0][0].z),
|
||||||
|
new THREE.Vector3(line[1][0].x, line[1][0].y, line[1][0].z),
|
||||||
|
lineColour,
|
||||||
|
line,
|
||||||
|
floorPlanGroupLine
|
||||||
|
)
|
||||||
|
lines.current.push(line);
|
||||||
|
|
||||||
|
const zonesData = await getZonesApi(organization);
|
||||||
|
const highestLayer = Math.max(
|
||||||
|
1,
|
||||||
|
lines.current.reduce((maxLayer: number, segment: any) => Math.max(maxLayer, segment.layer || 0), 0),
|
||||||
|
zonesData.data.reduce((maxLayer: number, zone: any) => Math.max(maxLayer, zone.layer || 0), 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
setLayers(highestLayer);
|
||||||
|
|
||||||
|
Layer2DVisibility(activeLayer, floorPlanGroup, floorPlanGroupLine, floorPlanGroupPoint, currentLayerPoint, dragPointControls)
|
||||||
|
|
||||||
|
loadWalls(lines, setWalls);
|
||||||
|
setUpdateScene(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.off('Line:response:create');
|
||||||
|
};
|
||||||
|
}, [socket, activeLayer])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!socket) return
|
||||||
|
const email = localStorage.getItem('email');
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
socket.on('zone:response:updates', (data: any) => {
|
||||||
|
if (socket.id === data.socketId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (organization !== data.organization) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.message === "zone created") {
|
||||||
|
const pointsArray: [number, number, number][] = data.data.points;
|
||||||
|
const vector3Array = pointsArray.map(([x, y, z]) => new THREE.Vector3(x, y, z));
|
||||||
|
const newZones = [...zones, data.data];
|
||||||
|
setZones(newZones);
|
||||||
|
const updatedZonePoints = [...zonePoints, ...vector3Array];
|
||||||
|
setZonePoints(updatedZonePoints);
|
||||||
|
|
||||||
|
const highestLayer = Math.max(
|
||||||
|
1,
|
||||||
|
lines.current.reduce((maxLayer: number, segment: any) => Math.max(maxLayer, segment.layer || 0), 0),
|
||||||
|
newZones.reduce((maxLayer: number, zone: any) => Math.max(maxLayer, zone.layer || 0), 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
setLayers(highestLayer);
|
||||||
|
setUpdateScene(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.message === "zone updated") {
|
||||||
|
const updatedZones = zones.map((zone: any) =>
|
||||||
|
zone.zoneId === data.data.zoneId ? data.data : zone
|
||||||
|
);
|
||||||
|
setZones(updatedZones);
|
||||||
|
setUpdateScene(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('zone:response:delete', (data: any) => {
|
||||||
|
if (socket.id === data.socketId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (organization !== data.organization) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (data.message === "zone deleted") {
|
||||||
|
const updatedZones = zones.filter((zone: any) => zone.zoneId !== data.data.zoneId);
|
||||||
|
setZones(updatedZones);
|
||||||
|
|
||||||
|
const zoneIndex = zones.findIndex((zone: any) => zone.zoneId === data.data.zoneId);
|
||||||
|
if (zoneIndex !== -1) {
|
||||||
|
const updatedzonePoints = zonePoints.filter((_: any, index: any) => index < zoneIndex * 4 || index >= zoneIndex * 4 + 4);
|
||||||
|
setZonePoints(updatedzonePoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
const highestLayer = Math.max(
|
||||||
|
1,
|
||||||
|
lines.current.reduce((maxLayer: number, segment: any) => Math.max(maxLayer, segment.layer || 0), 0),
|
||||||
|
updatedZones.reduce((maxLayer: number, zone: any) => Math.max(maxLayer, zone.layer || 0), 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
setLayers(highestLayer);
|
||||||
|
setUpdateScene(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.off('zone:response:updates');
|
||||||
|
socket.off('zone:response:updates');
|
||||||
|
};
|
||||||
|
}, [socket, zones, zonePoints])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<></>
|
||||||
|
)
|
||||||
|
}
|
||||||
202
app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts
Normal file
202
app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||||
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||||
|
import gsap from 'gsap';
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import * as Types from "../../../types/world/worldTypes";
|
||||||
|
import { getFloorItems } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
|
||||||
|
import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils';
|
||||||
|
import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi';
|
||||||
|
|
||||||
|
async function loadInitialFloorItems(
|
||||||
|
itemsGroup: Types.RefGroup,
|
||||||
|
setFloorItems: Types.setFloorItemSetState
|
||||||
|
): Promise<void> {
|
||||||
|
if (!itemsGroup.current) return;
|
||||||
|
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||||
|
const email = localStorage.getItem('email');
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
const items = await getFloorItems(organization);
|
||||||
|
localStorage.setItem("FloorItems", JSON.stringify(items));
|
||||||
|
await initializeDB();
|
||||||
|
|
||||||
|
if (items) {
|
||||||
|
const storedFloorItems: Types.FloorItems = 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);
|
||||||
|
|
||||||
|
let modelsLoaded = 0;
|
||||||
|
const modelsToLoad = storedFloorItems.length;
|
||||||
|
|
||||||
|
const camData = await getCamera(organization, localStorage.getItem('userId')!);
|
||||||
|
let storedPosition;
|
||||||
|
if (camData && camData.position) {
|
||||||
|
storedPosition = camData?.position;
|
||||||
|
} else {
|
||||||
|
storedPosition = new THREE.Vector3(0, 40, 30);
|
||||||
|
}
|
||||||
|
if (!storedPosition) return;
|
||||||
|
const cameraPosition = new THREE.Vector3(storedPosition.x, storedPosition.y, storedPosition.z);
|
||||||
|
|
||||||
|
storedFloorItems.sort((a, b) => {
|
||||||
|
const aPosition = new THREE.Vector3(a.position[0], a.position[1], a.position[2]);
|
||||||
|
const bPosition = new THREE.Vector3(b.position[0], b.position[1], b.position[2]);
|
||||||
|
return cameraPosition.distanceTo(aPosition) - cameraPosition.distanceTo(bPosition);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const item of storedFloorItems) {
|
||||||
|
if (!item.modelfileID) return;
|
||||||
|
const itemPosition = new THREE.Vector3(item.position[0], item.position[1], item.position[2]);
|
||||||
|
let storedPosition;
|
||||||
|
if (localStorage.getItem("cameraPosition")) {
|
||||||
|
storedPosition = JSON.parse(localStorage.getItem("cameraPosition")!);
|
||||||
|
} else {
|
||||||
|
storedPosition = new THREE.Vector3(0, 40, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cameraPosition = new THREE.Vector3(storedPosition.x, storedPosition.y, storedPosition.z);
|
||||||
|
|
||||||
|
if (cameraPosition.distanceTo(itemPosition) < 50) {
|
||||||
|
await new Promise<void>(async (resolve) => {
|
||||||
|
|
||||||
|
// Check Three.js Cache
|
||||||
|
const cachedModel = THREE.Cache.get(item.modelfileID!);
|
||||||
|
if (cachedModel) {
|
||||||
|
// console.log(`[Cache] Fetching ${item.modelname}`);
|
||||||
|
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems);
|
||||||
|
modelsLoaded++;
|
||||||
|
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check IndexedDB
|
||||||
|
const indexedDBModel = await retrieveGLTF(item.modelfileID!);
|
||||||
|
if (indexedDBModel) {
|
||||||
|
// console.log(`[IndexedDB] Fetching ${item.modelname}`);
|
||||||
|
const blobUrl = URL.createObjectURL(indexedDBModel);
|
||||||
|
loader.load(
|
||||||
|
blobUrl,
|
||||||
|
(gltf) => {
|
||||||
|
URL.revokeObjectURL(blobUrl);
|
||||||
|
THREE.Cache.remove(blobUrl);
|
||||||
|
THREE.Cache.add(item.modelfileID!, gltf);
|
||||||
|
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems);
|
||||||
|
modelsLoaded++;
|
||||||
|
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
(error) => {
|
||||||
|
toast.error(`[IndexedDB] Error loading ${item.modelname}:`);
|
||||||
|
URL.revokeObjectURL(blobUrl);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch from Backend
|
||||||
|
// console.log(`[Backend] Fetching ${item.modelname}`);
|
||||||
|
const modelUrl = `${url_Backend_dwinzo}/api/v1/AssetFile/${item.modelfileID!}`;
|
||||||
|
loader.load(
|
||||||
|
modelUrl,
|
||||||
|
async (gltf) => {
|
||||||
|
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
||||||
|
await storeGLTF(item.modelfileID!, modelBlob);
|
||||||
|
THREE.Cache.add(item.modelfileID!, gltf);
|
||||||
|
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems);
|
||||||
|
modelsLoaded++;
|
||||||
|
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
(error) => {
|
||||||
|
toast.error(`[Backend] Error loading ${item.modelname}:`);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// console.log(`Item ${item.modelname} is not near`);
|
||||||
|
setFloorItems((prevItems) => [
|
||||||
|
...(prevItems || []),
|
||||||
|
{
|
||||||
|
modeluuid: item.modeluuid,
|
||||||
|
modelname: item.modelname,
|
||||||
|
position: item.position,
|
||||||
|
rotation: item.rotation,
|
||||||
|
modelfileID: item.modelfileID,
|
||||||
|
isLocked: item.isLocked,
|
||||||
|
isVisible: item.isVisible,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
modelsLoaded++;
|
||||||
|
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, () => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispose loader after all models
|
||||||
|
dracoLoader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function processLoadedModel(
|
||||||
|
gltf: any,
|
||||||
|
item: Types.FloorItemType,
|
||||||
|
itemsGroup: Types.RefGroup,
|
||||||
|
setFloorItems: Types.setFloorItemSetState
|
||||||
|
) {
|
||||||
|
const model = gltf;
|
||||||
|
model.uuid = item.modeluuid;
|
||||||
|
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
|
||||||
|
model.userData = { name: item.modelname, modelId: item.modelfileID };
|
||||||
|
model.position.set(...item.position);
|
||||||
|
model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z);
|
||||||
|
|
||||||
|
model.traverse((child: any) => {
|
||||||
|
if (child.isMesh) {
|
||||||
|
// Clone the material to ensure changes are independent
|
||||||
|
// child.material = child.material.clone();
|
||||||
|
|
||||||
|
child.castShadow = true;
|
||||||
|
child.receiveShadow = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
itemsGroup?.current?.add(model);
|
||||||
|
setFloorItems((prevItems) => [
|
||||||
|
...(prevItems || []),
|
||||||
|
{
|
||||||
|
modeluuid: item.modeluuid,
|
||||||
|
modelname: item.modelname,
|
||||||
|
position: item.position,
|
||||||
|
rotation: item.rotation,
|
||||||
|
modelfileID: item.modelfileID,
|
||||||
|
isLocked: item.isLocked,
|
||||||
|
isVisible: item.isVisible,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
gsap.to(model.position, { y: item.position[1], duration: 1.5, ease: 'power2.out' });
|
||||||
|
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: 'power2.out' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkLoadingCompletion(
|
||||||
|
modelsLoaded: number,
|
||||||
|
modelsToLoad: number,
|
||||||
|
dracoLoader: DRACOLoader,
|
||||||
|
resolve: () => void
|
||||||
|
) {
|
||||||
|
if (modelsLoaded === modelsToLoad) {
|
||||||
|
toast.success("Models Loaded!");
|
||||||
|
dracoLoader.dispose();
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default loadInitialFloorItems;
|
||||||
30
app/src/modules/scene/IntialLoad/loadInitialLine.ts
Normal file
30
app/src/modules/scene/IntialLoad/loadInitialLine.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import addLineToScene from '../../builder/geomentries/lines/addLineToScene';
|
||||||
|
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||||
|
import * as Types from "../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
function loadInitialLine(
|
||||||
|
floorPlanGroupLine: Types.RefGroup,
|
||||||
|
lines: Types.RefLines
|
||||||
|
): void {
|
||||||
|
|
||||||
|
if (!floorPlanGroupLine.current) return
|
||||||
|
|
||||||
|
////////// Load the Lines initially if there are any //////////
|
||||||
|
|
||||||
|
floorPlanGroupLine.current.children = [];
|
||||||
|
lines.current.forEach((line) => {
|
||||||
|
let colour;
|
||||||
|
if (line[0][3] && line[1][3] === CONSTANTS.lineConfig.wallName) {
|
||||||
|
colour = CONSTANTS.lineConfig.wallColor;
|
||||||
|
} else if (line[0][3] && line[1][3] === CONSTANTS.lineConfig.floorName) {
|
||||||
|
colour = CONSTANTS.lineConfig.floorColor;
|
||||||
|
} else if (line[0][3] && line[1][3] === CONSTANTS.lineConfig.aisleName) {
|
||||||
|
colour = CONSTANTS.lineConfig.aisleColor;
|
||||||
|
}
|
||||||
|
if (colour) {
|
||||||
|
addLineToScene(line[0][0], line[1][0], colour, line, floorPlanGroupLine);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default loadInitialLine;
|
||||||
87
app/src/modules/scene/IntialLoad/loadInitialPoint.ts
Normal file
87
app/src/modules/scene/IntialLoad/loadInitialPoint.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||||
|
import * as Types from "../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
////////// Load the Boxes initially if there are any //////////
|
||||||
|
|
||||||
|
function loadInitialPoint(
|
||||||
|
lines: Types.RefLines,
|
||||||
|
floorPlanGroupPoint: Types.RefGroup,
|
||||||
|
currentLayerPoint: Types.RefMeshArray,
|
||||||
|
dragPointControls: Types.RefDragControl
|
||||||
|
): void {
|
||||||
|
|
||||||
|
if (!floorPlanGroupPoint.current) return
|
||||||
|
|
||||||
|
floorPlanGroupPoint.current.children = [];
|
||||||
|
currentLayerPoint.current = [];
|
||||||
|
lines.current.forEach((line) => {
|
||||||
|
const colour = getPointColor(line[0][3]);
|
||||||
|
line.forEach((pointData) => {
|
||||||
|
const [point, id] = pointData;
|
||||||
|
|
||||||
|
/////////// Check if a box with this id already exists //////////
|
||||||
|
|
||||||
|
const existingBox = floorPlanGroupPoint.current?.getObjectByProperty('uuid', id);
|
||||||
|
if (existingBox) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const geometry = new THREE.BoxGeometry(...CONSTANTS.pointConfig.boxScale);
|
||||||
|
const material = new THREE.ShaderMaterial({
|
||||||
|
uniforms: {
|
||||||
|
uColor: { value: new THREE.Color(colour) }, // Blue color for the border
|
||||||
|
uInnerColor: { value: new THREE.Color(CONSTANTS.pointConfig.defaultInnerColor) }, // White color for the inner square
|
||||||
|
},
|
||||||
|
vertexShader: `
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
fragmentShader: `
|
||||||
|
varying vec2 vUv;
|
||||||
|
uniform vec3 uColor;
|
||||||
|
uniform vec3 uInnerColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Define the size of the white square as a proportion of the face
|
||||||
|
float borderThickness = 0.2; // Adjust this value for border thickness
|
||||||
|
if (vUv.x > borderThickness && vUv.x < 1.0 - borderThickness &&
|
||||||
|
vUv.y > borderThickness && vUv.y < 1.0 - borderThickness) {
|
||||||
|
gl_FragColor = vec4(uInnerColor, 1.0); // White inner square
|
||||||
|
} else {
|
||||||
|
gl_FragColor = vec4(uColor, 1.0); // Blue border
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
const box = new THREE.Mesh(geometry, material);
|
||||||
|
box.name = "point";
|
||||||
|
box.uuid = id;
|
||||||
|
box.userData = { type: line[0][3], color: colour };
|
||||||
|
box.position.set(point.x, point.y, point.z);
|
||||||
|
currentLayerPoint.current.push(box);
|
||||||
|
|
||||||
|
floorPlanGroupPoint.current?.add(box);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function getPointColor(lineType: string | undefined): string {
|
||||||
|
switch (lineType) {
|
||||||
|
case CONSTANTS.lineConfig.wallName: return CONSTANTS.pointConfig.wallOuterColor;
|
||||||
|
case CONSTANTS.lineConfig.floorName: return CONSTANTS.pointConfig.floorOuterColor;
|
||||||
|
case CONSTANTS.lineConfig.aisleName: return CONSTANTS.pointConfig.aisleOuterColor;
|
||||||
|
default: return CONSTANTS.pointConfig.defaultOuterColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dragPointControls.current) {
|
||||||
|
dragPointControls.current!.objects = currentLayerPoint.current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default loadInitialPoint;
|
||||||
54
app/src/modules/scene/IntialLoad/loadInitialWallItems.ts
Normal file
54
app/src/modules/scene/IntialLoad/loadInitialWallItems.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||||
|
|
||||||
|
import * as Types from "../../../types/world/worldTypes";
|
||||||
|
import { getWallItems } from '../../../services/factoryBuilder/assest/wallAsset/getWallItemsApi';
|
||||||
|
|
||||||
|
////////// Load the Wall Items's intially of there is any //////////
|
||||||
|
|
||||||
|
async function loadInitialWallItems(
|
||||||
|
setWallItems: Types.setWallItemSetState,
|
||||||
|
AssetConfigurations: Types.AssetConfigurations
|
||||||
|
): Promise<void> {
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
const items = await getWallItems(organization);
|
||||||
|
|
||||||
|
localStorage.setItem("WallItems", JSON.stringify(items));
|
||||||
|
if (items.length > 0) {
|
||||||
|
const storedWallItems: Types.wallItems = items;
|
||||||
|
|
||||||
|
const loadedWallItems = await Promise.all(storedWallItems.map(async (item) => {
|
||||||
|
const loader = new GLTFLoader();
|
||||||
|
return new Promise<Types.WallItem>((resolve) => {
|
||||||
|
loader.load(AssetConfigurations[item.modelname!].modelUrl, (gltf) => {
|
||||||
|
const model = gltf.scene;
|
||||||
|
model.uuid = item.modeluuid!;
|
||||||
|
|
||||||
|
model.children[0].children.forEach((child: any) => {
|
||||||
|
if (child.name !== "CSG_REF") {
|
||||||
|
child.castShadow = true;
|
||||||
|
child.receiveShadow = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
type: item.type,
|
||||||
|
model: model,
|
||||||
|
modelname: item.modelname,
|
||||||
|
scale: item.scale,
|
||||||
|
csgscale: item.csgscale,
|
||||||
|
csgposition: item.csgposition,
|
||||||
|
position: item.position,
|
||||||
|
quaternion: item.quaternion,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
setWallItems(loadedWallItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default loadInitialWallItems;
|
||||||
89
app/src/modules/scene/camera/camMode.tsx
Normal file
89
app/src/modules/scene/camera/camMode.tsx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { useFrame, useThree } from '@react-three/fiber';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||||
|
import { useCamMode, useToggleView } from '../../../store/store';
|
||||||
|
import { useKeyboardControls } from '@react-three/drei';
|
||||||
|
import switchToThirdPerson from './switchToThirdPerson';
|
||||||
|
import switchToFirstPerson from './switchToFirstPerson';
|
||||||
|
|
||||||
|
const CamMode: React.FC = () => {
|
||||||
|
const { camMode, setCamMode } = useCamMode();
|
||||||
|
const [, get] = useKeyboardControls()
|
||||||
|
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||||
|
const state: any = useThree();
|
||||||
|
const { toggleView } = useToggleView();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handlePointerLockChange = async () => {
|
||||||
|
if (document.pointerLockElement && !toggleView) {
|
||||||
|
// console.log('Pointer is locked');
|
||||||
|
} else {
|
||||||
|
// console.log('Pointer is unlocked');
|
||||||
|
if (camMode === "FirstPerson" && !toggleView) {
|
||||||
|
setCamMode("ThirdPerson");
|
||||||
|
await switchToThirdPerson(state.controls, state.camera);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('pointerlockchange', handlePointerLockChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('pointerlockchange', handlePointerLockChange);
|
||||||
|
};
|
||||||
|
}, [camMode, toggleView, setCamMode, state.controls, state.camera]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyPress = async (event: any) => {
|
||||||
|
if (!state.controls) return;
|
||||||
|
|
||||||
|
if (event.key === "/" && !isTransitioning && !toggleView) {
|
||||||
|
setIsTransitioning(true);
|
||||||
|
state.controls.mouseButtons.left = CONSTANTS.controlsTransition.leftMouse;
|
||||||
|
state.controls.mouseButtons.right = CONSTANTS.controlsTransition.rightMouse;
|
||||||
|
state.controls.mouseButtons.wheel = CONSTANTS.controlsTransition.wheelMouse;
|
||||||
|
state.controls.mouseButtons.middle = CONSTANTS.controlsTransition.middleMouse;
|
||||||
|
|
||||||
|
if (camMode === 'ThirdPerson') {
|
||||||
|
setCamMode("FirstPerson");
|
||||||
|
await switchToFirstPerson(state.controls, state.camera);
|
||||||
|
} else if (camMode === "FirstPerson") {
|
||||||
|
setCamMode("ThirdPerson");
|
||||||
|
await switchToThirdPerson(state.controls, state.camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsTransitioning(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("keydown", handleKeyPress);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("keydown", handleKeyPress);
|
||||||
|
};
|
||||||
|
}, [camMode, isTransitioning, toggleView, state.controls, state.camera, setCamMode]);
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
const { forward, backward, left, right } = get();
|
||||||
|
if (!state.controls) return
|
||||||
|
if (!state.controls || camMode === "ThirdPerson" || !document.pointerLockElement) return;
|
||||||
|
|
||||||
|
if (forward) {
|
||||||
|
state.controls.forward(CONSTANTS.firstPersonControls.forwardSpeed, true)
|
||||||
|
}
|
||||||
|
if (backward) {
|
||||||
|
state.controls.forward(CONSTANTS.firstPersonControls.backwardSpeed, true)
|
||||||
|
}
|
||||||
|
if (left) {
|
||||||
|
state.controls.truck(CONSTANTS.firstPersonControls.leftSpeed, 0, true)
|
||||||
|
}
|
||||||
|
if (right) {
|
||||||
|
state.controls.truck(CONSTANTS.firstPersonControls.rightSpeed, 0, true)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<></>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CamMode;
|
||||||
25
app/src/modules/scene/camera/switchToFirstPerson.ts
Normal file
25
app/src/modules/scene/camera/switchToFirstPerson.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||||
|
|
||||||
|
export default async function switchToFirstPerson(
|
||||||
|
controls: any,
|
||||||
|
camera: any
|
||||||
|
) {
|
||||||
|
if (!controls) return;
|
||||||
|
|
||||||
|
const cameraDirection = new THREE.Vector3();
|
||||||
|
camera.getWorldDirection(cameraDirection);
|
||||||
|
cameraDirection.normalize();
|
||||||
|
|
||||||
|
await controls.setPosition(camera.position.x, 2, camera.position.z, true);
|
||||||
|
controls.setTarget(camera.position.x, 2, camera.position.z, true);
|
||||||
|
controls.mouseButtons.left = CONSTANTS.firstPersonControls.leftMouse;
|
||||||
|
controls.lockPointer();
|
||||||
|
|
||||||
|
controls.azimuthRotateSpeed = CONSTANTS.firstPersonControls.azimuthRotateSpeed;
|
||||||
|
controls.polarRotateSpeed = CONSTANTS.firstPersonControls.polarRotateSpeed;
|
||||||
|
controls.truckSpeed = CONSTANTS.firstPersonControls.truckSpeed;
|
||||||
|
controls.minDistance = CONSTANTS.firstPersonControls.minDistance;
|
||||||
|
controls.maxDistance = CONSTANTS.firstPersonControls.maxDistance;
|
||||||
|
controls.maxPolarAngle = CONSTANTS.firstPersonControls.maxPolarAngle;
|
||||||
|
}
|
||||||
29
app/src/modules/scene/camera/switchToThirdPerson.ts
Normal file
29
app/src/modules/scene/camera/switchToThirdPerson.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||||
|
|
||||||
|
export default async function switchToThirdPerson(
|
||||||
|
controls: any,
|
||||||
|
camera: any
|
||||||
|
) {
|
||||||
|
if (!controls) return;
|
||||||
|
controls.mouseButtons.left = CONSTANTS.thirdPersonControls.leftMouse;
|
||||||
|
controls.mouseButtons.right = CONSTANTS.thirdPersonControls.rightMouse;
|
||||||
|
controls.mouseButtons.middle = CONSTANTS.thirdPersonControls.middleMouse;
|
||||||
|
controls.mouseButtons.wheel = CONSTANTS.thirdPersonControls.wheelMouse;
|
||||||
|
controls.unlockPointer();
|
||||||
|
|
||||||
|
const cameraDirection = new THREE.Vector3();
|
||||||
|
camera.getWorldDirection(cameraDirection);
|
||||||
|
const targetOffset = cameraDirection.multiplyScalar(CONSTANTS.thirdPersonControls.targetOffset);
|
||||||
|
const targetPosition = new THREE.Vector3(camera.position.x, camera.position.y, camera.position.z).add(targetOffset);
|
||||||
|
|
||||||
|
controls.setPosition(camera.position.x, CONSTANTS.thirdPersonControls.cameraHeight, camera.position.z, true);
|
||||||
|
controls.setTarget(targetPosition.x, 0, targetPosition.z, true);
|
||||||
|
|
||||||
|
controls.azimuthRotateSpeed = CONSTANTS.thirdPersonControls.azimuthRotateSpeed;
|
||||||
|
controls.polarRotateSpeed = CONSTANTS.thirdPersonControls.polarRotateSpeed;
|
||||||
|
controls.truckSpeed = CONSTANTS.thirdPersonControls.truckSpeed;
|
||||||
|
controls.minDistance = CONSTANTS.threeDimension.minDistance;
|
||||||
|
controls.maxDistance = CONSTANTS.thirdPersonControls.maxDistance;
|
||||||
|
controls.maxPolarAngle = CONSTANTS.thirdPersonControls.maxPolarAngle;
|
||||||
|
}
|
||||||
70
app/src/modules/scene/camera/switchView.tsx
Normal file
70
app/src/modules/scene/camera/switchView.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import * as THREE from "three";
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
import { useToggleView } from "../../../store/store";
|
||||||
|
import { useThree } from "@react-three/fiber";
|
||||||
|
import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi";
|
||||||
|
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 && state.controls) {
|
||||||
|
state.controls.mouseButtons.left = CONSTANTS.twoDimension.leftMouse;
|
||||||
|
state.controls.mouseButtons.right = CONSTANTS.twoDimension.rightMouse;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const email = localStorage.getItem('email');
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
getCamera(organization, localStorage.getItem('userId')!).then((data) => {
|
||||||
|
if (data && data.position && data.target) {
|
||||||
|
state.controls?.setPosition(data.position.x, data.position.y, data.position.z);
|
||||||
|
state.controls?.setTarget(data.target.x, data.target.y, data.target.z);
|
||||||
|
localStorage.setItem("cameraPosition", JSON.stringify(data.position));
|
||||||
|
localStorage.setItem("controlTarget", JSON.stringify(data.target));
|
||||||
|
} else {
|
||||||
|
state.controls?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
|
||||||
|
state.controls?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
|
||||||
|
localStorage.setItem("cameraPosition", JSON.stringify(new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition)));
|
||||||
|
localStorage.setItem("controlTarget", JSON.stringify(new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to retrieve camera position or target:", error);
|
||||||
|
state.controls?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
|
||||||
|
state.controls?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.controls) {
|
||||||
|
state.controls.mouseButtons.left = CONSTANTS.threeDimension.leftMouse;
|
||||||
|
state.controls.mouseButtons.right = CONSTANTS.threeDimension.rightMouse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [toggleView, state.controls]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<></>
|
||||||
|
);
|
||||||
|
}
|
||||||
26
app/src/modules/scene/camera/updateCameraPosition.ts
Normal file
26
app/src/modules/scene/camera/updateCameraPosition.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Socket } from "socket.io-client";
|
||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
export default function updateCamPosition(
|
||||||
|
controls: any,
|
||||||
|
socket: Socket,
|
||||||
|
position: THREE.Vector3,
|
||||||
|
rotation: THREE.Euler
|
||||||
|
) {
|
||||||
|
if (!controls.current) return;
|
||||||
|
const target = controls.current.getTarget(new THREE.Vector3());
|
||||||
|
const email = localStorage.getItem("email");
|
||||||
|
const organization = email!.split("@")[1].split(".")[0];
|
||||||
|
|
||||||
|
const camData = {
|
||||||
|
organization: organization,
|
||||||
|
userId: localStorage.getItem("userId")!,
|
||||||
|
position: position,
|
||||||
|
target: new THREE.Vector3(target.x, 0, target.z),
|
||||||
|
rotation: new THREE.Vector3(rotation.x, rotation.y, rotation.z),
|
||||||
|
socketId: socket.id,
|
||||||
|
};
|
||||||
|
socket.emit("v1:Camera:set", camData);
|
||||||
|
localStorage.setItem("cameraPosition", JSON.stringify(position));
|
||||||
|
localStorage.setItem("controlTarget", JSON.stringify(new THREE.Vector3(target.x, 0, target.z)));
|
||||||
|
}
|
||||||
136
app/src/modules/scene/controls/controls.tsx
Normal file
136
app/src/modules/scene/controls/controls.tsx
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import { CameraControls } from "@react-three/drei";
|
||||||
|
import { useRef, useEffect } from "react";
|
||||||
|
import { useThree } from "@react-three/fiber";
|
||||||
|
import * as THREE from "three";
|
||||||
|
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||||
|
|
||||||
|
import { useSocketStore, useToggleView, useResetCamera } from "../../../store/store";
|
||||||
|
import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi";
|
||||||
|
import updateCamPosition from "../camera/updateCameraPosition";
|
||||||
|
import CamMode from "../camera/camMode";
|
||||||
|
import SwitchView from "../camera/switchView";
|
||||||
|
|
||||||
|
export default function Controls() {
|
||||||
|
const controlsRef = useRef<CameraControls>(null);
|
||||||
|
|
||||||
|
const { toggleView } = useToggleView();
|
||||||
|
const { resetCamera, setResetCamera } = useResetCamera();
|
||||||
|
const { socket } = useSocketStore();
|
||||||
|
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];
|
||||||
|
getCamera(organization, localStorage.getItem("userId")!).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));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (resetCamera) {
|
||||||
|
controlsRef.current?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
|
||||||
|
controlsRef.current?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
|
||||||
|
controlsRef.current?.rotateAzimuthTo(CONSTANTS.threeDimension.defaultAzimuth);
|
||||||
|
|
||||||
|
localStorage.setItem("cameraPosition", JSON.stringify(new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition)));
|
||||||
|
localStorage.setItem("controlTarget", JSON.stringify(new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget)));
|
||||||
|
|
||||||
|
const email = localStorage.getItem('email')
|
||||||
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
|
||||||
|
const camData = {
|
||||||
|
organization: organization,
|
||||||
|
userId: localStorage.getItem('userId')!,
|
||||||
|
position: new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition),
|
||||||
|
target: new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget),
|
||||||
|
rotation: new THREE.Vector3(...CONSTANTS.threeDimension.defaultRotation),
|
||||||
|
socketId: socket.id
|
||||||
|
};
|
||||||
|
socket.emit('v1:Camera:set', camData)
|
||||||
|
|
||||||
|
setResetCamera(false);
|
||||||
|
}
|
||||||
|
}, [resetCamera]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
controlsRef.current?.setBoundary(new THREE.Box3(new THREE.Vector3(...CONSTANTS.threeDimension.boundaryBottom), new THREE.Vector3(...CONSTANTS.threeDimension.boundaryTop)));
|
||||||
|
// state.scene.add(new THREE.Box3Helper(new THREE.Box3(new THREE.Vector3(...CONSTANTS.threeDimension.boundaryBottom), new THREE.Vector3(...CONSTANTS.threeDimension.boundaryTop)), 0xffff00));
|
||||||
|
let hasInteracted = false;
|
||||||
|
let intervalId: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
const handleRest = () => {
|
||||||
|
if (hasInteracted && controlsRef.current && state.camera.position && !toggleView) {
|
||||||
|
const position = state.camera.position;
|
||||||
|
if (position.x === 0 && position.y === 0 && position.z === 0) return;
|
||||||
|
updateCamPosition(controlsRef, socket, position, state.camera.rotation);
|
||||||
|
stopInterval();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startInterval = () => {
|
||||||
|
hasInteracted = true;
|
||||||
|
if (!intervalId) {
|
||||||
|
intervalId = setInterval(() => {
|
||||||
|
if (controlsRef.current && !toggleView) {
|
||||||
|
handleRest();
|
||||||
|
}
|
||||||
|
}, CONSTANTS.camPositionUpdateInterval);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopInterval = () => {
|
||||||
|
if (intervalId) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
intervalId = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const controls = controlsRef.current;
|
||||||
|
if (controls) {
|
||||||
|
controls.addEventListener("sleep", handleRest);
|
||||||
|
controls.addEventListener("control", startInterval);
|
||||||
|
controls.addEventListener("controlend", stopInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (controls) {
|
||||||
|
controls.removeEventListener("sleep", handleRest);
|
||||||
|
controls.removeEventListener("control", startInterval);
|
||||||
|
controls.removeEventListener("controlend", stopInterval);
|
||||||
|
}
|
||||||
|
stopInterval();
|
||||||
|
};
|
||||||
|
}, [toggleView, state, socket]);
|
||||||
|
|
||||||
|
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}
|
||||||
|
>
|
||||||
|
<SwitchView />
|
||||||
|
<CamMode />
|
||||||
|
</CameraControls>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { Line } from "@react-three/drei";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import * as THREE from "three";
|
||||||
|
import { useSelectedAssets } from "../../../../store/store";
|
||||||
|
|
||||||
|
const BoundingBox = ({ boundingBoxRef }: any) => {
|
||||||
|
const { selectedAssets } = useSelectedAssets();
|
||||||
|
|
||||||
|
const { points, boxProps } = useMemo(() => {
|
||||||
|
if (selectedAssets.length === 0) return { points: [], boxProps: {} };
|
||||||
|
|
||||||
|
const box = new THREE.Box3();
|
||||||
|
selectedAssets.forEach((obj: any) => box.expandByObject(obj.clone()));
|
||||||
|
|
||||||
|
const size = new THREE.Vector3();
|
||||||
|
box.getSize(size);
|
||||||
|
const center = new THREE.Vector3();
|
||||||
|
box.getCenter(center);
|
||||||
|
|
||||||
|
const halfSize = size.clone().multiplyScalar(0.5);
|
||||||
|
const min = center.clone().sub(halfSize);
|
||||||
|
const max = center.clone().add(halfSize);
|
||||||
|
|
||||||
|
const points: any = [
|
||||||
|
[min.x, min.y, min.z], [max.x, min.y, min.z],
|
||||||
|
[max.x, min.y, min.z], [max.x, max.y, min.z],
|
||||||
|
[max.x, max.y, min.z], [min.x, max.y, min.z],
|
||||||
|
[min.x, max.y, min.z], [min.x, min.y, min.z],
|
||||||
|
|
||||||
|
[min.x, min.y, max.z], [max.x, min.y, max.z],
|
||||||
|
[max.x, min.y, max.z], [max.x, max.y, max.z],
|
||||||
|
[max.x, max.y, max.z], [min.x, max.y, max.z],
|
||||||
|
[min.x, max.y, max.z], [min.x, min.y, max.z],
|
||||||
|
|
||||||
|
[min.x, min.y, min.z], [min.x, min.y, max.z],
|
||||||
|
[max.x, min.y, min.z], [max.x, min.y, max.z],
|
||||||
|
[max.x, max.y, min.z], [max.x, max.y, max.z],
|
||||||
|
[min.x, max.y, min.z], [min.x, max.y, max.z],
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
points,
|
||||||
|
boxProps: { position: center.toArray(), args: size.toArray() }
|
||||||
|
};
|
||||||
|
}, [selectedAssets]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{points.length > 0 && (
|
||||||
|
<>
|
||||||
|
<Line points={points} color="yellow" lineWidth={2.5} segments />
|
||||||
|
<mesh ref={boundingBoxRef} visible={false} position={boxProps.position}>
|
||||||
|
<boxGeometry args={boxProps.args} />
|
||||||
|
<meshBasicMaterial />
|
||||||
|
</mesh>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BoundingBox;
|
||||||
209
app/src/modules/scene/controls/selection/copyPasteControls.tsx
Normal file
209
app/src/modules/scene/controls/selection/copyPasteControls.tsx
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
import * as THREE from "three";
|
||||||
|
import { useEffect, useMemo } from "react";
|
||||||
|
import { useFrame, useThree } from "@react-three/fiber";
|
||||||
|
import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, selectionGroup, setDuplicatedObjects, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) => {
|
||||||
|
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
|
||||||
|
const { toggleView } = useToggleView();
|
||||||
|
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
||||||
|
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
||||||
|
const { floorItems, setFloorItems } = useFloorItems();
|
||||||
|
const { socket } = useSocketStore()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!camera || !scene || toggleView) return;
|
||||||
|
const canvasElement = gl.domElement;
|
||||||
|
canvasElement.tabIndex = 0;
|
||||||
|
|
||||||
|
let isMoving = false;
|
||||||
|
|
||||||
|
const onPointerDown = () => {
|
||||||
|
isMoving = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPointerMove = () => {
|
||||||
|
isMoving = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPointerUp = (event: PointerEvent) => {
|
||||||
|
if (!isMoving && pastedObjects.length > 0 && event.button === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
|
||||||
|
event.preventDefault();
|
||||||
|
addPastedObjects();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.ctrlKey && event.key.toLowerCase() === "c" && movedObjects.length === 0 && rotatedObjects.length === 0) {
|
||||||
|
copySelection();
|
||||||
|
}
|
||||||
|
if (event.ctrlKey && event.key.toLowerCase() === "v" && copiedObjects.length > 0 && pastedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
|
||||||
|
pasteCopiedObjects();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!toggleView) {
|
||||||
|
canvasElement.addEventListener("pointerdown", onPointerDown);
|
||||||
|
canvasElement.addEventListener("pointermove", onPointerMove);
|
||||||
|
canvasElement.addEventListener("pointerup", onPointerUp);
|
||||||
|
canvasElement.addEventListener("keydown", onKeyDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
canvasElement.removeEventListener("pointerdown", onPointerDown);
|
||||||
|
canvasElement.removeEventListener("pointermove", onPointerMove);
|
||||||
|
canvasElement.removeEventListener("pointerup", onPointerUp);
|
||||||
|
canvasElement.removeEventListener("keydown", onKeyDown);
|
||||||
|
};
|
||||||
|
|
||||||
|
}, [camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, movedObjects, socket, floorItems, rotatedObjects]);
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
if (pastedObjects.length > 0) {
|
||||||
|
const intersectionPoint = new THREE.Vector3();
|
||||||
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||||
|
if (point) {
|
||||||
|
const position = new THREE.Vector3();
|
||||||
|
if (boundingBoxRef.current) {
|
||||||
|
boundingBoxRef.current?.getWorldPosition(position)
|
||||||
|
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
|
||||||
|
} else {
|
||||||
|
const box = new THREE.Box3();
|
||||||
|
pastedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
|
||||||
|
const center = new THREE.Vector3();
|
||||||
|
box.getCenter(center);
|
||||||
|
selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const copySelection = () => {
|
||||||
|
if (selectedAssets.length > 0) {
|
||||||
|
const newClones = selectedAssets.map((asset: any) => {
|
||||||
|
const clone = asset.clone();
|
||||||
|
clone.position.copy(asset.position);
|
||||||
|
return clone;
|
||||||
|
});
|
||||||
|
setCopiedObjects(newClones);
|
||||||
|
toast.info("Objects copied!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const pasteCopiedObjects = () => {
|
||||||
|
if (copiedObjects.length > 0 && pastedObjects.length === 0) {
|
||||||
|
const newClones = copiedObjects.map((obj: THREE.Object3D) => {
|
||||||
|
const clone = obj.clone();
|
||||||
|
clone.position.copy(obj.position);
|
||||||
|
return clone;
|
||||||
|
});
|
||||||
|
selectionGroup.current.add(...newClones);
|
||||||
|
setpastedObjects([...newClones]);
|
||||||
|
setSelectedAssets([...newClones]);
|
||||||
|
|
||||||
|
const intersectionPoint = new THREE.Vector3();
|
||||||
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||||
|
|
||||||
|
if (point) {
|
||||||
|
const position = new THREE.Vector3();
|
||||||
|
if (boundingBoxRef.current) {
|
||||||
|
boundingBoxRef.current?.getWorldPosition(position)
|
||||||
|
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
|
||||||
|
} else {
|
||||||
|
const box = new THREE.Box3();
|
||||||
|
newClones.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
|
||||||
|
const center = new THREE.Vector3();
|
||||||
|
box.getCenter(center);
|
||||||
|
selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addPastedObjects = () => {
|
||||||
|
if (pastedObjects.length === 0) return;
|
||||||
|
pastedObjects.forEach(async (obj: THREE.Object3D) => {
|
||||||
|
const worldPosition = new THREE.Vector3();
|
||||||
|
obj.getWorldPosition(worldPosition);
|
||||||
|
obj.position.copy(worldPosition);
|
||||||
|
|
||||||
|
if (itemsGroupRef.current) {
|
||||||
|
|
||||||
|
const newFloorItem: Types.FloorItemType = {
|
||||||
|
modeluuid: obj.uuid,
|
||||||
|
modelname: obj.userData.name,
|
||||||
|
modelfileID: obj.userData.modelId,
|
||||||
|
position: [worldPosition.x, worldPosition.y, worldPosition.z],
|
||||||
|
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
|
||||||
|
isLocked: false,
|
||||||
|
isVisible: true
|
||||||
|
};
|
||||||
|
|
||||||
|
setFloorItems((prevItems: Types.FloorItems) => {
|
||||||
|
const updatedItems = [...(prevItems || []), newFloorItem];
|
||||||
|
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
|
||||||
|
return updatedItems;
|
||||||
|
});
|
||||||
|
|
||||||
|
const email = localStorage.getItem("email");
|
||||||
|
const organization = email ? email.split("@")[1].split(".")[0] : "default";
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// await setFloorItemApi(
|
||||||
|
// organization,
|
||||||
|
// obj.uuid,
|
||||||
|
// obj.userData.name,
|
||||||
|
// [worldPosition.x, worldPosition.y, worldPosition.z],
|
||||||
|
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
|
||||||
|
// obj.userData.modelId,
|
||||||
|
// false,
|
||||||
|
// true,
|
||||||
|
// );
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
organization,
|
||||||
|
modeluuid: newFloorItem.modeluuid,
|
||||||
|
modelname: newFloorItem.modelname,
|
||||||
|
modelfileID: newFloorItem.modelfileID,
|
||||||
|
position: newFloorItem.position,
|
||||||
|
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
|
||||||
|
isLocked: false,
|
||||||
|
isVisible: true,
|
||||||
|
socketId: socket.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.emit("v1:FloorItems:set", data);
|
||||||
|
|
||||||
|
itemsGroupRef.current.add(obj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success("Object added!");
|
||||||
|
clearSelection();
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearSelection = () => {
|
||||||
|
selectionGroup.current.children = [];
|
||||||
|
selectionGroup.current.position.set(0, 0, 0);
|
||||||
|
selectionGroup.current.rotation.set(0, 0, 0);
|
||||||
|
setMovedObjects([]);
|
||||||
|
setpastedObjects([]);
|
||||||
|
setDuplicatedObjects([]);
|
||||||
|
setRotatedObjects([]);
|
||||||
|
setSelectedAssets([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<></>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CopyPasteControls;
|
||||||
190
app/src/modules/scene/controls/selection/duplicationControls.tsx
Normal file
190
app/src/modules/scene/controls/selection/duplicationControls.tsx
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
import * as THREE from "three";
|
||||||
|
import { useEffect, useMemo } from "react";
|
||||||
|
import { useFrame, useThree } from "@react-three/fiber";
|
||||||
|
import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
|
||||||
|
import * as Types from "../../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedObjects, setpastedObjects, selectionGroup, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) => {
|
||||||
|
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
|
||||||
|
const { toggleView } = useToggleView();
|
||||||
|
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
||||||
|
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
||||||
|
const { floorItems, setFloorItems } = useFloorItems();
|
||||||
|
const { socket } = useSocketStore();
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!camera || !scene || toggleView) return;
|
||||||
|
const canvasElement = gl.domElement;
|
||||||
|
canvasElement.tabIndex = 0;
|
||||||
|
|
||||||
|
let isMoving = false;
|
||||||
|
|
||||||
|
const onPointerDown = () => {
|
||||||
|
isMoving = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPointerMove = () => {
|
||||||
|
isMoving = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPointerUp = (event: PointerEvent) => {
|
||||||
|
if (!isMoving && duplicatedObjects.length > 0 && event.button === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
|
||||||
|
event.preventDefault();
|
||||||
|
addDuplicatedAssets();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key.toLowerCase() === "d") {
|
||||||
|
event.preventDefault();
|
||||||
|
if (event.ctrlKey && event.key.toLowerCase() === "d" && selectedAssets.length > 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
|
||||||
|
duplicateSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!toggleView) {
|
||||||
|
canvasElement.addEventListener("pointerdown", onPointerDown);
|
||||||
|
canvasElement.addEventListener("pointermove", onPointerMove);
|
||||||
|
canvasElement.addEventListener("pointerup", onPointerUp);
|
||||||
|
canvasElement.addEventListener("keydown", onKeyDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
canvasElement.removeEventListener("pointerdown", onPointerDown);
|
||||||
|
canvasElement.removeEventListener("pointermove", onPointerMove);
|
||||||
|
canvasElement.removeEventListener("pointerup", onPointerUp);
|
||||||
|
canvasElement.removeEventListener("keydown", onKeyDown);
|
||||||
|
};
|
||||||
|
|
||||||
|
}, [camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, floorItems, rotatedObjects]);
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
if (duplicatedObjects.length > 0) {
|
||||||
|
const intersectionPoint = new THREE.Vector3();
|
||||||
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||||
|
if (point) {
|
||||||
|
const position = new THREE.Vector3();
|
||||||
|
if (boundingBoxRef.current) {
|
||||||
|
boundingBoxRef.current?.getWorldPosition(position)
|
||||||
|
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
|
||||||
|
} else {
|
||||||
|
const box = new THREE.Box3();
|
||||||
|
duplicatedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
|
||||||
|
const center = new THREE.Vector3();
|
||||||
|
box.getCenter(center);
|
||||||
|
selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const duplicateSelection = () => {
|
||||||
|
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
|
||||||
|
const newClones = selectedAssets.map((asset: any) => {
|
||||||
|
const clone = asset.clone();
|
||||||
|
clone.position.copy(asset.position);
|
||||||
|
return clone;
|
||||||
|
});
|
||||||
|
|
||||||
|
selectionGroup.current.add(...newClones);
|
||||||
|
setDuplicatedObjects(newClones);
|
||||||
|
|
||||||
|
const intersectionPoint = new THREE.Vector3();
|
||||||
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||||
|
|
||||||
|
if (point) {
|
||||||
|
const position = new THREE.Vector3();
|
||||||
|
boundingBoxRef.current?.getWorldPosition(position)
|
||||||
|
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addDuplicatedAssets = () => {
|
||||||
|
if (duplicatedObjects.length === 0) return;
|
||||||
|
duplicatedObjects.forEach(async (obj: THREE.Object3D) => {
|
||||||
|
const worldPosition = new THREE.Vector3();
|
||||||
|
obj.getWorldPosition(worldPosition);
|
||||||
|
obj.position.copy(worldPosition);
|
||||||
|
|
||||||
|
if (itemsGroupRef.current) {
|
||||||
|
|
||||||
|
const newFloorItem: Types.FloorItemType = {
|
||||||
|
modeluuid: obj.uuid,
|
||||||
|
modelname: obj.userData.name,
|
||||||
|
modelfileID: obj.userData.modelId,
|
||||||
|
position: [worldPosition.x, worldPosition.y, worldPosition.z],
|
||||||
|
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
|
||||||
|
isLocked: false,
|
||||||
|
isVisible: true
|
||||||
|
};
|
||||||
|
|
||||||
|
setFloorItems((prevItems: Types.FloorItems) => {
|
||||||
|
const updatedItems = [...(prevItems || []), newFloorItem];
|
||||||
|
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
|
||||||
|
return updatedItems;
|
||||||
|
});
|
||||||
|
|
||||||
|
const email = localStorage.getItem("email");
|
||||||
|
const organization = email ? email.split("@")[1].split(".")[0] : "default";
|
||||||
|
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// await setFloorItemApi(
|
||||||
|
// organization,
|
||||||
|
// obj.uuid,
|
||||||
|
// obj.userData.name,
|
||||||
|
// [worldPosition.x, worldPosition.y, worldPosition.z],
|
||||||
|
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
|
||||||
|
// obj.userData.modelId,
|
||||||
|
// false,
|
||||||
|
// true,
|
||||||
|
// );
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
organization,
|
||||||
|
modeluuid: newFloorItem.modeluuid,
|
||||||
|
modelname: newFloorItem.modelname,
|
||||||
|
modelfileID: newFloorItem.modelfileID,
|
||||||
|
position: newFloorItem.position,
|
||||||
|
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
|
||||||
|
isLocked: false,
|
||||||
|
isVisible: true,
|
||||||
|
socketId: socket.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.emit("v1:FloorItems:set", data);
|
||||||
|
|
||||||
|
itemsGroupRef.current.add(obj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success("Object duplicated!");
|
||||||
|
clearSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearSelection = () => {
|
||||||
|
selectionGroup.current.children = [];
|
||||||
|
selectionGroup.current.position.set(0, 0, 0);
|
||||||
|
selectionGroup.current.rotation.set(0, 0, 0);
|
||||||
|
setMovedObjects([]);
|
||||||
|
setpastedObjects([]);
|
||||||
|
setDuplicatedObjects([]);
|
||||||
|
setRotatedObjects([]);
|
||||||
|
setSelectedAssets([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<></>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DuplicationControls;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user