diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx index 1c27ad1..2138f07 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx @@ -8,11 +8,9 @@ import { upsertProductOrEventApi } from "../../../../../../services/simulation/p import { useProductStore } from "../../../../../../store/simulation/useProductStore"; import { useSelectedAction, useSelectedEventData } from "../../../../../../store/simulation/useSimulationStore"; import * as THREE from 'three'; -import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; function StorageMechanics() { - const { storageUnitStore } = useSceneContext(); const [activeOption, setActiveOption] = useState<"default" | "store" | "spawn">("default"); const [selectedPointData, setSelectedPointData] = useState(); const { selectedEventData } = useSelectedEventData(); @@ -20,7 +18,6 @@ function StorageMechanics() { const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { setSelectedAction, clearSelectedAction } = useSelectedAction(); - const { setCurrentMaterials, clearCurrentMaterials, updateCurrentLoad, getStorageUnitById } = storageUnitStore(); const email = localStorage.getItem('email') const organization = (email!.split("@")[1]).split(".")[0]; @@ -92,22 +89,6 @@ function StorageMechanics() { ); updateSelectedPointData(); } - - if (option === "spawn") { - const storageUnit = getStorageUnitById(selectedEventData.data.modelUuid); - if (storageUnit) { - const materialType = selectedPointData.action.materialType || "Default material"; - const materials = Array.from({ length: selectedPointData.action.storageCapacity }, () => - createNewMaterial(materialType) - ).filter(Boolean) as { materialType: string; materialId: string }[]; - - setCurrentMaterials(selectedEventData.data.modelUuid, materials); - updateCurrentLoad(selectedEventData.data.modelUuid, selectedPointData.action.storageCapacity); - } - } else { - clearCurrentMaterials(selectedEventData.data.modelUuid); - updateCurrentLoad(selectedEventData.data.modelUuid, 0); - } }; const handleRenameAction = (newName: string) => { @@ -141,21 +122,6 @@ function StorageMechanics() { event ); updateSelectedPointData(); - - if (activeOption === "spawn") { - const storageUnit = getStorageUnitById(selectedEventData.data.modelUuid); - if (storageUnit) { - const materialType = selectedPointData.action.materialType || "Default material"; - const materials = Array.from({ length: selectedPointData.action.storageCapacity }, () => - createNewMaterial(materialType) - ).filter(Boolean) as { materialType: string; materialId: string }[]; - - setCurrentMaterials(selectedEventData.data.modelUuid, materials); - updateCurrentLoad(selectedEventData.data.modelUuid, selectedPointData.action.storageCapacity); - } - } else { - updateCurrentLoad(selectedEventData.data.modelUuid, 0); - } } }; @@ -183,17 +149,6 @@ function StorageMechanics() { event ); updateSelectedPointData(); - - if (activeOption === "spawn") { - const storageUnit = getStorageUnitById(selectedEventData.data.modelUuid); - if (storageUnit) { - const materials = Array.from({ length: storageUnit.currentMaterials.length }, () => - createNewMaterial(value) - ).filter(Boolean) as { materialType: string; materialId: string }[]; - - setCurrentMaterials(selectedEventData.data.modelUuid, materials); - } - } } }; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx index 786f38e..93c1bd8 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx @@ -4,316 +4,293 @@ import RenameInput from "../../../../../ui/inputs/RenameInput"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import Trigger from "../trigger/Trigger"; import { - useSelectedAction, - useSelectedEventData, - useSelectedEventSphere, + useSelectedAction, + useSelectedEventData, } from "../../../../../../store/simulation/useSimulationStore"; import { useProductStore } from "../../../../../../store/simulation/useProductStore"; import TravelAction from "../actions/TravelAction"; import ActionsList from "../components/ActionsList"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; -import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; function VehicleMechanics() { - const { vehicleStore } = useSceneContext(); - const [activeOption, setActiveOption] = useState<"default" | "travel">("default"); - const [selectedPointData, setSelectedPointData] = useState(); - const { selectedEventData } = useSelectedEventData(); - const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction } = useProductStore(); - const { selectedProductStore } = useProductContext(); - const { selectedProduct } = selectedProductStore(); - const { setSelectedAction, clearSelectedAction } = useSelectedAction(); - const { getVehicleById } = vehicleStore(); + const [activeOption, setActiveOption] = useState<"default" | "travel">("default"); + const [selectedPointData, setSelectedPointData] = useState(); + const { selectedEventData } = useSelectedEventData(); + const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction } = useProductStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { setSelectedAction, clearSelectedAction } = useSelectedAction(); - const email = localStorage.getItem("email"); - const organization = email!.split("@")[1].split(".")[0]; + const email = localStorage.getItem("email"); + const organization = email!.split("@")[1].split(".")[0]; - useEffect(() => { - if (selectedEventData && selectedEventData.data.type === "vehicle") { - const point = getPointByUuid( - selectedProduct.productId, - selectedEventData.data.modelUuid, - selectedEventData.selectedPoint - ) as VehiclePointSchema | undefined; + useEffect(() => { + if (selectedEventData && selectedEventData.data.type === "vehicle") { + const point = getPointByUuid( + selectedProduct.productId, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint + ) as VehiclePointSchema | undefined; - if (point) { - setSelectedPointData(point); - setActiveOption(point.action.actionType as "travel"); - setSelectedAction(point.action.actionUuid, point.action.actionName); - } - } else { - clearSelectedAction(); - } - }, [selectedProduct, selectedEventData]); + if (point) { + setSelectedPointData(point); + setActiveOption(point.action.actionType as "travel"); + setSelectedAction(point.action.actionUuid, point.action.actionName); + } + } else { + clearSelectedAction(); + } + }, [selectedProduct, selectedEventData]); - const updateBackend = ( - productName: string, - productId: string, - organization: string, - eventData: EventsSchema - ) => { - upsertProductOrEventApi({ - productName: productName, - productId: productId, - organization: organization, - eventDatas: eventData, - }); - }; + const updateBackend = ( + productName: string, + productId: string, + organization: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productId: productId, + organization: organization, + eventDatas: eventData, + }); + }; - const handleSpeedChange = (value: string) => { - if (!selectedEventData) return; - const event = updateEvent( - selectedProduct.productId, - selectedEventData.data.modelUuid, - { - speed: parseFloat(value), - } - ); + const handleSpeedChange = (value: string) => { + if (!selectedEventData) return; + const event = updateEvent( + selectedProduct.productId, + selectedEventData.data.modelUuid, + { + speed: parseFloat(value), + } + ); - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - }; + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + }; - const handleActionTypeChange = (option: string) => { - if (!selectedEventData || !selectedPointData) return; - const validOption = option as "travel"; - setActiveOption(validOption); + const handleActionTypeChange = (option: string) => { + if (!selectedEventData || !selectedPointData) return; + const validOption = option as "travel"; + setActiveOption(validOption); - const event = updateAction( - selectedProduct.productId, - selectedPointData.action.actionUuid, - { - actionType: validOption, - } - ); + const event = updateAction( + selectedProduct.productId, + selectedPointData.action.actionUuid, + { + actionType: validOption, + } + ); - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - }; + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + }; - const handleRenameAction = (newName: string) => { - if (!selectedPointData) return; - const event = updateAction( - selectedProduct.productId, - selectedPointData.action.actionUuid, - { actionName: newName } - ); + const handleRenameAction = (newName: string) => { + if (!selectedPointData) return; + const event = updateAction( + selectedProduct.productId, + selectedPointData.action.actionUuid, + { actionName: newName } + ); - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - }; + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + }; - const handleLoadCapacityChange = (value: string) => { - if (!selectedPointData) return; - const event = updateAction( - selectedProduct.productId, - selectedPointData.action.actionUuid, - { - loadCapacity: parseFloat(value), - } - ); + const handleLoadCapacityChange = (value: string) => { + if (!selectedPointData) return; + const event = updateAction( + selectedProduct.productId, + selectedPointData.action.actionUuid, + { + loadCapacity: parseFloat(value), + } + ); - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - }; + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + }; - const handleUnloadDurationChange = (value: string) => { - if (!selectedPointData) return; - const event = updateAction( - selectedProduct.productId, - selectedPointData.action.actionUuid, - { - unLoadDuration: parseFloat(value), - } - ); + const handleUnloadDurationChange = (value: string) => { + if (!selectedPointData) return; + const event = updateAction( + selectedProduct.productId, + selectedPointData.action.actionUuid, + { + unLoadDuration: parseFloat(value), + } + ); - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } - }; + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + }; - const handlePickPointChange = (value: string) => { - if (!selectedPointData) return; - }; + const handlePickPointChange = (value: string) => { + if (!selectedPointData) return; + }; - const handleUnloadPointChange = (value: string) => { - if (!selectedPointData) return; - }; + const handleUnloadPointChange = (value: string) => { + if (!selectedPointData) return; + }; - // Get current values from store + // Get current values from store - const currentSpeed = - ( - getEventByModelUuid( - selectedProduct.productId, - selectedEventData?.data.modelUuid || "" - ) as VehicleEventSchema | undefined - )?.speed?.toString() || "0.5"; + const currentSpeed = + ( + getEventByModelUuid( + selectedProduct.productId, + selectedEventData?.data.modelUuid || "" + ) as VehicleEventSchema | undefined + )?.speed?.toString() || "0.5"; - const currentActionName = selectedPointData - ? selectedPointData.action.actionName - : "Action Name"; + const currentActionName = selectedPointData + ? selectedPointData.action.actionName + : "Action Name"; - const currentLoadCapacity = selectedPointData - ? selectedPointData.action.loadCapacity.toString() - : "1"; + const currentLoadCapacity = selectedPointData + ? selectedPointData.action.loadCapacity.toString() + : "1"; - const currentUnloadDuration = selectedPointData - ? selectedPointData.action.unLoadDuration.toString() - : "1"; + const currentUnloadDuration = selectedPointData + ? selectedPointData.action.unLoadDuration.toString() + : "1"; - const { selectedEventSphere } = useSelectedEventSphere(); + function handleClearPoints() { - function handleClearPoints() { - const updatedVehicle = getVehicleById( - selectedEventSphere?.userData.modelUuid - ); + if (!selectedEventData || !selectedPointData?.action.actionUuid) return; - if ( - !selectedProduct?.productId || - !selectedEventSphere?.userData?.modelUuid || - !updatedVehicle?.point - ) - return; - - const event = updateEvent( - selectedProduct.productId, - selectedEventSphere.userData.modelUuid, - { - point: { - ...updatedVehicle.point, - action: { - ...updatedVehicle.point?.action, + const event = updateAction( + selectedProduct.productId, selectedPointData.action.actionUuid, { pickUpPoint: null, unLoadPoint: null, steeringAngle: 0, - }, - }, - } - ); + }) - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } } - } - const availableActions = { - defaultOption: "travel", - options: ["travel"], - }; + const availableActions = { + defaultOption: "travel", + options: ["travel"], + }; - return ( - <> - {selectedEventData && ( + return ( <> -
-
-
- { }} - onChange={handleSpeedChange} - /> -
-
-
-
- -
-
- -
-
- + {selectedEventData && ( + <> +
+
+
+ { }} + onChange={handleSpeedChange} + /> +
+
+
+
+ +
+
+ +
+
+ - {activeOption === "travel" && ( - - )} -
-
-
- -
-
+ {activeOption === "travel" && ( + + )} +
+
+
+ +
+
+ + )} - )} - - ); + ); } export default VehicleMechanics; diff --git a/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx b/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx index d4a4007..78c63c7 100644 --- a/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx +++ b/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx @@ -5,7 +5,7 @@ import { useActiveLayer, useSocketStore, useToggleView, useToolMode } from '../. import { useAisleStore } from '../../../../store/builder/useAisleStore'; import ReferenceAisle from './referenceAisle'; import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; -import ReferencePoint from '../../point/referencePoint'; +import ReferencePoint from '../../point/reference/referencePoint'; function AisleCreator() { const { scene, camera, raycaster, gl, pointer } = useThree(); @@ -249,8 +249,10 @@ function AisleCreator() { canvasElement.addEventListener("click", onMouseClick); canvasElement.addEventListener("contextmenu", onContext); } else { - setTempPoints([]); - setIsCreating(false); + if (tempPoints.length > 0 || isCreating) { + setTempPoints([]); + setIsCreating(false); + } canvasElement.removeEventListener("mousedown", onMouseDown); canvasElement.removeEventListener("mouseup", onMouseUp); canvasElement.removeEventListener("mousemove", onMouseMove); @@ -265,7 +267,7 @@ function AisleCreator() { canvasElement.removeEventListener("click", onMouseClick); canvasElement.removeEventListener("contextmenu", onContext); }; - }, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addAisle, aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, snappedPosition, snappedPoint]); + }, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addAisle, getAislePointById, aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, snappedPosition, snappedPoint]); return ( <> diff --git a/app/src/modules/builder/aisle/aisleCreator/referenceAisle.tsx b/app/src/modules/builder/aisle/aisleCreator/referenceAisle.tsx index 149233b..cbb7c7e 100644 --- a/app/src/modules/builder/aisle/aisleCreator/referenceAisle.tsx +++ b/app/src/modules/builder/aisle/aisleCreator/referenceAisle.tsx @@ -24,10 +24,7 @@ function ReferenceAisle({ tempPoints }: Readonly) { const [tempAisle, setTempAisle] = useState(null); const [currentPosition, setCurrentPosition] = useState<[number, number, number]>(tempPoints[0]?.position); - const directionalSnap = useDirectionalSnapping( - currentPosition, - tempPoints[0]?.position || null - ); + const directionalSnap = useDirectionalSnapping(currentPosition, tempPoints[0]?.position || null); const { checkSnapForAisle } = usePointSnapping({ uuid: 'temp-aisle', pointType: 'Aisle', position: directionalSnap.position || [0, 0, 0] }); useFrame(() => { @@ -39,7 +36,6 @@ function ReferenceAisle({ tempPoints }: Readonly) { setCurrentPosition([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]); if (intersectionPoint) { - const snapped = checkSnapForAisle([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]); if (snapped.isSnapped && snapped.snappedPoint) { @@ -216,14 +212,11 @@ function ReferenceAisle({ tempPoints }: Readonly) { <> {toggleView && (); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements. @@ -279,6 +280,8 @@ export default function Builder() { plane={plane} /> + + diff --git a/app/src/modules/builder/groups/floorPlanGroup.tsx b/app/src/modules/builder/groups/floorPlanGroup.tsx index 74f0783..6859f74 100644 --- a/app/src/modules/builder/groups/floorPlanGroup.tsx +++ b/app/src/modules/builder/groups/floorPlanGroup.tsx @@ -157,7 +157,7 @@ const FloorPlanGroup = ({ floorPlanGroup, floorPlanGroupLine, floorPlanGroupPoin } 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); + // 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") { diff --git a/app/src/modules/builder/line/line.tsx b/app/src/modules/builder/line/line.tsx new file mode 100644 index 0000000..584f1e2 --- /dev/null +++ b/app/src/modules/builder/line/line.tsx @@ -0,0 +1,56 @@ +import * as THREE from 'three'; +import { useMemo } from "react"; +import * as Constants from '../../../types/world/worldConstants'; +import { Tube } from '@react-three/drei'; + +interface LineProps { + points: [Point, Point]; +} + +function Line({ points }: Readonly) { + const path = useMemo(() => { + const [start, end] = points.map(p => new THREE.Vector3(...p.position)); + return new THREE.LineCurve3(start, end); + }, [points]); + + const colors = getColor(points[0]); + + function getColor(point: Point) { + if (point.pointType === 'Aisle') { + return { + defaultLineColor: Constants.lineConfig.aisleColor, + defaultDeleteColor: Constants.lineConfig.deleteColor, + } + } else if (point.pointType === 'Floor') { + return { + defaultLineColor: Constants.lineConfig.floorColor, + defaultDeleteColor: Constants.lineConfig.deleteColor, + } + } else if (point.pointType === 'Wall') { + return { + defaultLineColor: Constants.lineConfig.wallColor, + defaultDeleteColor: Constants.lineConfig.deleteColor, + } + } else if (point.pointType === 'Zone') { + return { + defaultLineColor: Constants.lineConfig.zoneColor, + defaultDeleteColor: Constants.lineConfig.deleteColor, + } + } else { + return { + defaultLineColor: Constants.lineConfig.defaultColor, + defaultDeleteColor: Constants.lineConfig.deleteColor, + } + } + } + + return ( + + + + ); +} + +export default Line; \ No newline at end of file diff --git a/app/src/modules/builder/line/reference/referenceLine.tsx b/app/src/modules/builder/line/reference/referenceLine.tsx new file mode 100644 index 0000000..12ba6a4 --- /dev/null +++ b/app/src/modules/builder/line/reference/referenceLine.tsx @@ -0,0 +1,25 @@ +import * as THREE from 'three'; +import { useMemo } from "react"; +import * as Constants from '../../../../types/world/worldConstants'; +import { Tube } from '@react-three/drei'; + +interface ReferenceLineProps { + points: [Point, Point]; +} + +function ReferenceLine({ points }: Readonly) { + const path = useMemo(() => { + const [start, end] = points.map(p => new THREE.Vector3(...p.position)); + return new THREE.LineCurve3(start, end); + }, [points]); + + return ( + + + + ); +} + +export default ReferenceLine; \ No newline at end of file diff --git a/app/src/modules/builder/point/helpers/usePointSnapping.tsx b/app/src/modules/builder/point/helpers/usePointSnapping.tsx index 8d56085..74328cb 100644 --- a/app/src/modules/builder/point/helpers/usePointSnapping.tsx +++ b/app/src/modules/builder/point/helpers/usePointSnapping.tsx @@ -1,6 +1,7 @@ import { useCallback } from 'react'; import { useAisleStore } from '../../../../store/builder/useAisleStore'; import * as THREE from 'three'; +import { useWallStore } from '../../../../store/builder/useWallStore'; const SNAP_THRESHOLD = 0.5; // Distance threshold for snapping in meters @@ -8,8 +9,9 @@ const CAN_SNAP = true; // Whether snapping is enabled or not export const usePointSnapping = (currentPoint: { uuid: string, pointType: string, position: [number, number, number] } | null) => { const { aisles } = useAisleStore(); + const { walls } = useWallStore(); - const getAllOtherPoints = useCallback(() => { + const getAllOtherAislePoints = useCallback(() => { if (!currentPoint) return []; return aisles.flatMap(aisle => @@ -17,10 +19,17 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string ); }, [aisles, currentPoint]); + const getAllOtherWallPoints = useCallback(() => { + if (!currentPoint) return []; + return walls.flatMap(wall => + wall.points.filter(point => point.pointUuid !== currentPoint.uuid) + ); + }, [walls, currentPoint]); + const checkSnapForAisle = useCallback((position: [number, number, number]) => { if (!currentPoint || !CAN_SNAP) return { position: position, isSnapped: false, snappedPoint: null }; - const otherPoints = getAllOtherPoints(); + const otherPoints = getAllOtherAislePoints(); const currentVec = new THREE.Vector3(...position); for (const point of otherPoints) { @@ -33,9 +42,25 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string } return { position: position, isSnapped: false, snappedPoint: null }; - }, [currentPoint, getAllOtherPoints]); + }, [currentPoint, getAllOtherAislePoints]); + + const checkSnapForWall = useCallback((position: [number, number, number]) => { + if (!currentPoint || !CAN_SNAP) return { position: position, isSnapped: false, snappedPoint: null }; + + const otherPoints = getAllOtherWallPoints(); + const currentVec = new THREE.Vector3(...position); + for (const point of otherPoints) { + const pointVec = new THREE.Vector3(...point.position); + const distance = currentVec.distanceTo(pointVec); + if (distance <= SNAP_THRESHOLD && currentPoint.pointType === 'Wall') { + return { position: point.position, isSnapped: true, snappedPoint: point }; + } + } + return { position: position, isSnapped: false, snappedPoint: null }; + }, [currentPoint, getAllOtherWallPoints]); return { checkSnapForAisle, + checkSnapForWall, }; }; \ No newline at end of file diff --git a/app/src/modules/builder/point/point.tsx b/app/src/modules/builder/point/point.tsx index 5c000f7..0a79e7e 100644 --- a/app/src/modules/builder/point/point.tsx +++ b/app/src/modules/builder/point/point.tsx @@ -22,35 +22,67 @@ function Point({ point }: { readonly point: Point }) { const { deletePointOrLine } = useDeletePointOrLine(); const boxScale: [number, number, number] = Constants.pointConfig.boxScale; - const defaultInnerColor = Constants.pointConfig.defaultInnerColor; - const defaultOuterColor = Constants.pointConfig.aisleOuterColor; - const defaultDeleteColor = Constants.pointConfig.deleteColor; + const colors = getColor(point); + + function getColor(point: Point) { + if (point.pointType === 'Aisle') { + return { + defaultInnerColor: Constants.pointConfig.defaultInnerColor, + defaultOuterColor: Constants.pointConfig.aisleOuterColor, + defaultDeleteColor: Constants.pointConfig.deleteColor, + } + } else if (point.pointType === 'Floor') { + return { + defaultInnerColor: Constants.pointConfig.defaultInnerColor, + defaultOuterColor: Constants.pointConfig.floorOuterColor, + defaultDeleteColor: Constants.pointConfig.deleteColor, + } + } else if (point.pointType === 'Wall') { + return { + defaultInnerColor: Constants.pointConfig.defaultInnerColor, + defaultOuterColor: Constants.pointConfig.wallOuterColor, + defaultDeleteColor: Constants.pointConfig.deleteColor, + } + } else if (point.pointType === 'Zone') { + return { + defaultInnerColor: Constants.pointConfig.defaultInnerColor, + defaultOuterColor: Constants.pointConfig.zoneOuterColor, + defaultDeleteColor: Constants.pointConfig.deleteColor, + } + } else { + return { + defaultInnerColor: Constants.pointConfig.defaultInnerColor, + defaultOuterColor: Constants.pointConfig.defaultOuterColor, + defaultDeleteColor: Constants.pointConfig.deleteColor, + } + } + } useEffect(() => { if (materialRef.current && (toolMode === 'move' || deletePointOrLine)) { let innerColor; let outerColor; if (isHovered) { - innerColor = deletePointOrLine ? defaultDeleteColor : defaultOuterColor; - outerColor = deletePointOrLine ? defaultDeleteColor : defaultOuterColor; + innerColor = deletePointOrLine ? colors.defaultDeleteColor : colors.defaultOuterColor; + outerColor = deletePointOrLine ? colors.defaultDeleteColor : colors.defaultOuterColor; } else { - innerColor = defaultInnerColor; - outerColor = defaultOuterColor; + innerColor = colors.defaultInnerColor; + outerColor = colors.defaultOuterColor; } materialRef.current.uniforms.uInnerColor.value.set(innerColor); materialRef.current.uniforms.uOuterColor.value.set(outerColor); materialRef.current.uniformsNeedUpdate = true; } else if (materialRef.current && toolMode !== 'move') { - materialRef.current.uniforms.uInnerColor.value.set(defaultInnerColor); - materialRef.current.uniforms.uOuterColor.value.set(defaultOuterColor); + materialRef.current.uniforms.uInnerColor.value.set(colors.defaultInnerColor); + materialRef.current.uniforms.uOuterColor.value.set(colors.defaultOuterColor); materialRef.current.uniformsNeedUpdate = true; } - }, [isHovered, defaultInnerColor, defaultOuterColor, toolMode, deletePointOrLine, defaultDeleteColor]); + }, [isHovered, colors.defaultInnerColor, colors.defaultOuterColor, colors.defaultDeleteColor, toolMode, deletePointOrLine]); const uniforms = useMemo(() => ({ - uOuterColor: { value: new THREE.Color(defaultOuterColor) }, - uInnerColor: { value: new THREE.Color(defaultInnerColor) }, - }), [defaultInnerColor, defaultInnerColor]); + uOuterColor: { value: new THREE.Color(colors.defaultOuterColor) }, + uInnerColor: { value: new THREE.Color(colors.defaultInnerColor) }, + }), [colors.defaultInnerColor, colors.defaultOuterColor]); const handleDrag = (point: Point) => { if (toolMode === 'move' && isHovered) { @@ -76,10 +108,11 @@ function Point({ point }: { readonly point: Point }) { const handlePointClick = (point: Point) => { if (deletePointOrLine) { - const removedAisles = removePoint(point.pointUuid); - if (removedAisles.length > 0) { - setHoveredPoint(null); - console.log(removedAisles); + if (point.pointType === 'Aisle') { + const removedAisles = removePoint(point.pointUuid); + if (removedAisles.length > 0) { + setHoveredPoint(null); + } } } } diff --git a/app/src/modules/builder/point/referencePoint.tsx b/app/src/modules/builder/point/reference/referencePoint.tsx similarity index 76% rename from app/src/modules/builder/point/referencePoint.tsx rename to app/src/modules/builder/point/reference/referencePoint.tsx index ac1666c..44d399f 100644 --- a/app/src/modules/builder/point/referencePoint.tsx +++ b/app/src/modules/builder/point/reference/referencePoint.tsx @@ -1,18 +1,21 @@ import * as THREE from 'three'; -import * as Constants from '../../../types/world/worldConstants'; +import * as Constants from '../../../../types/world/worldConstants'; import { useRef, useMemo } from 'react'; function ReferencePoint({ point }: { readonly point: Point }) { const materialRef = useRef(null); const boxScale: [number, number, number] = Constants.pointConfig.boxScale; - const defaultInnerColor = Constants.pointConfig.defaultInnerColor; - const defaultOuterColor = Constants.pointConfig.aisleOuterColor; + const colors = { + defaultInnerColor: Constants.pointConfig.defaultInnerColor, + defaultOuterColor: Constants.pointConfig.helperColor, + defaultDeleteColor: Constants.pointConfig.deleteColor, + }; const uniforms = useMemo(() => ({ - uOuterColor: { value: new THREE.Color(defaultOuterColor) }, - uInnerColor: { value: new THREE.Color(defaultInnerColor) }, - }), [defaultOuterColor, defaultInnerColor]); + uOuterColor: { value: new THREE.Color(colors.defaultOuterColor) }, + uInnerColor: { value: new THREE.Color(colors.defaultInnerColor) }, + }), [colors.defaultInnerColor, colors.defaultOuterColor, colors.defaultDeleteColor]); if (!point) { return null; diff --git a/app/src/modules/builder/wall/Instances/instance/wallInstance.tsx b/app/src/modules/builder/wall/Instances/instance/wallInstance.tsx new file mode 100644 index 0000000..f31f3c2 --- /dev/null +++ b/app/src/modules/builder/wall/Instances/instance/wallInstance.tsx @@ -0,0 +1,21 @@ +import Line from '../../../line/line' +import Point from '../../../point/point'; +import { useToggleView } from '../../../../../store/builder/store'; + +function WallInstance({ wall }: { readonly wall: Wall }) { + const { toggleView } = useToggleView(); + + return ( + <> + {toggleView && ( + <> + + + + + )} + + ) +} + +export default WallInstance \ No newline at end of file diff --git a/app/src/modules/builder/wall/Instances/wallInstances.tsx b/app/src/modules/builder/wall/Instances/wallInstances.tsx new file mode 100644 index 0000000..b9c5499 --- /dev/null +++ b/app/src/modules/builder/wall/Instances/wallInstances.tsx @@ -0,0 +1,21 @@ +import { useEffect } from 'react'; +import { useWallStore } from '../../../../store/builder/useWallStore' +import WallInstance from './instance/wallInstance'; + +function WallInstances() { + const { walls } = useWallStore(); + + useEffect(() => { + // console.log('walls: ', walls); + }, [walls]) + + return ( + <> + {walls.map((wall) => ( + + ))} + + ) +} + +export default WallInstances \ No newline at end of file diff --git a/app/src/modules/builder/wall/wallCreator/referenceWall.tsx b/app/src/modules/builder/wall/wallCreator/referenceWall.tsx new file mode 100644 index 0000000..7849705 --- /dev/null +++ b/app/src/modules/builder/wall/wallCreator/referenceWall.tsx @@ -0,0 +1,128 @@ +import { useEffect, useRef, useState } from 'react'; +import * as THREE from 'three'; +import { useFrame, useThree } from '@react-three/fiber'; +import { Html } from '@react-three/drei'; +import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; +import { useActiveLayer, useToolMode, useToggleView } from '../../../../store/builder/store'; +import { useDirectionalSnapping } from '../../point/helpers/useDirectionalSnapping'; +import { usePointSnapping } from '../../point/helpers/usePointSnapping'; +import * as Constants from '../../../../types/world/worldConstants'; +import ReferenceLine from '../../line/reference/referenceLine'; + +interface ReferenceWallProps { + tempPoints: Point[]; +} + +function ReferenceWall({ tempPoints }: Readonly) { + const { wallHeight, wallThickness, setSnappedPosition, setSnappedPoint } = useBuilderStore(); + + const { pointer, raycaster, camera } = useThree(); + const { toolMode } = useToolMode(); + const { toggleView } = useToggleView(); + const { activeLayer } = useActiveLayer(); + const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); + const finalPosition = useRef<[number, number, number] | null>(null); + + const [tempWall, setTempWall] = useState(null); + const [currentPosition, setCurrentPosition] = useState<[number, number, number]>(tempPoints[0]?.position); + + const directionalSnap = useDirectionalSnapping(currentPosition, tempPoints[0]?.position || null); + const { checkSnapForWall } = usePointSnapping({ uuid: 'temp-wall', pointType: 'Wall', position: directionalSnap.position || [0, 0, 0] }); + + useFrame(() => { + if (toolMode === 'Wall' && toggleView && tempPoints.length === 1) { + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + raycaster.ray.intersectPlane(plane, intersectionPoint); + + setCurrentPosition([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]); + + if (intersectionPoint) { + const snapped = checkSnapForWall([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]); + + if (snapped.isSnapped && snapped.snappedPoint) { + finalPosition.current = snapped.position; + setSnappedPosition(snapped.position); + setSnappedPoint(snapped.snappedPoint); + } else if (directionalSnap.isSnapped) { + finalPosition.current = directionalSnap.position; + setSnappedPosition(directionalSnap.position); + setSnappedPoint(null); + } else { + finalPosition.current = [intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]; + setSnappedPosition(null); + setSnappedPoint(null); + } + + if (!finalPosition.current) return; + + const wallPoints: [Point, Point] = [ + tempPoints[0], + { + pointUuid: 'temp-point', + pointType: 'Wall', + position: finalPosition.current, + layer: activeLayer, + } + ]; + + setTempWall({ + wallUuid: 'temp-wall', + points: wallPoints, + outSideMaterial: 'default', + inSideMaterial: 'default', + wallThickness: wallThickness, + wallHeight: wallHeight, + }) + + } + } else if (tempWall !== null) { + setTempWall(null); + } + }); + + useEffect(() => { + setTempWall(null); + }, [toolMode, toggleView, tempPoints.length, wallHeight, wallThickness]); + + if (!tempWall) return null; + + const renderWall = () => { + return ( + + ) + }; + + const textPosition = new THREE.Vector3().addVectors(new THREE.Vector3(...tempWall.points[0].position), new THREE.Vector3(...tempWall.points[1].position)).divideScalar(2); + const distance = new THREE.Vector3(...tempWall.points[0].position).distanceTo(new THREE.Vector3(...tempWall.points[1].position)); + + const rendertext = () => { + return ( + <> + {toggleView && ( + +
{distance.toFixed(2)} m
+ + )} + + ) + } + + return ( + + {renderWall()} + {rendertext()} + + ); +} + +export default ReferenceWall; \ No newline at end of file diff --git a/app/src/modules/builder/wall/wallCreator/wallCreator.tsx b/app/src/modules/builder/wall/wallCreator/wallCreator.tsx new file mode 100644 index 0000000..adc3f6d --- /dev/null +++ b/app/src/modules/builder/wall/wallCreator/wallCreator.tsx @@ -0,0 +1,155 @@ +import * as THREE from 'three' +import { useEffect, useMemo, useRef, useState } from 'react' +import { useThree } from '@react-three/fiber'; +import { useActiveLayer, useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store'; +import { useWallStore } from '../../../../store/builder/useWallStore'; +import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; +import ReferencePoint from '../../point/reference/referencePoint'; +import ReferenceWall from './referenceWall'; + +function WallCreator() { + const { scene, camera, raycaster, gl, pointer } = useThree(); + const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); + const { toggleView } = useToggleView(); + const { toolMode } = useToolMode(); + const { activeLayer } = useActiveLayer(); + const { socket } = useSocketStore(); + const { addWall, getWallPointById } = useWallStore(); + const drag = useRef(false); + const isLeftMouseDown = useRef(false); + const { wallThickness, wallHeight, snappedPosition, snappedPoint } = useBuilderStore(); + + const [tempPoints, setTempPoints] = useState([]); + const [isCreating, setIsCreating] = useState(false); + + useEffect(() => { + const canvasElement = gl.domElement; + + const onMouseDown = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown.current = true; + drag.current = false; + } + }; + + const onMouseUp = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown.current = false; + } + }; + + const onMouseMove = () => { + if (isLeftMouseDown) { + drag.current = true; + } + }; + + const onMouseClick = () => { + if (drag.current || !toggleView) return; + + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + let position = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (!position) return; + + const intersects = raycaster.intersectObjects(scene.children).find((intersect) => intersect.object.name === 'Aisle-Point'); + + const newPoint: Point = { + pointUuid: THREE.MathUtils.generateUUID(), + pointType: 'Wall', + position: [position.x, position.y, position.z], + layer: activeLayer + }; + + if (snappedPosition && snappedPoint) { + newPoint.pointUuid = snappedPoint.pointUuid; + newPoint.position = snappedPosition; + newPoint.layer = snappedPoint.layer; + } + + if (snappedPosition && !snappedPoint) { + newPoint.position = snappedPosition; + } + + if (intersects && !snappedPoint) { + const point = getWallPointById(intersects.object.uuid); + if (point) { + newPoint.pointUuid = point.pointUuid; + newPoint.position = point.position; + newPoint.layer = point.layer; + } + } + + if (tempPoints.length === 0) { + setTempPoints([newPoint]); + setIsCreating(true); + } else { + const wall: Wall = { + wallUuid: THREE.MathUtils.generateUUID(), + points: [tempPoints[0], newPoint], + outSideMaterial: 'default', + inSideMaterial: 'default', + wallThickness: wallThickness, + wallHeight: wallHeight + }; + addWall(wall); + setTempPoints([newPoint]); + } + + }; + + const onContext = (event: any) => { + event.preventDefault(); + if (isCreating) { + setTempPoints([]); + setIsCreating(false); + } + }; + + if (toolMode === "Wall" && toggleView) { + canvasElement.addEventListener("mousedown", onMouseDown); + canvasElement.addEventListener("mouseup", onMouseUp); + canvasElement.addEventListener("mousemove", onMouseMove); + canvasElement.addEventListener("click", onMouseClick); + canvasElement.addEventListener("contextmenu", onContext); + } else { + if (tempPoints.length > 0 || isCreating) { + setTempPoints([]); + setIsCreating(false); + } + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + canvasElement.removeEventListener("click", onMouseClick); + canvasElement.removeEventListener("contextmenu", onContext); + } + + return () => { + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + canvasElement.removeEventListener("click", onMouseClick); + canvasElement.removeEventListener("contextmenu", onContext); + }; + }, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addWall, getWallPointById, snappedPosition, snappedPoint]); + + return ( + <> + {toggleView && + <> + + {tempPoints.map((point) => ( + + ))} + + + {tempPoints.length > 0 && + + } + + } + + ) +} + +export default WallCreator \ No newline at end of file diff --git a/app/src/modules/builder/wall/wallGroup.tsx b/app/src/modules/builder/wall/wallGroup.tsx new file mode 100644 index 0000000..b3fe3c4 --- /dev/null +++ b/app/src/modules/builder/wall/wallGroup.tsx @@ -0,0 +1,16 @@ +import WallCreator from './wallCreator/wallCreator' +import WallInstances from './Instances/wallInstances' + +function WallGroup() { + return ( + <> + + + + + + + ) +} + +export default WallGroup \ No newline at end of file diff --git a/app/src/modules/simulation/materials/materials.tsx b/app/src/modules/simulation/materials/materials.tsx index 0adc327..296f96c 100644 --- a/app/src/modules/simulation/materials/materials.tsx +++ b/app/src/modules/simulation/materials/materials.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react' import MaterialInstances from './instances/materialInstances' import { usePlayButtonStore, useResetButtonStore } from '../../../store/usePlayButtonStore'; -import MaterialCollisionDetector from './collisionDetection/materialCollitionDetector'; +// import MaterialCollisionDetector from './collisionDetection/materialCollitionDetector'; import { useSceneContext } from '../../scene/sceneContext'; function Materials() { @@ -25,7 +25,7 @@ function Materials() { } - + {/* */} ) diff --git a/app/src/store/builder/useBuilderStore.ts b/app/src/store/builder/useBuilderStore.ts index 8b209dc..29f1753 100644 --- a/app/src/store/builder/useBuilderStore.ts +++ b/app/src/store/builder/useBuilderStore.ts @@ -9,6 +9,16 @@ interface BuilderState { snappedPoint: Point | null; snappedPosition: [number, number, number] | null; + // Wall + + wallThickness: number; + wallHeight: number; + + setWallThickness: (thickness: number) => void; + setWallHeight: (height: number) => void; + + // Aisle + selectedAisle: Object3D | null; aisleType: AisleTypes; @@ -68,6 +78,25 @@ export const useBuilderStore = create()( snappedPoint: null, snappedPosition: null, + // Wall + + wallThickness: 2, + wallHeight: 7, + + setWallThickness: (thickness: number) => { + set((state) => { + state.wallThickness = thickness; + }) + }, + + setWallHeight: (height: number) => { + set((state) => { + state.wallHeight = height; + }) + }, + + // Aisle + selectedAisle: null, aisleType: 'solid-aisle', diff --git a/app/src/store/builder/useWallStore.tsx b/app/src/store/builder/useWallStore.tsx new file mode 100644 index 0000000..d1bd02d --- /dev/null +++ b/app/src/store/builder/useWallStore.tsx @@ -0,0 +1,100 @@ +import { create } from 'zustand'; +import { immer } from 'zustand/middleware/immer'; + +interface WallStore { + walls: Wall[]; + setWalls: (walls: Wall[]) => void; + addWall: (wall: Wall) => void; + updateWall: (uuid: string, updated: Partial) => void; + removeWall: (uuid: string) => void; + + removePoint: (pointUuid: string) => Wall[]; + setPosition: (pointUuid: string, position: [number, number, number]) => void; + setLayer: (pointUuid: string, layer: number) => void; + + getWallById: (uuid: string) => Wall | undefined; + getWallPointById: (uuid: string) => Point | undefined; + getConnectedPoints: (uuid: string) => Point[]; +} + +export const useWallStore = create()( + immer((set, get) => ({ + walls: [], + + setWalls: (walls) => set((state) => { + state.walls = walls; + }), + + addWall: (wall) => set((state) => { + state.walls.push(wall); + }), + + updateWall: (uuid, updated) => set((state) => { + const wall = state.walls.find(w => w.wallUuid === uuid); + if (wall) { + Object.assign(wall, updated); + } + }), + + removeWall: (uuid) => set((state) => { + state.walls = state.walls.filter(w => w.wallUuid !== uuid); + }), + + removePoint: (pointUuid) => { + const removedWalls: Wall[] = []; + set((state) => { + state.walls = state.walls.filter((wall) => { + const hasPoint = wall.points.some(p => p.pointUuid === pointUuid); + if (hasPoint) { + removedWalls.push(JSON.parse(JSON.stringify(wall))); + return false; + } + return true; + }); + }); + return removedWalls; + }, + + setPosition: (pointUuid, position) => set((state) => { + for (const wall of state.walls) { + const point = wall.points.find(p => p.pointUuid === pointUuid); + if (point) { + point.position = position; + } + } + }), + + setLayer: (pointUuid, layer) => set((state) => { + for (const wall of state.walls) { + const point = wall.points.find(p => p.pointUuid === pointUuid); + if (point) { + point.layer = layer; + } + } + }), + + getWallById: (uuid) => { + return get().walls.find(w => w.wallUuid === uuid); + }, + + getWallPointById: (uuid) => { + for (const wall of get().walls) { + const point = wall.points.find(p => p.pointUuid === uuid); + if (point) return point; + } + return undefined; + }, + + getConnectedPoints: (uuid) => { + const connected: Point[] = []; + for (const wall of get().walls) { + for (const point of wall.points) { + if (point.pointUuid === uuid) { + connected.push(...wall.points.filter(p => p.pointUuid !== uuid)); + } + } + } + return connected; + }, + })) +); diff --git a/app/src/types/builderTypes.d.ts b/app/src/types/builderTypes.d.ts index 19d413e..b1ffa5d 100644 --- a/app/src/types/builderTypes.d.ts +++ b/app/src/types/builderTypes.d.ts @@ -52,10 +52,12 @@ interface Wall { points: [Point, Point]; outSideMaterial: string; inSideMaterial: string; - thickness: number; - height: number; + wallThickness: number; + wallHeight: number; } +type Walls = Wall[]; + // Aisle diff --git a/app/src/types/world/worldConstants.ts b/app/src/types/world/worldConstants.ts index c2cb62d..1d49585 100644 --- a/app/src/types/world/worldConstants.ts +++ b/app/src/types/world/worldConstants.ts @@ -118,6 +118,7 @@ export type PointConfig = { aisleOuterColor: string; zoneOuterColor: string; snappingThreshold: number; + helperColor: string; }; export type LineConfig = { @@ -135,6 +136,7 @@ export type LineConfig = { floorColor: string; aisleColor: string; zoneColor: string; + deleteColor: string; helperColor: string; }; @@ -305,6 +307,7 @@ export const pointConfig: PointConfig = { aisleOuterColor: "#FBBC05", // Outer color of the aisle points zoneOuterColor: "#007BFF", // Outer color of the zone points snappingThreshold: 1, // Threshold for snapping + helperColor: "#C164FF", // Color of the helper lines }; export const lineConfig: LineConfig = { @@ -322,6 +325,7 @@ export const lineConfig: LineConfig = { floorColor: "#808080", // Color of the floor lines aisleColor: "#FBBC05", // Color of the aisle lines zoneColor: "#007BFF", // Color of the zone lines + deleteColor: "#ff0000", // Color of the line when deleting helperColor: "#C164FF", // Color of the helper lines };