1. Integerated DashBoard, #97

Merged
Marikannan merged 31 commits from v3-wall into main 2025-06-04 12:09:09 +00:00
21 changed files with 903 additions and 355 deletions
Showing only changes of commit 20b6f84b38 - Show all commits

View File

@ -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<StoragePointSchema | undefined>();
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);
}
}
}
};

View File

@ -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<VehiclePointSchema | undefined>();
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<VehiclePointSchema | undefined>();
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 (
<>
<div className="global-props section">
<div className="property-list-container">
<div className="property-item">
<InputWithDropDown
label="Speed"
value={currentSpeed}
min={0}
step={0.1}
defaultValue={"0.5"}
max={10}
activeOption="m/s"
onClick={() => { }}
onChange={handleSpeedChange}
/>
</div>
</div>
</div>
<section>
<ActionsList selectedPointData={selectedPointData} />
<div className="selected-actions-details">
<div className="selected-actions-header">
<RenameInput
value={currentActionName}
onRename={handleRenameAction}
/>
</div>
<div className="selected-actions-list">
<LabledDropdown
defaultOption="travel"
options={availableActions.options}
onSelect={handleActionTypeChange}
/>
{selectedEventData && (
<>
<div className="global-props section">
<div className="property-list-container">
<div className="property-item">
<InputWithDropDown
label="Speed"
value={currentSpeed}
min={0}
step={0.1}
defaultValue={"0.5"}
max={10}
activeOption="m/s"
onClick={() => { }}
onChange={handleSpeedChange}
/>
</div>
</div>
</div>
<section>
<ActionsList selectedPointData={selectedPointData} />
<div className="selected-actions-details">
<div className="selected-actions-header">
<RenameInput
value={currentActionName}
onRename={handleRenameAction}
/>
</div>
<div className="selected-actions-list">
<LabledDropdown
defaultOption="travel"
options={availableActions.options}
onSelect={handleActionTypeChange}
/>
{activeOption === "travel" && (
<TravelAction
loadCapacity={{
value: currentLoadCapacity,
min: 1,
max: 100,
defaultValue: "1",
onChange: handleLoadCapacityChange,
}}
unloadDuration={{
value: currentUnloadDuration,
min: 1,
max: 60,
defaultValue: "1",
onChange: handleUnloadDurationChange,
}}
clearPoints={handleClearPoints}
// pickPoint={{
// value: currentPickPoint,
// onChange: handlePickPointChange,
// }}
// unloadPoint={{
// value: currentUnloadPoint,
// onChange: handleUnloadPointChange,
// }}
/>
)}
</div>
</div>
<div className="tirgger">
<Trigger
selectedPointData={selectedPointData as any}
type={"Vehicle"}
/>
</div>
</section>
{activeOption === "travel" && (
<TravelAction
loadCapacity={{
value: currentLoadCapacity,
min: 1,
max: 100,
defaultValue: "1",
onChange: handleLoadCapacityChange,
}}
unloadDuration={{
value: currentUnloadDuration,
min: 1,
max: 60,
defaultValue: "1",
onChange: handleUnloadDurationChange,
}}
clearPoints={handleClearPoints}
// pickPoint={{
// value: currentPickPoint,
// onChange: handlePickPointChange,
// }}
// unloadPoint={{
// value: currentUnloadPoint,
// onChange: handleUnloadPointChange,
// }}
/>
)}
</div>
</div>
<div className="tirgger">
<Trigger
selectedPointData={selectedPointData as any}
type={"Vehicle"}
/>
</div>
</section>
</>
)}
</>
)}
</>
);
);
}
export default VehicleMechanics;

View File

@ -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 (
<>

View File

@ -24,10 +24,7 @@ function ReferenceAisle({ tempPoints }: Readonly<ReferenceAisleProps>) {
const [tempAisle, setTempAisle] = useState<Aisle | null>(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<ReferenceAisleProps>) {
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<ReferenceAisleProps>) {
<>
{toggleView &&
<Html
// data
key={tempAisle.aisleUuid}
userData={tempAisle}
position={[textPosition.x, 1, textPosition.z]}
// class
wrapperClass="distance-text-wrapper"
className="distance-text"
// other
zIndexRange={[1, 0]}
prepend
sprite

View File

@ -48,6 +48,7 @@ import LayoutImage from "./layout/layoutImage";
import AssetsGroup from "./asset/assetsGroup";
import { Bvh } from "@react-three/drei";
import AislesGroup from "./aisle/aislesGroup";
import WallGroup from "./wall/wallGroup";
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.
@ -279,6 +280,8 @@ export default function Builder() {
plane={plane}
/>
<WallGroup />
<AislesGroup />
<MeasurementTool />

View File

@ -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") {

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 { 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,
};
};

View File

@ -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);
}
}
}
}

View File

@ -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<THREE.ShaderMaterial>(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;

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 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() {
}
<MaterialCollisionDetector />
{/* <MaterialCollisionDetector /> */}
</>
)

View File

@ -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<BuilderState>()(
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',

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];
outSideMaterial: string;
inSideMaterial: string;
thickness: number;
height: number;
wallThickness: number;
wallHeight: number;
}
type Walls = Wall[];
// Aisle

View File

@ -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
};