feat: Implement wall creation and management features

- Added WallGroup component to manage wall creation and instances.
- Introduced WallCreator for handling wall creation logic, including mouse events and snapping.
- Created ReferenceWall component to visualize temporary wall during creation.
- Implemented WallInstances to render existing walls in the scene.
- Added useWallStore for state management of walls, including adding, updating, and removing walls.
- Enhanced Point and Line components to support wall-related functionalities.
- Updated builder store to include wall properties such as thickness and height.
- Refactored point snapping logic to accommodate wall snapping.
- Removed unused ReferencePoint component and adjusted imports accordingly.
- Updated world constants to include new wall-related configurations.
This commit is contained in:
Jerald-Golden-B 2025-06-04 10:23:22 +05:30
parent 49ac226078
commit 20b6f84b38
21 changed files with 903 additions and 355 deletions

View File

@ -8,11 +8,9 @@ import { upsertProductOrEventApi } from "../../../../../../services/simulation/p
import { useProductStore } from "../../../../../../store/simulation/useProductStore"; import { useProductStore } from "../../../../../../store/simulation/useProductStore";
import { useSelectedAction, useSelectedEventData } from "../../../../../../store/simulation/useSimulationStore"; import { useSelectedAction, useSelectedEventData } from "../../../../../../store/simulation/useSimulationStore";
import * as THREE from 'three'; import * as THREE from 'three';
import { useSceneContext } from "../../../../../../modules/scene/sceneContext";
import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; import { useProductContext } from "../../../../../../modules/simulation/products/productContext";
function StorageMechanics() { function StorageMechanics() {
const { storageUnitStore } = useSceneContext();
const [activeOption, setActiveOption] = useState<"default" | "store" | "spawn">("default"); const [activeOption, setActiveOption] = useState<"default" | "store" | "spawn">("default");
const [selectedPointData, setSelectedPointData] = useState<StoragePointSchema | undefined>(); const [selectedPointData, setSelectedPointData] = useState<StoragePointSchema | undefined>();
const { selectedEventData } = useSelectedEventData(); const { selectedEventData } = useSelectedEventData();
@ -20,7 +18,6 @@ function StorageMechanics() {
const { selectedProductStore } = useProductContext(); const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore(); const { selectedProduct } = selectedProductStore();
const { setSelectedAction, clearSelectedAction } = useSelectedAction(); const { setSelectedAction, clearSelectedAction } = useSelectedAction();
const { setCurrentMaterials, clearCurrentMaterials, updateCurrentLoad, getStorageUnitById } = storageUnitStore();
const email = localStorage.getItem('email') const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0]; const organization = (email!.split("@")[1]).split(".")[0];
@ -92,22 +89,6 @@ function StorageMechanics() {
); );
updateSelectedPointData(); 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) => { const handleRenameAction = (newName: string) => {
@ -141,21 +122,6 @@ function StorageMechanics() {
event event
); );
updateSelectedPointData(); 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 event
); );
updateSelectedPointData(); 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);
}
}
} }
}; };

View File

