feat: enhance aisle management with new properties and types

This commit is contained in:
Jerald-Golden-B 2025-05-28 10:44:19 +05:30
parent 13ec906fac
commit cb414f2824
11 changed files with 296 additions and 148 deletions

View File

@ -16,6 +16,7 @@ import Simulations from "./simulation/Simulations";
import useVersionHistoryStore, {
useSaveVersion,
useSelectedFloorItem,
useToolMode,
} from "../../../store/builder/store";
import {
useSelectedEventData,
@ -26,10 +27,12 @@ import AsstePropertiies from "./properties/AssetProperties";
import ZoneProperties from "./properties/ZoneProperties";
import EventProperties from "./properties/eventProperties/EventProperties";
import VersionHistory from "./versionHisory/VersionHistory";
import AisleProperties from "./properties/AisleProperties";
const SideBarRight: React.FC = () => {
const { activeModule } = useModuleStore();
const { toggleUIRight } = useToggleStore();
const { toolMode } = useToolMode();
const { subModule, setSubModule } = useSubModuleStore();
const { selectedFloorItem } = useSelectedFloorItem();
const { selectedEventData } = useSelectedEventData();
@ -62,9 +65,8 @@ const SideBarRight: React.FC = () => {
return (
<div
className={`sidebar-right-wrapper ${
toggleUIRight && !isVersionSaved ? "open" : "closed"
}`}
className={`sidebar-right-wrapper ${toggleUIRight && !isVersionSaved ? "open" : "closed"
}`}
>
<Header />
{toggleUIRight && (
@ -74,9 +76,8 @@ const SideBarRight: React.FC = () => {
{activeModule !== "simulation" && (
<button
id="sidebar-action-list-properties"
className={`sidebar-action-list ${
subModule === "properties" ? "active" : ""
}`}
className={`sidebar-action-list ${subModule === "properties" ? "active" : ""
}`}
onClick={() => {
setSubModule("properties");
setVersionHistory(false);
@ -90,9 +91,8 @@ const SideBarRight: React.FC = () => {
<>
<button
id="sidebar-action-list-simulation"
className={`sidebar-action-list ${
subModule === "simulations" ? "active" : ""
}`}
className={`sidebar-action-list ${subModule === "simulations" ? "active" : ""
}`}
onClick={() => {
setSubModule("simulations");
setVersionHistory(false);
@ -103,9 +103,8 @@ const SideBarRight: React.FC = () => {
</button>
<button
id="sidebar-action-list-mechanics"
className={`sidebar-action-list ${
subModule === "mechanics" ? "active" : ""
}`}
className={`sidebar-action-list ${subModule === "mechanics" ? "active" : ""
}`}
onClick={() => {
setSubModule("mechanics");
setVersionHistory(false);
@ -116,9 +115,8 @@ const SideBarRight: React.FC = () => {
</button>
<button
id="sidebar-action-list-analysis"
className={`sidebar-action-list ${
subModule === "analysis" ? "active" : ""
}`}
className={`sidebar-action-list ${subModule === "analysis" ? "active" : ""
}`}
onClick={() => {
setSubModule("analysis");
setVersionHistory(false);
@ -147,7 +145,10 @@ const SideBarRight: React.FC = () => {
!selectedFloorItem && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
<GlobalProperties />
{toolMode === "Aisle" ? (
<AisleProperties />) : (
<GlobalProperties />
)}
</div>
</div>
)}

View File

@ -11,9 +11,10 @@ import Dashed from "../../../../assets/image/aisleTypes/Dashed.png";
import Directional from "../../../../assets/image/aisleTypes/Directional.png";
import Dotted from "../../../../assets/image/aisleTypes/Dotted.png";
import Solid from "../../../../assets/image/aisleTypes/Solid.png";
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
interface TextureItem {
color: string;
color: AisleColors;
id: string;
brief: string;
texture: string;
@ -22,10 +23,7 @@ interface TextureItem {
const AisleProperties: React.FC = () => {
const [collapsePresets, setCollapsePresets] = useState(false);
const [collapseTexture, setCollapseTexture] = useState(true);
const [selectedTexture, setSelectedTexture] = useState<string | null>(
"yellow1"
);
const [selectedType, setSelectedType] = useState<string | null>("Solid");
const { aisleType, aisleWidth, aisleColor, setAisleType, setAisleColor, setAisleWidth } = useBuilderStore();
const aisleTextureList: TextureItem[] = [
{ color: "gray", id: "gray", brief: "basic", texture: "" },
@ -60,58 +58,82 @@ const AisleProperties: React.FC = () => {
},
];
const aisleTypes = [
{
name: "Solid",
id: "1",
thumbnail: Solid,
},
{
name: "Dotted",
id: "2",
thumbnail: Dotted,
},
{
name: "Dashed",
id: "3",
thumbnail: Dashed,
},
{
name: "Arrow",
id: "4",
thumbnail: Arrow,
},
{
name: "Contiuous Arrows",
id: "5",
thumbnail: Arrows,
},
{
name: "Directional",
id: "6",
thumbnail: Directional,
},
{
name: "Arc",
id: "7",
thumbnail: Arc,
},
{
name: "Circle",
id: "8",
thumbnail: Circle,
},
];
const aisleTypes: {
name: string;
type: AisleTypes;
id: string;
thumbnail: string;
}[] = [
{
name: "Solid",
type: "solid-aisle",
id: "1",
thumbnail: Solid,
},
{
name: "Dotted",
type: "dotted-aisle",
id: "2",
thumbnail: Dotted,
},
{
name: "Dashed",
type: "dashed-aisle",
id: "3",
thumbnail: Dashed,
},
{
name: "Arrow",
type: "arrow-aisle",
id: "4",
thumbnail: Arrow,
},
{
name: "Contiuous Arrows",
type: "arrows-aisle",
id: "5",
thumbnail: Arrows,
},
{
name: "Directional",
type: "junction-aisle",
id: "6",
thumbnail: Directional,
},
{
name: "Arc",
type: "arc-aisle",
id: "7",
thumbnail: Arc,
},
{
name: "Circle",
type: "circle-aisle",
id: "8",
thumbnail: Circle,
},
];
const handleAisleWidthChange = (value: string) => {
const width = parseFloat(value);
if (isNaN(width)) {
return;
}
setAisleWidth(width);
}
return (
<div className="aisle-properties-container">
<div className="header">Properties</div>
<section>
<InputWithDropDown
label="Width"
value="1"
editableLabel
onChange={() => {}}
label="Aisle Width"
value={`${aisleWidth}`}
min={0.1}
step={0.1}
max={2}
defaultValue="0.1"
onChange={handleAisleWidthChange}
/>
</section>
@ -132,12 +154,11 @@ const AisleProperties: React.FC = () => {
{aisleTypes.map((val) => (
<div className="preset-list" key={val.id}>
<button
className={`thumbnail ${
selectedType === val.name ? "selected" : ""
}`}
className={`thumbnail ${aisleType === val.type ? "selected" : ""
}`}
title={val.name}
onClick={() => {
setSelectedType(val.name);
setAisleType(val.type);
}}
>
<img src={val.thumbnail} alt="" />
@ -170,11 +191,10 @@ const AisleProperties: React.FC = () => {
<button
key={val.id}
title={val.brief || val.id}
className={`aisle-list ${
selectedTexture === val.id ? "selected" : ""
}`}
onClick={() => setSelectedTexture(val.id)}
aria-pressed={selectedTexture === val.id}
className={`aisle-list ${aisleColor === val.color ? "selected" : ""
}`}
onClick={() => setAisleColor(val.color)}
aria-pressed={aisleColor === val.id}
>
<div className="texture-display">{val.texture}</div>
<div className="aisle-color">{val.color}</div>

View File

@ -1,4 +1,5 @@
import DashedAisle from './aisleTypes/dashedAisle';
import DottedAisle from './aisleTypes/dottedAisle';
import SolidAisle from './aisleTypes/solidAisle';
function AisleInstance({ aisle }: { readonly aisle: Aisle }) {
@ -12,6 +13,10 @@ function AisleInstance({ aisle }: { readonly aisle: Aisle }) {
{aisle.type.aisleType === 'dashed-aisle' && (
<DashedAisle aisle={aisle} />
)}
{aisle.type.aisleType === 'dotted-aisle' && (
<DottedAisle aisle={aisle} />
)}
</>
);
}

View File

@ -9,7 +9,7 @@ function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
const start = new THREE.Vector3(...aisle.points[0].position);
const end = new THREE.Vector3(...aisle.points[1].position);
const width = aisle.type.width || 0.1;
const width = aisle.type.aisleWidth || 0.1;
const dashLength = 0.5;
const gapLength = 0.3;
@ -59,7 +59,7 @@ function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
castShadow
>
<meshStandardMaterial
color={aisle.type.color || '#ffffff'}
color={aisle.type.aisleColor || '#ffffff'}
side={THREE.DoubleSide}
/>
</Extrude>

View File

@ -0,0 +1,58 @@
import * as THREE from 'three';
import { useMemo } from 'react';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
const shapes = useMemo(() => {
if (aisle.points.length < 2) return [];
const start = new THREE.Vector3(...aisle.points[0].position);
const end = new THREE.Vector3(...aisle.points[1].position);
const width = aisle.type.aisleWidth || 0.1;
const dotSpacing = 0.5;
const dotRadius = width * 0.4;
const totalLength = new THREE.Vector3().subVectors(end, start).length();
const dotCount = Math.floor(totalLength / dotSpacing);
const shapes = [];
const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize();
for (let i = 0; i < dotCount; i++) {
const dotCenter = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * dotSpacing + dotSpacing / 2);
const shape = new THREE.Shape();
shape.absarc(dotCenter.x, dotCenter.z, dotRadius, 0, Math.PI * 2, false);
shapes.push(shape);
}
return shapes;
}, [aisle]);
if (shapes.length === 0) return null;
return (
<group
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
rotation={[Math.PI / 2, 0, 0]}
>
{shapes.map((shape, index) => (
<Extrude
key={index}
args={[shape, { depth: 0.01, bevelEnabled: false }]}
receiveShadow
castShadow
>
<meshStandardMaterial
color={aisle.type.aisleColor || '#ffffff'}
side={THREE.DoubleSide}
/>
</Extrude>
))}
</group>
);
}
export default DottedAisle;

View File

@ -9,7 +9,7 @@ function SolidAisle({ aisle }: { readonly aisle: Aisle }) {
const start = new THREE.Vector3(...aisle.points[0].position);
const end = new THREE.Vector3(...aisle.points[1].position);
const width = aisle.type.width || 0.1;
const width = aisle.type.aisleWidth || 0.1;
const direction = new THREE.Vector3().subVectors(end, start).normalize();
const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize();
@ -42,7 +42,7 @@ function SolidAisle({ aisle }: { readonly aisle: Aisle }) {
castShadow
>
<meshStandardMaterial
color={aisle.type.color || '#ffffff'}
color={aisle.type.aisleColor || '#ffffff'}
side={THREE.DoubleSide}
/>
</Extrude>

View File

@ -3,8 +3,8 @@ import { useEffect, useMemo, useState } from 'react'
import { useThree } from '@react-three/fiber';
import { useActiveLayer, useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store';
import { useAisleStore } from '../../../../store/builder/useAisleStore';
import * as Constants from '../../../../types/world/worldConstants';
import ReferenceAisle from './referenceAisle';
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
function AisleCreator() {
const { scene, camera, raycaster, gl, pointer } = useThree();
@ -17,14 +17,14 @@ function AisleCreator() {
const [tempPoints, setTempPoints] = useState<Point[]>([]);
const [isCreating, setIsCreating] = useState(false);
const [aisleType, setAisleType] = useState<'solid-aisle' | 'dashed-aisle' | 'stripped-aisle' | 'dotted-aisle' | 'arrow-aisle' | 'arrows-aisle' | 'arc-aisle' | 'circle-aisle' | 'junction-aisle'>('dashed-aisle');
const { aisleType, aisleWidth, aisleColor } = useBuilderStore();
useEffect(() => {
if (tempPoints.length > 0) {
setTempPoints([]);
setIsCreating(false);
}
}, [aisleType]);
// useEffect(() => {
// if (tempPoints.length > 0) {
// setTempPoints([]);
// setIsCreating(false);
// }
// }, [aisleType]);
const allPoints = useMemo(() => {
const points: Point[] = [];
@ -102,10 +102,9 @@ function AisleCreator() {
points: [tempPoints[0], newPoint],
type: {
typeName: 'Aisle',
material: 'default',
aisleType: aisleType,
color: Constants.aisleConfig.defaultColor,
width: Constants.aisleConfig.width
aisleColor: aisleColor,
aisleWidth: aisleWidth
}
};
@ -114,33 +113,7 @@ function AisleCreator() {
setTempPoints([newPoint]);
}
} else if (['arc-aisle', 'circle-aisle', 'arrow-aisle', 'junction-aisle'].includes(aisleType)) {
const newPoint: Point = {
uuid: THREE.MathUtils.generateUUID(),
position: [point.x, point.y, point.z],
layer: activeLayer
};
if (tempPoints.length === 0) {
setTempPoints([newPoint]);
setIsCreating(true);
} else {
const aisle: Aisle = {
uuid: THREE.MathUtils.generateUUID(),
points: [tempPoints[0], newPoint],
type: {
typeName: 'Aisle',
material: 'default',
aisleType: aisleType,
color: Constants.aisleConfig.defaultColor,
width: Constants.aisleConfig.width
}
};
addAisle(aisle);
setTempPoints([]);
setIsCreating(false);
}
console.log();
}
};
@ -170,7 +143,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]);
}, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addAisle, aisleType, aisleColor, aisleWidth]);
return (
<>
@ -187,8 +160,8 @@ function AisleCreator() {
</mesh>
))}
</group>
<ReferenceAisle tempPoints={tempPoints} aisleType={aisleType} />
<ReferenceAisle tempPoints={tempPoints} aisleType={aisleType} aisleWidth={aisleWidth} aisleColor={aisleColor} />
</>
);
}

View File

@ -7,10 +7,12 @@ import { Extrude } from '@react-three/drei';
interface ReferenceAisleProps {
tempPoints: Point[];
aisleType: 'solid-aisle' | 'dashed-aisle' | 'stripped-aisle' | 'dotted-aisle' | 'arrow-aisle' | 'arrows-aisle' | 'arc-aisle' | 'circle-aisle' | 'junction-aisle';
aisleType: AisleTypes;
aisleWidth: number;
aisleColor: AisleColors;
}
function ReferenceAisle({ tempPoints, aisleType }: Readonly<ReferenceAisleProps>) {
function ReferenceAisle({ tempPoints, aisleType, aisleWidth, aisleColor }: Readonly<ReferenceAisleProps>) {
const { pointer, raycaster, camera } = useThree();
const { toolMode } = useToolMode();
const { toggleView } = useToggleView();
@ -41,10 +43,9 @@ function ReferenceAisle({ tempPoints, aisleType }: Readonly<ReferenceAisleProps>
],
type: {
typeName: 'Aisle',
material: 'default',
aisleType: aisleType,
color: Constants.aisleConfig.defaultColor,
width: Constants.aisleConfig.width
aisleColor: aisleColor,
aisleWidth: aisleWidth
}
});
}
@ -55,7 +56,7 @@ function ReferenceAisle({ tempPoints, aisleType }: Readonly<ReferenceAisleProps>
useEffect(() => {
setTempAisle(null);
}, [toolMode, toggleView, tempPoints.length, aisleType]);
}, [toolMode, toggleView, tempPoints.length, aisleType, aisleWidth, aisleColor]);
if (!tempAisle) return null;
@ -65,6 +66,8 @@ function ReferenceAisle({ tempPoints, aisleType }: Readonly<ReferenceAisleProps>
return <SolidAisle aisle={tempAisle} />;
case 'dashed-aisle':
return <DashedAisle aisle={tempAisle} />;
case 'dotted-aisle':
return <DottedAisle aisle={tempAisle} />;
default:
return null;
}
@ -79,14 +82,13 @@ function ReferenceAisle({ tempPoints, aisleType }: Readonly<ReferenceAisleProps>
export default ReferenceAisle;
function SolidAisle({ aisle }: { readonly aisle: Aisle }) {
const shape = useMemo(() => {
if (aisle.points.length < 2) return null;
const start = new THREE.Vector3(...aisle.points[0].position);
const end = new THREE.Vector3(...aisle.points[1].position);
const width = aisle.type.width || 0.1;
const width = aisle.type.aisleWidth || 0.1;
const direction = new THREE.Vector3().subVectors(end, start).normalize();
const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize();
@ -119,7 +121,7 @@ function SolidAisle({ aisle }: { readonly aisle: Aisle }) {
castShadow
>
<meshStandardMaterial
color={aisle.type.color || '#ffffff'}
color={aisle.type.aisleColor || '#ffffff'}
side={THREE.DoubleSide}
/>
</Extrude>
@ -133,7 +135,7 @@ function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
const start = new THREE.Vector3(...aisle.points[0].position);
const end = new THREE.Vector3(...aisle.points[1].position);
const width = aisle.type.width || 0.1;
const width = aisle.type.aisleWidth || 0.1;
const dashLength = 0.5;
const gapLength = 0.3;
@ -183,7 +185,7 @@ function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
castShadow
>
<meshStandardMaterial
color={aisle.type.color || '#ffffff'}
color={aisle.type.aisleColor || '#ffffff'}
side={THREE.DoubleSide}
/>
</Extrude>
@ -191,3 +193,55 @@ function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
</group>
);
}
function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
const shapes = useMemo(() => {
if (aisle.points.length < 2) return [];
const start = new THREE.Vector3(...aisle.points[0].position);
const end = new THREE.Vector3(...aisle.points[1].position);
const width = aisle.type.aisleWidth || 0.1;
const dotSpacing = 0.5;
const dotRadius = width * 0.4;
const totalLength = new THREE.Vector3().subVectors(end, start).length();
const dotCount = Math.floor(totalLength / dotSpacing);
const shapes = [];
const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize();
for (let i = 0; i < dotCount; i++) {
const dotCenter = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * dotSpacing + dotSpacing / 2);
const shape = new THREE.Shape();
shape.absarc(dotCenter.x, dotCenter.z, dotRadius, 0, Math.PI * 2, false);
shapes.push(shape);
}
return shapes;
}, [aisle]);
if (shapes.length === 0) return null;
return (
<group
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
rotation={[Math.PI / 2, 0, 0]}
>
{shapes.map((shape, index) => (
<Extrude
key={index}
args={[shape, { depth: 0.01, bevelEnabled: false }]}
receiveShadow
castShadow
>
<meshStandardMaterial
color={aisle.type.aisleColor || '#ffffff'}
side={THREE.DoubleSide}
/>
</Extrude>
))}
</group>
);
}

View File

@ -9,8 +9,7 @@ interface AisleStore {
removeAisle: (uuid: string) => void;
setPosition: (pointUuid: string, position: [number, number, number]) => void;
setLayer: (pointUuid: string, layer: number) => void;
setMaterial: (aisleUuid: string, material: string) => void;
setColor: (aisleUuid: string, color: string) => void;
setColor: (aisleUuid: string, color: AisleColors) => void;
setWidth: (aisleUuid: string, width: number) => void;
getAisleById: (uuid: string) => Aisle | undefined;
}
@ -58,24 +57,17 @@ export const useAisleStore = create<AisleStore>()(
}
}),
setMaterial: (aisleUuid: string, material: string) => set((state) => {
setColor: (aisleUuid: string, color: AisleColors) => set((state) => {
const aisle = state.aisles.find(a => a.uuid === aisleUuid);
if (aisle) {
aisle.type.material = material;
}
}),
setColor: (aisleUuid: string, color: string) => set((state) => {
const aisle = state.aisles.find(a => a.uuid === aisleUuid);
if (aisle) {
aisle.type.color = color;
aisle.type.aisleColor = color;
}
}),
setWidth: (aisleUuid: string, width: number) => set((state) => {
const aisle = state.aisles.find(a => a.uuid === aisleUuid);
if (aisle) {
aisle.type.width = width;
aisle.type.aisleWidth = width;
}
}),

View File

@ -0,0 +1,42 @@
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
interface BuilderState {
aisleType: AisleTypes;
aisleWidth: number;
aisleColor: AisleColors;
setAisleType: (type: AisleTypes) => void;
setAisleWidth: (width: number) => void;
setAisleColor: (color: AisleColors) => void;
setAisleProperties: (type: AisleTypes, width: number, color: AisleColors) => void;
}
export const useBuilderStore = create<BuilderState>()(
immer((set) => ({
aisleType: 'solid-aisle',
aisleWidth: 0.1,
aisleColor: 'gray',
setAisleType: (type) => {
set((state) => {
state.aisleType = type;
});
},
setAisleWidth: (width) => {
set((state) => {
state.aisleWidth = width;
});
},
setAisleColor: (color) => {
set((state) => {
state.aisleColor = color;
});
},
setAisleProperties: (type, width, color) => {
set((state) => {
state.aisleType = type;
state.aisleWidth = width;
state.aisleColor = color;
});
}
}))
);

View File

@ -37,12 +37,15 @@ interface Point {
layer: number;
}
type AisleTypes = | 'solid-aisle' | 'dashed-aisle' | 'stripped-aisle' | 'dotted-aisle' | 'arrow-aisle' | 'arrows-aisle' | 'arc-aisle' | 'circle-aisle' | 'junction-aisle';
type AisleColors = | 'gray' | 'yellow' | 'green' | 'orange' | 'blue' | 'purple' | 'red' | 'bright green' | 'yellow-black' | 'white-black'
interface AisleType {
typeName: 'Aisle';
material: string;
aisleType: 'solid-aisle' | 'dashed-aisle' | 'stripped-aisle' | 'dotted-aisle' | 'arrow-aisle'| 'arrows-aisle' | 'arc-aisle' | 'circle-aisle' | 'junction-aisle';
color: string;
width: number;
aisleType: AisleTypes;
aisleColor: AisleColors;
aisleWidth: number;
}
interface Aisle {