updated ui
This commit is contained in:
885
app/package-lock.json
generated
885
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",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
@@ -18,6 +18,8 @@
|
||||
"chart.js": "^4.4.8",
|
||||
"glob": "^11.0.0",
|
||||
"gsap": "^3.12.5",
|
||||
"leva": "^0.10.0",
|
||||
"mqtt": "^5.10.4",
|
||||
"postprocessing": "^6.36.4",
|
||||
"prompt-sync": "^4.2.0",
|
||||
"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.
@@ -9,7 +9,6 @@ const chartTypes: ChartType[] = [
|
||||
"line",
|
||||
"pie",
|
||||
"doughnut",
|
||||
"radar",
|
||||
"polarArea",
|
||||
];
|
||||
|
||||
|
||||
@@ -44,12 +44,13 @@ const AssetProperties: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="asset-properties-container">
|
||||
{/* Name */}
|
||||
<div className="header">Selected Object</div>
|
||||
|
||||
<div className="split"></div>
|
||||
|
||||
<PositionInput onChange={() => {}} />
|
||||
<RotationInput onChange={() => {}} />
|
||||
<PositionInput onChange={() => { }} />
|
||||
<RotationInput onChange={() => { }} />
|
||||
|
||||
<div className="split"></div>
|
||||
|
||||
|
||||
@@ -87,8 +87,7 @@ const Simulations: React.FC = () => {
|
||||
{productsList.map((action, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`list-item ${
|
||||
selectedItem === action ? "active" : ""
|
||||
className={`list-item ${selectedItem === action ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
@@ -123,8 +122,8 @@ const Simulations: React.FC = () => {
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</div>
|
||||
{Value.map((val) => (
|
||||
<DropList val={val} />
|
||||
{Value.map((val, index) => (
|
||||
<DropList key={index} val={val} />
|
||||
))}
|
||||
</div>
|
||||
<div className="compare-simulations-container">
|
||||
|
||||
@@ -17,6 +17,7 @@ import { handleSaveTemplate } from "../../modules/visualization/handleSaveTempla
|
||||
import { usePlayButtonStore } from "../../store/usePlayButtonStore";
|
||||
import useTemplateStore from "../../store/useTemplateStore";
|
||||
import { useSelectedZoneStore } from "../../store/useZoneStore";
|
||||
import { useAddAction, useDeleteModels, useSelectedWallItem, useToggleView } from "../../store/store";
|
||||
|
||||
const Tools: React.FC = () => {
|
||||
const { templates } = useTemplateStore();
|
||||
@@ -32,12 +33,32 @@ const Tools: React.FC = () => {
|
||||
const { addTemplate } = useTemplateStore();
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
|
||||
// wall options
|
||||
const { setToggleView } = useToggleView();
|
||||
const { setDeleteModels } = useDeleteModels();
|
||||
const { setAddAction } = useAddAction();
|
||||
const { setSelectedWallItem } = useSelectedWallItem();
|
||||
|
||||
|
||||
// Reset activeTool whenever activeModule changes
|
||||
useEffect(() => {
|
||||
setActiveTool(activeSubTool);
|
||||
setActiveSubTool(activeSubTool);
|
||||
}, [activeModule]);
|
||||
|
||||
const toggleSwitch = () => {
|
||||
if (toggleThreeD) {
|
||||
setSelectedWallItem(null);
|
||||
setDeleteModels(false);
|
||||
setAddAction(null);
|
||||
setToggleView(true);
|
||||
}
|
||||
else {
|
||||
setToggleView(false);
|
||||
}
|
||||
setToggleThreeD(!toggleThreeD);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleOutsideClick = (event: MouseEvent) => {
|
||||
if (
|
||||
@@ -136,8 +157,7 @@ const Tools: React.FC = () => {
|
||||
<div className="split"></div>
|
||||
<div className="draw-tools">
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "draw-wall" ? "active" : ""
|
||||
className={`tool-button ${activeTool === "draw-wall" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("draw-wall");
|
||||
@@ -146,8 +166,7 @@ const Tools: React.FC = () => {
|
||||
<WallIcon isActive={activeTool === "draw-wall"} />
|
||||
</div>
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "draw-zone" ? "active" : ""
|
||||
className={`tool-button ${activeTool === "draw-zone" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("draw-zone");
|
||||
@@ -156,8 +175,7 @@ const Tools: React.FC = () => {
|
||||
<ZoneIcon isActive={activeTool === "draw-zone"} />
|
||||
</div>
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "draw-aisle" ? "active" : ""
|
||||
className={`tool-button ${activeTool === "draw-aisle" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("draw-aisle");
|
||||
@@ -166,8 +184,7 @@ const Tools: React.FC = () => {
|
||||
<AsileIcon isActive={activeTool === "draw-aisle"} />
|
||||
</div>
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "draw-floor" ? "active" : ""
|
||||
className={`tool-button ${activeTool === "draw-floor" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("draw-floor");
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
import { useWidgetStore } from "../../../store/useWidgetStore";
|
||||
import PieGraphComponent from "../charts/PieGraphComponent";
|
||||
import BarGraphComponent from "../charts/BarGraphComponent";
|
||||
import LineGraphComponent from "../charts/LineGraphComponent";
|
||||
import ProgressCard from "../realTimeVis/charts/ProgressCard";
|
||||
import PieGraphComponent from "../realTimeVis/charts/PieGraphComponent";
|
||||
import BarGraphComponent from "../realTimeVis/charts/BarGraphComponent";
|
||||
import LineGraphComponent from "../realTimeVis/charts/LineGraphComponent";
|
||||
import RadarGraphComponent from "../realTimeVis/charts/RadarGraphComponent";
|
||||
import DoughnutGraphComponent from "../realTimeVis/charts/DoughnutGraphComponent";
|
||||
import PolarAreaGraphComponent from "../realTimeVis/charts/PolarAreaGraphComponent";
|
||||
|
||||
export const DraggableWidget = ({ widget }: { widget: any }) => {
|
||||
export const DraggableWidget = ({
|
||||
widget,
|
||||
hiddenPanels, // Add this prop to track hidden panels
|
||||
}: {
|
||||
widget: any;
|
||||
hiddenPanels: string[]; // Array of hidden panel names
|
||||
}) => {
|
||||
console.log("widget: ", widget.type);
|
||||
const { selectedChartId, setSelectedChartId } = useWidgetStore();
|
||||
|
||||
const handlePointerDown = () => {
|
||||
@@ -12,6 +23,9 @@ export const DraggableWidget = ({ widget }: { widget: any }) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Determine if the widget's panel is hidden
|
||||
const isPanelHidden = hiddenPanels.includes(widget.panel);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -20,12 +34,15 @@ export const DraggableWidget = ({ widget }: { widget: any }) => {
|
||||
selectedChartId?.id === widget.id && "activeChart"
|
||||
}`}
|
||||
onPointerDown={handlePointerDown}
|
||||
style={{
|
||||
opacity: isPanelHidden ? 0 : 1, // Set opacity to 0 if the panel is hidden
|
||||
pointerEvents: isPanelHidden ? "none" : "auto", // Disable interaction when hidden
|
||||
}}
|
||||
>
|
||||
{widget.type === "progress" ? (
|
||||
// <ProgressCard title={widget.title} data={widget.data} />
|
||||
<></>
|
||||
) : (
|
||||
<>
|
||||
{/* Render charts based on widget type */}
|
||||
{widget.type === "progress" && (
|
||||
<ProgressCard title={widget.title} data={widget.data} />
|
||||
)}
|
||||
{widget.type === "line" && (
|
||||
<LineGraphComponent
|
||||
type={widget.type}
|
||||
@@ -58,6 +75,15 @@ export const DraggableWidget = ({ widget }: { widget: any }) => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* {widget.type === "radar" && (
|
||||
<RadarGraphComponent
|
||||
type={widget.type}
|
||||
title={widget.title}
|
||||
fontSize={widget.fontSize}
|
||||
fontWeight={widget.fontWeight}
|
||||
data={widget.data.measurements.map((item: any) => item.fields)}
|
||||
/>
|
||||
)} */}
|
||||
{widget.type === "pie" && (
|
||||
<PieGraphComponent
|
||||
type={widget.type}
|
||||
@@ -74,7 +100,37 @@ export const DraggableWidget = ({ widget }: { widget: any }) => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
{widget.type === "doughnut" && (
|
||||
<DoughnutGraphComponent
|
||||
type={widget.type}
|
||||
title={widget.title}
|
||||
fontSize={widget.fontSize}
|
||||
fontWeight={widget.fontWeight}
|
||||
data={{
|
||||
measurements: [
|
||||
{ name: "testDevice", fields: "powerConsumption" },
|
||||
{ name: "furnace", fields: "powerConsumption" },
|
||||
],
|
||||
interval: 1000,
|
||||
duration: "1h",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{widget.type === "polarArea" && (
|
||||
<PolarAreaGraphComponent
|
||||
type={widget.type}
|
||||
title={widget.title}
|
||||
fontSize={widget.fontSize}
|
||||
fontWeight={widget.fontWeight}
|
||||
data={{
|
||||
measurements: [
|
||||
{ name: "testDevice", fields: "powerConsumption" },
|
||||
{ name: "furnace", fields: "powerConsumption" },
|
||||
],
|
||||
interval: 1000,
|
||||
duration: "1h",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -30,12 +30,17 @@ interface PanelProps {
|
||||
widgets: Widget[];
|
||||
}>
|
||||
>;
|
||||
hiddenPanels: string[];
|
||||
}
|
||||
|
||||
const generateUniqueId = () =>
|
||||
`${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
const Panel: React.FC<PanelProps> = ({ selectedZone, setSelectedZone }) => {
|
||||
const Panel: React.FC<PanelProps> = ({
|
||||
selectedZone,
|
||||
setSelectedZone,
|
||||
hiddenPanels,
|
||||
}) => {
|
||||
const panelRefs = useRef<{ [side in Side]?: HTMLDivElement }>({});
|
||||
const [panelDimensions, setPanelDimensions] = useState<{
|
||||
[side in Side]?: { width: number; height: number };
|
||||
@@ -189,7 +194,11 @@ const Panel: React.FC<PanelProps> = ({ selectedZone, setSelectedZone }) => {
|
||||
{selectedZone.widgets
|
||||
.filter((w) => w.panel === side)
|
||||
.map((widget) => (
|
||||
<DraggableWidget widget={widget} key={widget.id} />
|
||||
<DraggableWidget
|
||||
hiddenPanels={hiddenPanels}
|
||||
widget={widget}
|
||||
key={widget.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -75,29 +75,30 @@ const RealTimeVisulization: React.FC = () => {
|
||||
className={`realTime-viz canvas ${!isPlaying ? "playActiveFalse" : ""}`}
|
||||
style={{
|
||||
height: isPlaying ? "100vh" : "",
|
||||
width: isPlaying ? "100%" : "",
|
||||
left: isPlaying ? "0%" : "",
|
||||
}}
|
||||
>
|
||||
<DisplayZone
|
||||
zonesData={zonesData}
|
||||
selectedZone={selectedZone}
|
||||
setSelectedZone={setSelectedZone}
|
||||
/>
|
||||
|
||||
{!isPlaying && (
|
||||
<AddButtons
|
||||
hiddenPanels={hiddenPanels}
|
||||
setHiddenPanels={setHiddenPanels}
|
||||
selectedZone={selectedZone}
|
||||
setSelectedZone={setSelectedZone}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Panel selectedZone={selectedZone} setSelectedZone={setSelectedZone} />
|
||||
{/* {!isPlaying && ( */}
|
||||
<AddButtons
|
||||
hiddenPanels={hiddenPanels}
|
||||
setHiddenPanels={setHiddenPanels}
|
||||
selectedZone={selectedZone}
|
||||
setSelectedZone={setSelectedZone}
|
||||
/>
|
||||
{/* )} */}
|
||||
<Panel
|
||||
selectedZone={selectedZone}
|
||||
hiddenPanels={hiddenPanels}
|
||||
setSelectedZone={setSelectedZone}
|
||||
/>{" "}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RealTimeVisulization;
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { StockIncreseIcon } from "../../../icons/RealTimeVisulationIcons";
|
||||
|
||||
const ProgressCard = ({
|
||||
title,
|
||||
data,
|
||||
@@ -16,7 +18,9 @@ const ProgressCard = ({
|
||||
</span>
|
||||
<div className="stock-description">{stock.description}</div>
|
||||
</span>
|
||||
<div className="icon">Icon</div>
|
||||
<div className="icon">
|
||||
<StockIncreseIcon />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { Radar } from "react-chartjs-2";
|
||||
import {
|
||||
ChartOptions,
|
||||
ChartData,
|
||||
RadialLinearScaleOptions,
|
||||
} from "chart.js";
|
||||
import { ChartOptions, ChartData, RadialLinearScaleOptions } from "chart.js";
|
||||
|
||||
interface ChartComponentProps {
|
||||
type: string;
|
||||
title: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: string;
|
||||
@@ -19,7 +16,7 @@ const RadarGraphComponent = ({
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight = "Regular",
|
||||
data,
|
||||
data, // Now guaranteed to be number[]
|
||||
}: ChartComponentProps) => {
|
||||
// Memoize Font Weight Mapping
|
||||
const chartFontWeightMap = useMemo(
|
||||
@@ -31,14 +28,15 @@ const RadarGraphComponent = ({
|
||||
[]
|
||||
);
|
||||
|
||||
const fontSizeValue = useMemo(() => (fontSize ? parseInt(fontSize) : 12), [
|
||||
fontSize,
|
||||
]);
|
||||
const fontSizeValue = useMemo(
|
||||
() => (fontSize ? parseInt(fontSize) : 12),
|
||||
[fontSize]
|
||||
);
|
||||
|
||||
const fontWeightValue = useMemo(() => chartFontWeightMap[fontWeight], [
|
||||
fontWeight,
|
||||
chartFontWeightMap,
|
||||
]);
|
||||
const fontWeightValue = useMemo(
|
||||
() => chartFontWeightMap[fontWeight],
|
||||
[fontWeight, chartFontWeightMap]
|
||||
);
|
||||
|
||||
const chartFontStyle = useMemo(
|
||||
() => ({
|
||||
|
||||
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;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user