@ -4,316 +4,293 @@ import RenameInput from "../../../../../ui/inputs/RenameInput";
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
import Trigger from "../trigger/Trigger"; import Trigger from "../trigger/Trigger";
import { import {
useSelectedAction, useSelectedAction,
useSelectedEventData, useSelectedEventData,
useSelectedEventSphere,
} from "../../../../../../store/simulation/useSimulationStore"; } from "../../../../../../store/simulation/useSimulationStore";
import { useProductStore } from "../../../../../../store/simulation/useProductStore"; import { useProductStore } from "../../../../../../store/simulation/useProductStore";
import TravelAction from "../actions/TravelAction"; import TravelAction from "../actions/TravelAction";
import ActionsList from "../components/ActionsList"; import ActionsList from "../components/ActionsList";
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";
import { useSceneContext } from "../../../../../../modules/scene/sceneContext";
import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; import { useProductContext } from "../../../../../../modules/simulation/products/productContext";
function VehicleMechanics() { function VehicleMechanics() {
const { vehicleStore } = useSceneContext(); const [activeOption, setActiveOption] = useState<"default" | "travel">("default");
const [activeOption, setActiveOption] = useState<"default" | "travel">("default"); const [selectedPointData, setSelectedPointData] = useState<VehiclePointSchema | undefined>();
const [selectedPointData, setSelectedPointData] = useState<VehiclePointSchema | undefined>(); const { selectedEventData } = useSelectedEventData();
const { selectedEventData } = useSelectedEventData(); const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction } = useProductStore();
const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction } = useProductStore(); const { selectedProductStore } = useProductContext();
const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore();
const { selectedProduct } = selectedProductStore(); const { setSelectedAction, clearSelectedAction } = useSelectedAction();
const { setSelectedAction, clearSelectedAction } = useSelectedAction();
const { getVehicleById } = vehicleStore();
const email = localStorage.getItem("email"); const email = localStorage.getItem("email");
const organization = email!.split("@")[1].split(".")[0]; const organization = email!.split("@")[1].split(".")[0];
useEffect(() => { useEffect(() => {
if (selectedEventData && selectedEventData.data.type === "vehicle") { if (selectedEventData && selectedEventData.data.type === "vehicle") {
const point = getPointByUuid( const point = getPointByUuid(
selectedProduct.productId, selectedProduct.productId,
selectedEventData.data.modelUuid, selectedEventData.data.modelUuid,
selectedEventData.selectedPoint selectedEventData.selectedPoint
) as VehiclePointSchema | undefined; ) as VehiclePointSchema | undefined;
if (point) { if (point) {
setSelectedPointData(point); setSelectedPointData(point);
setActiveOption(point.action.actionType as "travel"); setActiveOption(point.action.actionType as "travel");
setSelectedAction(point.action.actionUuid, point.action.actionName); setSelectedAction(point.action.actionUuid, point.action.actionName);
} }
} else { } else {
clearSelectedAction(); clearSelectedAction();
} }
}, [selectedProduct, selectedEventData]); }, [selectedProduct, selectedEventData]);
const updateBackend = ( const updateBackend = (
productName: string, productName: string,
productId: string, productId: string,
organization: string, organization: string,
eventData: EventsSchema eventData: EventsSchema
) => { ) => {
upsertProductOrEventApi({ upsertProductOrEventApi({
productName: productName, productName: productName,
productId: productId, productId: productId,
organization: organization, organization: organization,
eventDatas: eventData, eventDatas: eventData,
}); });
}; };
const handleSpeedChange = (value: string) => { const handleSpeedChange = (value: string) => {
if (!selectedEventData) return; if (!selectedEventData) return;
const event = updateEvent( const event = updateEvent(
selectedProduct.productId, selectedProduct.productId,
selectedEventData.data.modelUuid, selectedEventData.data.modelUuid,
{ {
speed: parseFloat(value), speed: parseFloat(value),
} }
); );
if (event) { if (event) {
updateBackend( updateBackend(
selectedProduct.productName, selectedProduct.productName,
selectedProduct.productId, selectedProduct.productId,
organization, organization,
event event
); );
} }
}; };
const handleActionTypeChange = (option: string) => { const handleActionTypeChange = (option: string) => {
if (!selectedEventData || !selectedPointData) return; if (!selectedEventData || !selectedPointData) return;
const validOption = option as "travel"; const validOption = option as "travel";
setActiveOption(validOption); setActiveOption(validOption);
const event = updateAction( const event = updateAction(
selectedProduct.productId, selectedProduct.productId,
selectedPointData.action.actionUuid, selectedPointData.action.actionUuid,
{ {
actionType: validOption, actionType: validOption,
} }
); );
if (event) { if (event) {
updateBackend( updateBackend(
selectedProduct.productName, selectedProduct.productName,
selectedProduct.productId, selectedProduct.productId,
organization, organization,
event event
); );
} }
}; };
const handleRenameAction = (newName: string) => { const handleRenameAction = (newName: string) => {
if (!selectedPointData) return; if (!selectedPointData) return;
const event = updateAction( const event = updateAction(
selectedProduct.productId, selectedProduct.productId,
selectedPointData.action.actionUuid, selectedPointData.action.actionUuid,
{ actionName: newName } { actionName: newName }
); );
if (event) { if (event) {
updateBackend( updateBackend(
selectedProduct.productName, selectedProduct.productName,
selectedProduct.productId, selectedProduct.productId,
organization, organization,
event event
); );
} }
}; };
const handleLoadCapacityChange = (value: string) => { const handleLoadCapacityChange = (value: string) => {
if (!selectedPointData) return; if (!selectedPointData) return;
const event = updateAction( const event = updateAction(
selectedProduct.productId, selectedProduct.productId,
selectedPointData.action.actionUuid, selectedPointData.action.actionUuid,
{ {
loadCapacity: parseFloat(value), loadCapacity: parseFloat(value),
} }
); );
if (event) { if (event) {
updateBackend( updateBackend(
selectedProduct.productName, selectedProduct.productName,
selectedProduct.productId, selectedProduct.productId,
organization, organization,
event event
); );
} }
}; };
const handleUnloadDurationChange = (value: string) => { const handleUnloadDurationChange = (value: string) => {
if (!selectedPointData) return; if (!selectedPointData) return;
const event = updateAction( const event = updateAction(
selectedProduct.productId, selectedProduct.productId,
selectedPointData.action.actionUuid, selectedPointData.action.actionUuid,
{ {
unLoadDuration: parseFloat(value), unLoadDuration: parseFloat(value),
} }
); );
if (event) { if (event) {
updateBackend( updateBackend(
selectedProduct.productName, selectedProduct.productName,
selectedProduct.productId, selectedProduct.productId,
organization, organization,
event event
); );
} }
}; };
const handlePickPointChange = (value: string) => { const handlePickPointChange = (value: string) => {
if (!selectedPointData) return; if (!selectedPointData) return;
}; };
const handleUnloadPointChange = (value: string) => { const handleUnloadPointChange = (value: string) => {
if (!selectedPointData) return; if (!selectedPointData) return;
}; };
// Get current values from store // Get current values from store
const currentSpeed = const currentSpeed =
( (
getEventByModelUuid( getEventByModelUuid(
selectedProduct.productId, selectedProduct.productId,
selectedEventData?.data.modelUuid || "" selectedEventData?.data.modelUuid || ""
) as VehicleEventSchema | undefined ) as VehicleEventSchema | undefined
)?.speed?.toString() || "0.5"; )?.speed?.toString() || "0.5";
const currentActionName = selectedPointData const currentActionName = selectedPointData
? selectedPointData.action.actionName ? selectedPointData.action.actionName
: "Action Name"; : "Action Name";
const currentLoadCapacity = selectedPointData const currentLoadCapacity = selectedPointData
? selectedPointData.action.loadCapacity.toString() ? selectedPointData.action.loadCapacity.toString()
: "1"; : "1";
const currentUnloadDuration = selectedPointData const currentUnloadDuration = selectedPointData
? selectedPointData.action.unLoadDuration.toString() ? selectedPointData.action.unLoadDuration.toString()
: "1"; : "1";
const { selectedEventSphere } = useSelectedEventSphere(); function handleClearPoints() {
function handleClearPoints() { if (!selectedEventData || !selectedPointData?.action.actionUuid) return;
const updatedVehicle = getVehicleById(
selectedEventSphere?.userData.modelUuid
);
if ( const event = updateAction(
!selectedProduct?.productId || selectedProduct.productId, selectedPointData.action.actionUuid, {
!selectedEventSphere?.userData?.modelUuid ||
!updatedVehicle?.point
)
return;
const event = updateEvent(
selectedProduct.productId,
selectedEventSphere.userData.modelUuid,
{
point: {
...updatedVehicle.point,
action: {
...updatedVehicle.point?.action,
pickUpPoint: null, pickUpPoint: null,
unLoadPoint: null, unLoadPoint: null,
steeringAngle: 0, steeringAngle: 0,
}, })
},
}
);
if (event) { if (event) {
updateBackend( updateBackend(
selectedProduct.productName, selectedProduct.productName,
selectedProduct.productId, selectedProduct.productId,
organization, organization,
event event
); );
}
} }
}
const availableActions = { const availableActions = {
defaultOption: "travel", defaultOption: "travel",
options: ["travel"], options: ["travel"],
}; };
return ( return (
<>
{selectedEventData && (
<> <>
<div className="global-props section"> {selectedEventData && (
<div className="property-list-container"> <>
<div className="property-item"> <div className="global-props section">
<InputWithDropDown <div className="property-list-container">
label="Speed" <div className="property-item">
value={currentSpeed} <InputWithDropDown
min={0} label="Speed"
step={0.1} value={currentSpeed}
defaultValue={"0.5"} min={0}
max={10} step={0.1}
activeOption="m/s" defaultValue={"0.5"}
onClick={() => { }} max={10}
onChange={handleSpeedChange} activeOption="m/s"
/> onClick={() => { }}
</div> onChange={handleSpeedChange}
</div> />
</div> </div>
<section> </div>
<ActionsList selectedPointData={selectedPointData} /> </div>
<div className="selected-actions-details"> <section>
<div className="selected-actions-header"> <ActionsList selectedPointData={selectedPointData} />
<RenameInput <div className="selected-actions-details">
value={currentActionName} <div className="selected-actions-header">
onRename={handleRenameAction} <RenameInput
/> value={currentActionName}
</div> onRename={handleRenameAction}
<div className="selected-actions-list"> />
<LabledDropdown </div>
defaultOption="travel" <div className="selected-actions-list">
options={availableActions.options} <LabledDropdown
onSelect={handleActionTypeChange} defaultOption="travel"
/> options={availableActions.options}
onSelect={handleActionTypeChange}
/>
{activeOption === "travel" && ( {activeOption === "travel" && (
<TravelAction <TravelAction
loadCapacity={{ loadCapacity={{
value: currentLoadCapacity, value: currentLoadCapacity,
min: 1, min: 1,
max: 100, max: 100,
defaultValue: "1", defaultValue: "1",
onChange: handleLoadCapacityChange, onChange: handleLoadCapacityChange,
}} }}
unloadDuration={{ unloadDuration={{
value: currentUnloadDuration, value: currentUnloadDuration,
min: 1, min: 1,
max: 60, max: 60,
defaultValue: "1", defaultValue: "1",
onChange: handleUnloadDurationChange, onChange: handleUnloadDurationChange,
}} }}
clearPoints={handleClearPoints} clearPoints={handleClearPoints}
// pickPoint={{ // pickPoint={{
// value: currentPickPoint, // value: currentPickPoint,
// onChange: handlePickPointChange, // onChange: handlePickPointChange,
// }} // }}
// unloadPoint={{ // unloadPoint={{
// value: currentUnloadPoint, // value: currentUnloadPoint,
// onChange: handleUnloadPointChange, // onChange: handleUnloadPointChange,
// }} // }}
/> />
)} )}
</div> </div>
</div> </div>
<div className="tirgger"> <div className="tirgger">
<Trigger <Trigger
selectedPointData={selectedPointData as any} selectedPointData={selectedPointData as any}
type={"Vehicle"} type={"Vehicle"}
/> />
</div> </div>
</section> </section>
</>
)}
</> </>
)} );
</>
);
} }
export default VehicleMechanics; export default VehicleMechanics;

View File

@ -5,7 +5,7 @@ import { useActiveLayer, useSocketStore, useToggleView, useToolMode } from '../.
import { useAisleStore } from '../../../../store/builder/useAisleStore'; import { useAisleStore } from '../../../../store/builder/useAisleStore';
import ReferenceAisle from './referenceAisle'; import ReferenceAisle from './referenceAisle';
import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
import ReferencePoint from '../../point/referencePoint'; import ReferencePoint from '../../point/reference/referencePoint';
function AisleCreator() { function AisleCreator() {
const { scene, camera, raycaster, gl, pointer } = useThree(); const { scene, camera, raycaster, gl, pointer } = useThree();
@ -249,8 +249,10 @@ function AisleCreator() {
canvasElement.addEventListener("click", onMouseClick); canvasElement.addEventListener("click", onMouseClick);
canvasElement.addEventListener("contextmenu", onContext); canvasElement.addEventListener("contextmenu", onContext);
} else { } else {
setTempPoints([]); if (tempPoints.length > 0 || isCreating) {
setIsCreating(false); setTempPoints([]);
setIsCreating(false);
}
canvasElement.removeEventListener("mousedown", onMouseDown); canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp); canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove); canvasElement.removeEventListener("mousemove", onMouseMove);
@ -265,7 +267,7 @@ function AisleCreator() {
canvasElement.removeEventListener("click", onMouseClick); canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContext); 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 ( return (
<> <>

View File

@ -24,10 +24,7 @@ function ReferenceAisle({ tempPoints }: Readonly<ReferenceAisleProps>) {
const [tempAisle, setTempAisle] = useState<Aisle | null>(null); const [tempAisle, setTempAisle] = useState<Aisle | null>(null);
const [currentPosition, setCurrentPosition] = useState<[number, number, number]>(tempPoints[0]?.position); const [currentPosition, setCurrentPosition] = useState<[number, number, number]>(tempPoints[0]?.position);
const directionalSnap = useDirectionalSnapping( const directionalSnap = useDirectionalSnapping(currentPosition, tempPoints[0]?.position || null);
currentPosition,
tempPoints[0]?.position || null
);
const { checkSnapForAisle } = usePointSnapping({ uuid: 'temp-aisle', pointType: 'Aisle', position: directionalSnap.position || [0, 0, 0] }); const { checkSnapForAisle } = usePointSnapping({ uuid: 'temp-aisle', pointType: 'Aisle', position: directionalSnap.position || [0, 0, 0] });
useFrame(() => { useFrame(() => {
@ -39,7 +36,6 @@ function ReferenceAisle({ tempPoints }: Readonly<ReferenceAisleProps>) {
setCurrentPosition([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]); setCurrentPosition([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]);
if (intersectionPoint) { if (intersectionPoint) {
const snapped = checkSnapForAisle([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]); const snapped = checkSnapForAisle([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]);
if (snapped.isSnapped && snapped.snappedPoint) { if (snapped.isSnapped && snapped.snappedPoint) {
@ -216,14 +212,11 @@ function ReferenceAisle({ tempPoints }: Readonly<ReferenceAisleProps>) {
<> <>
{toggleView && {toggleView &&
<Html <Html
// data
key={tempAisle.aisleUuid} key={tempAisle.aisleUuid}
userData={tempAisle} userData={tempAisle}
position={[textPosition.x, 1, textPosition.z]} position={[textPosition.x, 1, textPosition.z]}
// class
wrapperClass="distance-text-wrapper" wrapperClass="distance-text-wrapper"
className="distance-text" className="distance-text"
// other
zIndexRange={[1, 0]} zIndexRange={[1, 0]}
prepend prepend
sprite sprite

View File

@ -48,6 +48,7 @@ import LayoutImage from "./layout/layoutImage";
import AssetsGroup from "./asset/assetsGroup"; import AssetsGroup from "./asset/assetsGroup";
import { Bvh } from "@react-three/drei"; import { Bvh } from "@react-three/drei";
import AislesGroup from "./aisle/aislesGroup"; import AislesGroup from "./aisle/aislesGroup";
import WallGroup from "./wall/wallGroup";
export default function Builder() { export default function Builder() {
const state = useThree<Types.ThreeState>(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements. const state = useThree<Types.ThreeState>(); // 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} plane={plane}
/> />
<WallGroup />
<AislesGroup /> <AislesGroup />
<MeasurementTool /> <MeasurementTool />

View File

@ -157,7 +157,7 @@ const FloorPlanGroup = ({ floorPlanGroup, floorPlanGroupLine, floorPlanGroupPoin
} }
if (toolMode === "Wall") { 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") { if (toolMode === "Floor") {

View File

@ -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<LineProps>) {
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 (
<Tube
args={[path, Constants.lineConfig.tubularSegments, Constants.lineConfig.radius, Constants.lineConfig.radialSegments, false]}
>
<meshStandardMaterial color={colors.defaultLineColor} />
</Tube>
);
}
export default Line;

View File

@ -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<ReferenceLineProps>) {
const path = useMemo(() => {
const [start, end] = points.map(p => new THREE.Vector3(...p.position));
return new THREE.LineCurve3(start, end);
}, [points]);
return (
<Tube
args={[path, Constants.lineConfig.tubularSegments, Constants.lineConfig.radius, Constants.lineConfig.radialSegments, false]}
>
<meshStandardMaterial color={Constants.lineConfig.helperColor} />
</Tube>
);
}
export default ReferenceLine;

View File

@ -1,6 +1,7 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useAisleStore } from '../../../../store/builder/useAisleStore'; import { useAisleStore } from '../../../../store/builder/useAisleStore';
import * as THREE from 'three'; import * as THREE from 'three';
import { useWallStore } from '../../../../store/builder/useWallStore';
const SNAP_THRESHOLD = 0.5; // Distance threshold for snapping in meters 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) => { export const usePointSnapping = (currentPoint: { uuid: string, pointType: string, position: [number, number, number] } | null) => {
const { aisles } = useAisleStore(); const { aisles } = useAisleStore();
const { walls } = useWallStore();
const getAllOtherPoints = useCallback(() => { const getAllOtherAislePoints = useCallback(() => {
if (!currentPoint) return []; if (!currentPoint) return [];
return aisles.flatMap(aisle => return aisles.flatMap(aisle =>
@ -17,10 +19,17 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
); );
}, [aisles, currentPoint]); }, [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]) => { const checkSnapForAisle = useCallback((position: [number, number, number]) => {
if (!currentPoint || !CAN_SNAP) return { position: position, isSnapped: false, snappedPoint: null }; if (!currentPoint || !CAN_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
const otherPoints = getAllOtherPoints(); const otherPoints = getAllOtherAislePoints();
const currentVec = new THREE.Vector3(...position); const currentVec = new THREE.Vector3(...position);
for (const point of otherPoints) { for (const point of otherPoints) {
@ -33,9 +42,25 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
} }
return { position: position, isSnapped: false, snappedPoint: null }; 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 { return {
checkSnapForAisle, checkSnapForAisle,
checkSnapForWall,
}; };
}; };

View File

@ -22,35 +22,67 @@ function Point({ point }: { readonly point: Point }) {
const { deletePointOrLine } = useDeletePointOrLine(); const { deletePointOrLine } = useDeletePointOrLine();
const boxScale: [number, number, number] = Constants.pointConfig.boxScale; const boxScale: [number, number, number] = Constants.pointConfig.boxScale;
const defaultInnerColor = Constants.pointConfig.defaultInnerColor; const colors = getColor(point);
const defaultOuterColor = Constants.pointConfig.aisleOuterColor;
const defaultDeleteColor = Constants.pointConfig.deleteColor; 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(() => { useEffect(() => {
if (materialRef.current && (toolMode === 'move' || deletePointOrLine)) { if (materialRef.current && (toolMode === 'move' || deletePointOrLine)) {
let innerColor; let innerColor;
let outerColor; let outerColor;
if (isHovered) { if (isHovered) {
innerColor = deletePointOrLine ? defaultDeleteColor : defaultOuterColor; innerColor = deletePointOrLine ? colors.defaultDeleteColor : colors.defaultOuterColor;
outerColor = deletePointOrLine ? defaultDeleteColor : defaultOuterColor; outerColor = deletePointOrLine ? colors.defaultDeleteColor : colors.defaultOuterColor;
} else { } else {
innerColor = defaultInnerColor; innerColor = colors.defaultInnerColor;
outerColor = defaultOuterColor; outerColor = colors.defaultOuterColor;
} }
materialRef.current.uniforms.uInnerColor.value.set(innerColor); materialRef.current.uniforms.uInnerColor.value.set(innerColor);
materialRef.current.uniforms.uOuterColor.value.set(outerColor); materialRef.current.uniforms.uOuterColor.value.set(outerColor);
materialRef.current.uniformsNeedUpdate = true; materialRef.current.uniformsNeedUpdate = true;
} else if (materialRef.current && toolMode !== 'move') { } else if (materialRef.current && toolMode !== 'move') {
materialRef.current.uniforms.uInnerColor.value.set(defaultInnerColor); materialRef.current.uniforms.uInnerColor.value.set(colors.defaultInnerColor);
materialRef.current.uniforms.uOuterColor.value.set(defaultOuterColor); materialRef.current.uniforms.uOuterColor.value.set(colors.defaultOuterColor);
materialRef.current.uniformsNeedUpdate = true; materialRef.current.uniformsNeedUpdate = true;
} }
}, [isHovered, defaultInnerColor, defaultOuterColor, toolMode, deletePointOrLine, defaultDeleteColor]); }, [isHovered, colors.defaultInnerColor, colors.defaultOuterColor, colors.defaultDeleteColor, toolMode, deletePointOrLine]);
const uniforms = useMemo(() => ({ const uniforms = useMemo(() => ({
uOuterColor: { value: new THREE.Color(defaultOuterColor) }, uOuterColor: { value: new THREE.Color(colors.defaultOuterColor) },
uInnerColor: { value: new THREE.Color(defaultInnerColor) }, uInnerColor: { value: new THREE.Color(colors.defaultInnerColor) },
}), [defaultInnerColor, defaultInnerColor]); }), [colors.defaultInnerColor, colors.defaultOuterColor]);
const handleDrag = (point: Point) => { const handleDrag = (point: Point) => {
if (toolMode === 'move' && isHovered) { if (toolMode === 'move' && isHovered) {
@ -76,10 +108,11 @@ function Point({ point }: { readonly point: Point }) {
const handlePointClick = (point: Point) => { const handlePointClick = (point: Point) => {
if (deletePointOrLine) { if (deletePointOrLine) {
const removedAisles = removePoint(point.pointUuid); if (point.pointType === 'Aisle') {
if (removedAisles.length > 0) { const removedAisles = removePoint(point.pointUuid);
setHoveredPoint(null); if (removedAisles.length > 0) {
console.log(removedAisles); setHoveredPoint(null);
}
} }
} }
} }

View File

@ -1,18 +1,21 @@
import * as THREE from 'three'; import * as THREE from 'three';
import * as Constants from '../../../types/world/worldConstants'; import * as Constants from '../../../../types/world/worldConstants';
import { useRef, useMemo } from 'react'; import { useRef, useMemo } from 'react';
function ReferencePoint({ point }: { readonly point: Point }) { function ReferencePoint({ point }: { readonly point: Point }) {
const materialRef = useRef<THREE.ShaderMaterial>(null); const materialRef = useRef<THREE.ShaderMaterial>(null);
const boxScale: [number, number, number] = Constants.pointConfig.boxScale; const boxScale: [number, number, number] = Constants.pointConfig.boxScale;
const defaultInnerColor = Constants.pointConfig.defaultInnerColor; const colors = {
const defaultOuterColor = Constants.pointConfig.aisleOuterColor; defaultInnerColor: Constants.pointConfig.defaultInnerColor,
defaultOuterColor: Constants.pointConfig.helperColor,
defaultDeleteColor: Constants.pointConfig.deleteColor,
};
const uniforms = useMemo(() => ({ const uniforms = useMemo(() => ({
uOuterColor: { value: new THREE.Color(defaultOuterColor) }, uOuterColor: { value: new THREE.Color(colors.defaultOuterColor) },
uInnerColor: { value: new THREE.Color(defaultInnerColor) }, uInnerColor: { value: new THREE.Color(colors.defaultInnerColor) },
}), [defaultOuterColor, defaultInnerColor]); }), [colors.defaultInnerColor, colors.defaultOuterColor, colors.defaultDeleteColor]);
if (!point) { if (!point) {
return null; return null;

View File

@ -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 && (
<>
<Point point={wall.points[0]} />
<Line points={wall.points} />
<Point point={wall.points[1]} />
</>
)}
</>
)
}
export default WallInstance

View File

@ -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) => (
<WallInstance key={wall.wallUuid} wall={wall} />
))}
</>
)
}
export default WallInstances

View File

@ -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<ReferenceWallProps>) {
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<Wall | null>(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 (
<ReferenceLine points={tempWall.points} />
)
};
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 && (
<Html
key={tempWall.wallUuid}
userData={tempWall}
position={[textPosition.x, 1, textPosition.z]}
wrapperClass="distance-text-wrapper"
className="distance-text"
zIndexRange={[1, 0]}
prepend
sprite
>
<div className={`distance ${tempWall.wallUuid}`}>{distance.toFixed(2)} m</div>
</Html>
)}
</>
)
}
return (
<group name="Wall-Reference-Group">
{renderWall()}
{rendertext()}
</group>
);
}
export default ReferenceWall;

View File

@ -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<Point[]>([]);
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 &&
<>
<group name='Aisle-Reference-Points-Group'>
{tempPoints.map((point) => (
<ReferencePoint key={point.pointUuid} point={point} />
))}
</group>
{tempPoints.length > 0 &&
<ReferenceWall tempPoints={tempPoints} />
}
</>
}
</>
)
}
export default WallCreator

View File

@ -0,0 +1,16 @@
import WallCreator from './wallCreator/wallCreator'
import WallInstances from './Instances/wallInstances'
function WallGroup() {
return (
<>
<WallCreator />
<WallInstances />
</>
)
}
export default WallGroup

View File

@ -1,7 +1,7 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import MaterialInstances from './instances/materialInstances' import MaterialInstances from './instances/materialInstances'
import { usePlayButtonStore, useResetButtonStore } from '../../../store/usePlayButtonStore'; import { usePlayButtonStore, useResetButtonStore } from '../../../store/usePlayButtonStore';
import MaterialCollisionDetector from './collisionDetection/materialCollitionDetector'; // import MaterialCollisionDetector from './collisionDetection/materialCollitionDetector';
import { useSceneContext } from '../../scene/sceneContext'; import { useSceneContext } from '../../scene/sceneContext';
function Materials() { function Materials() {
@ -25,7 +25,7 @@ function Materials() {
} }
<MaterialCollisionDetector /> {/* <MaterialCollisionDetector /> */}
</> </>
) )

View File

@ -9,6 +9,16 @@ interface BuilderState {
snappedPoint: Point | null; snappedPoint: Point | null;
snappedPosition: [number, number, number] | null; snappedPosition: [number, number, number] | null;
// Wall
wallThickness: number;
wallHeight: number;
setWallThickness: (thickness: number) => void;
setWallHeight: (height: number) => void;
// Aisle
selectedAisle: Object3D | null; selectedAisle: Object3D | null;
aisleType: AisleTypes; aisleType: AisleTypes;
@ -68,6 +78,25 @@ export const useBuilderStore = create<BuilderState>()(
snappedPoint: null, snappedPoint: null,
snappedPosition: 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, selectedAisle: null,
aisleType: 'solid-aisle', aisleType: 'solid-aisle',

View File

@ -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<Wall>) => 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<WallStore>()(
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;
},
}))
);

View File

@ -52,10 +52,12 @@ interface Wall {
points: [Point, Point]; points: [Point, Point];
outSideMaterial: string; outSideMaterial: string;
inSideMaterial: string; inSideMaterial: string;
thickness: number; wallThickness: number;
height: number; wallHeight: number;
} }
type Walls = Wall[];
// Aisle // Aisle

View File

@ -118,6 +118,7 @@ export type PointConfig = {
aisleOuterColor: string; aisleOuterColor: string;
zoneOuterColor: string; zoneOuterColor: string;
snappingThreshold: number; snappingThreshold: number;
helperColor: string;
}; };
export type LineConfig = { export type LineConfig = {
@ -135,6 +136,7 @@ export type LineConfig = {
floorColor: string; floorColor: string;
aisleColor: string; aisleColor: string;
zoneColor: string; zoneColor: string;
deleteColor: string;
helperColor: string; helperColor: string;
}; };
@ -305,6 +307,7 @@ export const pointConfig: PointConfig = {
aisleOuterColor: "#FBBC05", // Outer color of the aisle points aisleOuterColor: "#FBBC05", // Outer color of the aisle points
zoneOuterColor: "#007BFF", // Outer color of the zone points zoneOuterColor: "#007BFF", // Outer color of the zone points
snappingThreshold: 1, // Threshold for snapping snappingThreshold: 1, // Threshold for snapping
helperColor: "#C164FF", // Color of the helper lines
}; };
export const lineConfig: LineConfig = { export const lineConfig: LineConfig = {
@ -322,6 +325,7 @@ export const lineConfig: LineConfig = {
floorColor: "#808080", // Color of the floor lines floorColor: "#808080", // Color of the floor lines
aisleColor: "#FBBC05", // Color of the aisle lines aisleColor: "#FBBC05", // Color of the aisle lines
zoneColor: "#007BFF", // Color of the zone lines zoneColor: "#007BFF", // Color of the zone lines
deleteColor: "#ff0000", // Color of the line when deleting
helperColor: "#C164FF", // Color of the helper lines helperColor: "#C164FF", // Color of the helper lines
}; };