feat: implement aisle management features with creator and instances
This commit is contained in:
24
app/src/modules/builder/aisle/Instances/aisleInstances.tsx
Normal file
24
app/src/modules/builder/aisle/Instances/aisleInstances.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useAisleStore } from '../../../../store/builder/useAisleStore';
|
||||
import AisleInstance from './instance/aisleInstance';
|
||||
|
||||
function AisleInstances() {
|
||||
const { aisles } = useAisleStore();
|
||||
|
||||
useEffect(() => {
|
||||
console.log('aisles: ', aisles);
|
||||
}, [aisles]);
|
||||
|
||||
return (
|
||||
|
||||
<>
|
||||
|
||||
{aisles.map((aisle) =>
|
||||
<AisleInstance aisle={aisle} key={aisle.uuid} />
|
||||
)}
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AisleInstances
|
||||
@@ -0,0 +1,19 @@
|
||||
import DashedAisle from './aisleTypes/dashedAisle';
|
||||
import SolidAisle from './aisleTypes/solidAisle';
|
||||
|
||||
function AisleInstance({ aisle }: { readonly aisle: Aisle }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{aisle.type.aisleType === 'solid-aisle' && (
|
||||
<SolidAisle aisle={aisle} />
|
||||
)}
|
||||
|
||||
{aisle.type.aisleType === 'dashed-aisle' && (
|
||||
<DashedAisle aisle={aisle} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default AisleInstance;
|
||||
@@ -0,0 +1,71 @@
|
||||
import * as THREE from 'three';
|
||||
import { useMemo } from 'react';
|
||||
import { Extrude } from '@react-three/drei';
|
||||
import * as Constants from '../../../../../../types/world/worldConstants';
|
||||
|
||||
function DashedAisle({ 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.width || 0.1;
|
||||
const dashLength = 0.5;
|
||||
const gapLength = 0.3;
|
||||
|
||||
const direction = new THREE.Vector3().subVectors(end, start).normalize();
|
||||
const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize();
|
||||
|
||||
const totalLength = new THREE.Vector3().subVectors(end, start).length();
|
||||
const segmentCount = Math.floor(totalLength / (dashLength + gapLength));
|
||||
|
||||
const shapes = [];
|
||||
const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize();
|
||||
|
||||
for (let i = 0; i < segmentCount; i++) {
|
||||
const segmentStart = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * (dashLength + gapLength));
|
||||
const segmentEnd = new THREE.Vector3().copy(segmentStart).addScaledVector(directionNormalized, dashLength);
|
||||
|
||||
const leftStart = new THREE.Vector3().copy(segmentStart).addScaledVector(perp, width / 2);
|
||||
const rightStart = new THREE.Vector3().copy(segmentStart).addScaledVector(perp, -width / 2);
|
||||
const leftEnd = new THREE.Vector3().copy(segmentEnd).addScaledVector(perp, width / 2);
|
||||
const rightEnd = new THREE.Vector3().copy(segmentEnd).addScaledVector(perp, -width / 2);
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
shape.moveTo(leftStart.x, leftStart.z);
|
||||
shape.lineTo(leftEnd.x, leftEnd.z);
|
||||
shape.lineTo(rightEnd.x, rightEnd.z);
|
||||
shape.lineTo(rightStart.x, rightStart.z);
|
||||
shape.closePath();
|
||||
|
||||
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.color || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashedAisle;
|
||||
@@ -0,0 +1,53 @@
|
||||
import * as THREE from 'three';
|
||||
import { useMemo } from 'react';
|
||||
import { Extrude } from '@react-three/drei';
|
||||
import * as Constants from '../../../../../../types/world/worldConstants';
|
||||
|
||||
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 direction = new THREE.Vector3().subVectors(end, start).normalize();
|
||||
const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize();
|
||||
|
||||
const leftStart = new THREE.Vector3().copy(start).addScaledVector(perp, width / 2);
|
||||
const rightStart = new THREE.Vector3().copy(start).addScaledVector(perp, -width / 2);
|
||||
const leftEnd = new THREE.Vector3().copy(end).addScaledVector(perp, width / 2);
|
||||
const rightEnd = new THREE.Vector3().copy(end).addScaledVector(perp, -width / 2);
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
shape.moveTo(leftStart.x, leftStart.z);
|
||||
shape.lineTo(leftEnd.x, leftEnd.z);
|
||||
shape.lineTo(rightEnd.x, rightEnd.z);
|
||||
shape.lineTo(rightStart.x, rightStart.z);
|
||||
shape.closePath();
|
||||
|
||||
return shape;
|
||||
}, [aisle]);
|
||||
|
||||
if (!shape) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
>
|
||||
<Extrude
|
||||
args={[shape, { depth: 0.01, bevelEnabled: false }]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.color || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default SolidAisle;
|
||||
196
app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx
Normal file
196
app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx
Normal file
@@ -0,0 +1,196 @@
|
||||
import * as THREE from 'three'
|
||||
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';
|
||||
|
||||
function AisleCreator() {
|
||||
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 { aisles, addAisle } = useAisleStore();
|
||||
|
||||
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');
|
||||
|
||||
useEffect(() => {
|
||||
if (tempPoints.length > 0) {
|
||||
setTempPoints([]);
|
||||
setIsCreating(false);
|
||||
}
|
||||
}, [aisleType]);
|
||||
|
||||
const allPoints = useMemo(() => {
|
||||
const points: Point[] = [];
|
||||
const seenUuids = new Set<string>();
|
||||
|
||||
// Add points from existing aisles
|
||||
aisles.forEach(aisle => {
|
||||
aisle.points.forEach(point => {
|
||||
if (!seenUuids.has(point.uuid)) {
|
||||
seenUuids.add(point.uuid);
|
||||
points.push(point);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add temporary points
|
||||
tempPoints.forEach(point => {
|
||||
if (!seenUuids.has(point.uuid)) {
|
||||
seenUuids.add(point.uuid);
|
||||
points.push(point);
|
||||
}
|
||||
});
|
||||
|
||||
return points;
|
||||
}, [aisles, tempPoints]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const canvasElement = gl.domElement;
|
||||
|
||||
let drag = false;
|
||||
let isLeftMouseDown = false;
|
||||
|
||||
const onMouseDown = (evt: any) => {
|
||||
if (evt.button === 0) {
|
||||
isLeftMouseDown = true;
|
||||
drag = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseUp = (evt: any) => {
|
||||
if (evt.button === 0) {
|
||||
isLeftMouseDown = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseMove = () => {
|
||||
if (isLeftMouseDown) {
|
||||
drag = true;
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseClick = () => {
|
||||
if (drag || !toggleView) return;
|
||||
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersectionPoint = new THREE.Vector3();
|
||||
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
|
||||
if (!point) return;
|
||||
|
||||
if (['solid-aisle', 'dashed-aisle', 'stripped-aisle', 'dotted-aisle', 'arrows-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([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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onContext = (event: any) => {
|
||||
event.preventDefault();
|
||||
if (isCreating) {
|
||||
setTempPoints([]);
|
||||
setIsCreating(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (toolMode === "Aisle" && toggleView) {
|
||||
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||
canvasElement.addEventListener("click", onMouseClick);
|
||||
canvasElement.addEventListener("contextmenu", onContext);
|
||||
} else {
|
||||
setTempPoints([]);
|
||||
setIsCreating(false);
|
||||
}
|
||||
|
||||
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, addAisle, aisleType]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<group >
|
||||
{allPoints.map((point) => (
|
||||
<mesh
|
||||
key={point.uuid}
|
||||
position={new THREE.Vector3(...point.position)}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshBasicMaterial
|
||||
color={0xffff00}
|
||||
/>
|
||||
</mesh>
|
||||
))}
|
||||
</group>
|
||||
|
||||
<ReferenceAisle tempPoints={tempPoints} aisleType={aisleType} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default AisleCreator;
|
||||
193
app/src/modules/builder/aisle/aisleCreator/referenceAisle.tsx
Normal file
193
app/src/modules/builder/aisle/aisleCreator/referenceAisle.tsx
Normal file
@@ -0,0 +1,193 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import { useActiveLayer, useToolMode, useToggleView } from '../../../../store/builder/store';
|
||||
import * as Constants from '../../../../types/world/worldConstants';
|
||||
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';
|
||||
}
|
||||
|
||||
function ReferenceAisle({ tempPoints, aisleType }: Readonly<ReferenceAisleProps>) {
|
||||
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 [tempAisle, setTempAisle] = useState<Aisle | null>(null);
|
||||
const mousePosRef = useRef<THREE.Vector3>(new THREE.Vector3());
|
||||
|
||||
useFrame(() => {
|
||||
if (toolMode === "Aisle" && toggleView && tempPoints.length === 1) {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersectionPoint = new THREE.Vector3();
|
||||
raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
|
||||
if (intersectionPoint) {
|
||||
mousePosRef.current.copy(intersectionPoint);
|
||||
|
||||
setTempAisle({
|
||||
uuid: 'temp-aisle',
|
||||
points: [
|
||||
tempPoints[0],
|
||||
{
|
||||
uuid: 'temp-point',
|
||||
position: [mousePosRef.current.x, mousePosRef.current.y, mousePosRef.current.z],
|
||||
layer: activeLayer
|
||||
}
|
||||
],
|
||||
type: {
|
||||
typeName: 'Aisle',
|
||||
material: 'default',
|
||||
aisleType: aisleType,
|
||||
color: Constants.aisleConfig.defaultColor,
|
||||
width: Constants.aisleConfig.width
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (tempAisle !== null) {
|
||||
setTempAisle(null);
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setTempAisle(null);
|
||||
}, [toolMode, toggleView, tempPoints.length, aisleType]);
|
||||
|
||||
if (!tempAisle) return null;
|
||||
|
||||
const renderAisle = () => {
|
||||
switch (aisleType) {
|
||||
case 'solid-aisle':
|
||||
return <SolidAisle aisle={tempAisle} />;
|
||||
case 'dashed-aisle':
|
||||
return <DashedAisle aisle={tempAisle} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<group>
|
||||
{renderAisle()}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
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 direction = new THREE.Vector3().subVectors(end, start).normalize();
|
||||
const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize();
|
||||
|
||||
const leftStart = new THREE.Vector3().copy(start).addScaledVector(perp, width / 2);
|
||||
const rightStart = new THREE.Vector3().copy(start).addScaledVector(perp, -width / 2);
|
||||
const leftEnd = new THREE.Vector3().copy(end).addScaledVector(perp, width / 2);
|
||||
const rightEnd = new THREE.Vector3().copy(end).addScaledVector(perp, -width / 2);
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
shape.moveTo(leftStart.x, leftStart.z);
|
||||
shape.lineTo(leftEnd.x, leftEnd.z);
|
||||
shape.lineTo(rightEnd.x, rightEnd.z);
|
||||
shape.lineTo(rightStart.x, rightStart.z);
|
||||
shape.closePath();
|
||||
|
||||
return shape;
|
||||
}, [aisle]);
|
||||
|
||||
if (!shape) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
>
|
||||
<Extrude
|
||||
args={[shape, { depth: 0.01, bevelEnabled: false }]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.color || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
function DashedAisle({ 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.width || 0.1;
|
||||
const dashLength = 0.5;
|
||||
const gapLength = 0.3;
|
||||
|
||||
const direction = new THREE.Vector3().subVectors(end, start).normalize();
|
||||
const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize();
|
||||
|
||||
const totalLength = new THREE.Vector3().subVectors(end, start).length();
|
||||
const segmentCount = Math.floor(totalLength / (dashLength + gapLength));
|
||||
|
||||
const shapes = [];
|
||||
const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize();
|
||||
|
||||
for (let i = 0; i < segmentCount; i++) {
|
||||
const segmentStart = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * (dashLength + gapLength));
|
||||
const segmentEnd = new THREE.Vector3().copy(segmentStart).addScaledVector(directionNormalized, dashLength);
|
||||
|
||||
const leftStart = new THREE.Vector3().copy(segmentStart).addScaledVector(perp, width / 2);
|
||||
const rightStart = new THREE.Vector3().copy(segmentStart).addScaledVector(perp, -width / 2);
|
||||
const leftEnd = new THREE.Vector3().copy(segmentEnd).addScaledVector(perp, width / 2);
|
||||
const rightEnd = new THREE.Vector3().copy(segmentEnd).addScaledVector(perp, -width / 2);
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
shape.moveTo(leftStart.x, leftStart.z);
|
||||
shape.lineTo(leftEnd.x, leftEnd.z);
|
||||
shape.lineTo(rightEnd.x, rightEnd.z);
|
||||
shape.lineTo(rightStart.x, rightStart.z);
|
||||
shape.closePath();
|
||||
|
||||
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.color || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
21
app/src/modules/builder/aisle/aislesGroup.tsx
Normal file
21
app/src/modules/builder/aisle/aislesGroup.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
import AisleCreator from './aisleCreator/aisleCreator'
|
||||
import AisleInstances from './Instances/aisleInstances'
|
||||
|
||||
function AislesGroup() {
|
||||
|
||||
return (
|
||||
|
||||
<>
|
||||
|
||||
<AisleCreator />
|
||||
|
||||
|
||||
<AisleInstances />
|
||||
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default AislesGroup
|
||||
@@ -4,18 +4,17 @@ import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
|
||||
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||
import { ThreeEvent, useFrame, useThree } from '@react-three/fiber';
|
||||
import { useActiveTool, useDeletableFloorItem, useRenderDistance, useSelectedFloorItem } from '../../../../../store/builder/store';
|
||||
import { useActiveTool, useDeletableFloorItem, useRenderDistance, useSelectedFloorItem, useSocketStore } from '../../../../../store/builder/store';
|
||||
import { AssetBoundingBox } from '../../functions/assetBoundingBox';
|
||||
import { CameraControls } from '@react-three/drei';
|
||||
import { useAssetsStore } from '../../../../../store/builder/useAssetStore';
|
||||
import { useEventsStore } from "../../../../../store/simulation/useEventsStore";
|
||||
import { useProductStore } from "../../../../../store/simulation/useProductStore";
|
||||
import { useSocketStore } from '../../../../../store/builder/store';
|
||||
import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore';
|
||||
import { useLeftData, useTopData } from '../../../../../store/visualization/useZone3DWidgetStore';
|
||||
import { useSelectedAsset, useSelectedProduct } from '../../../../../store/simulation/useSimulationStore';
|
||||
|
||||
function Model({ asset }: { asset: Asset }) {
|
||||
function Model({ asset }: { readonly asset: Asset }) {
|
||||
const { camera, controls, gl } = useThree();
|
||||
const { activeTool } = useActiveTool();
|
||||
const { subModule } = useSubModuleStore();
|
||||
@@ -46,7 +45,8 @@ function Model({ asset }: { asset: Asset }) {
|
||||
const loadModel = async () => {
|
||||
try {
|
||||
// Check Cache
|
||||
const cachedModel = THREE.Cache.get(asset.assetId!);
|
||||
const assetId = asset.assetId;
|
||||
const cachedModel = THREE.Cache.get(assetId);
|
||||
if (cachedModel) {
|
||||
setGltfScene(cachedModel);
|
||||
calculateBoundingBox(cachedModel.scene);
|
||||
@@ -54,13 +54,13 @@ function Model({ asset }: { asset: Asset }) {
|
||||
}
|
||||
|
||||
// Check IndexedDB
|
||||
const indexedDBModel = await retrieveGLTF(asset.assetId!);
|
||||
const indexedDBModel = await retrieveGLTF(assetId);
|
||||
if (indexedDBModel) {
|
||||
const blobUrl = URL.createObjectURL(indexedDBModel);
|
||||
loader.load(blobUrl, (gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(asset.assetId!, gltf);
|
||||
THREE.Cache.add(assetId, gltf);
|
||||
setGltfScene(gltf);
|
||||
calculateBoundingBox(gltf.scene);
|
||||
},
|
||||
@@ -74,14 +74,22 @@ function Model({ asset }: { asset: Asset }) {
|
||||
}
|
||||
|
||||
// Fetch from Backend
|
||||
const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${asset.assetId!}`;
|
||||
loader.load(modelUrl, async (gltf) => {
|
||||
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
||||
await storeGLTF(asset.assetId!, modelBlob);
|
||||
THREE.Cache.add(asset.assetId!, gltf);
|
||||
setGltfScene(gltf);
|
||||
calculateBoundingBox(gltf.scene);
|
||||
},
|
||||
const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${assetId}`;
|
||||
const handleBackendLoad = async (gltf: GLTF) => {
|
||||
try {
|
||||
const response = await fetch(modelUrl);
|
||||
const modelBlob = await response.blob();
|
||||
await storeGLTF(assetId, modelBlob);
|
||||
THREE.Cache.add(assetId, gltf);
|
||||
setGltfScene(gltf);
|
||||
calculateBoundingBox(gltf.scene);
|
||||
} catch (error) {
|
||||
console.error(`[Backend] Error storing/loading ${asset.modelName}:`, error);
|
||||
}
|
||||
};
|
||||
loader.load(
|
||||
modelUrl,
|
||||
handleBackendLoad,
|
||||
undefined,
|
||||
(error) => {
|
||||
echo.error(`[Backend] Error loading ${asset.modelName}:`);
|
||||
@@ -130,7 +138,7 @@ function Model({ asset }: { asset: Asset }) {
|
||||
true
|
||||
);
|
||||
(controls as CameraControls).setTarget(center.x, center.y, center.z, true);
|
||||
(controls as CameraControls).fitToBox(groupRef.current!, true, {
|
||||
(controls as CameraControls).fitToBox(groupRef.current, true, {
|
||||
cover: true,
|
||||
paddingTop: 5,
|
||||
paddingLeft: 5,
|
||||
@@ -191,6 +199,8 @@ function Model({ asset }: { asset: Asset }) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const handleContextMenu = (asset: Asset, evt: ThreeEvent<MouseEvent>) => {
|
||||
if (activeTool === "cursor" && subModule === 'simulations') {
|
||||
if (asset.modelUuid) {
|
||||
@@ -215,7 +225,6 @@ function Model({ asset }: { asset: Asset }) {
|
||||
const canvasRect = canvasElement.getBoundingClientRect();
|
||||
const relativeX = evt.clientX - canvasRect.left;
|
||||
const relativeY = evt.clientY - canvasRect.top;
|
||||
|
||||
setTop(relativeY);
|
||||
setLeft(relativeX);
|
||||
} else {
|
||||
|
||||
@@ -48,6 +48,7 @@ import CalculateAreaGroup from "./groups/calculateAreaGroup";
|
||||
import LayoutImage from "./layout/layoutImage";
|
||||
import AssetsGroup from "./asset/assetsGroup";
|
||||
import { Bvh } from "@react-three/drei";
|
||||
import AislesGroup from "./aisle/aislesGroup";
|
||||
|
||||
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.
|
||||
@@ -274,7 +275,7 @@ export default function Builder() {
|
||||
|
||||
<ZoneGroup />
|
||||
|
||||
<FloorGroupAilse
|
||||
{/* <FloorGroupAilse
|
||||
floorGroupAisle={floorGroupAisle}
|
||||
plane={plane}
|
||||
floorPlanGroupLine={floorPlanGroupLine}
|
||||
@@ -292,13 +293,15 @@ export default function Builder() {
|
||||
isSnappedUUID={isSnappedUUID}
|
||||
isAngleSnapped={isAngleSnapped}
|
||||
anglesnappedPoint={anglesnappedPoint}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<AssetsGroup
|
||||
floorGroup={floorGroup}
|
||||
plane={plane}
|
||||
/>
|
||||
|
||||
<AislesGroup />
|
||||
|
||||
<MeasurementTool />
|
||||
|
||||
<CalculateAreaGroup />
|
||||
|
||||
@@ -28,7 +28,7 @@ export const handleAddEventToProduct = ({
|
||||
organization: organization,
|
||||
eventDatas: event
|
||||
}).then((data) => {
|
||||
console.log(data);
|
||||
// console.log(data);
|
||||
})
|
||||
|
||||
if (clearSelectedAsset) {
|
||||
|
||||
86
app/src/store/builder/useAisleStore.ts
Normal file
86
app/src/store/builder/useAisleStore.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { create } from 'zustand';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
|
||||
interface AisleStore {
|
||||
aisles: Aisles;
|
||||
setAisles: (aisles: Aisles) => void;
|
||||
addAisle: (aisle: Aisle) => void;
|
||||
updateAisle: (uuid: string, updated: Partial<Aisle>) => void;
|
||||
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;
|
||||
setWidth: (aisleUuid: string, width: number) => void;
|
||||
getAisleById: (uuid: string) => Aisle | undefined;
|
||||
}
|
||||
|
||||
export const useAisleStore = create<AisleStore>()(
|
||||
immer((set, get) => ({
|
||||
aisles: [],
|
||||
|
||||
setAisles: (aisles) => set((state) => {
|
||||
state.aisles = aisles;
|
||||
}),
|
||||
|
||||
addAisle: (aisle) => set((state) => {
|
||||
state.aisles.push(aisle);
|
||||
}),
|
||||
|
||||
updateAisle: (uuid, updated) => set((state) => {
|
||||
const aisle = state.aisles.find((a) => a.uuid === uuid);
|
||||
if (aisle) {
|
||||
Object.assign(aisle, updated);
|
||||
}
|
||||
}),
|
||||
|
||||
removeAisle: (uuid) => set((state) => {
|
||||
state.aisles = state.aisles.filter((a) => a.uuid !== uuid);
|
||||
}),
|
||||
|
||||
setPosition: (pointUuid: string, position: [number, number, number]) => set((state) => {
|
||||
for (const aisle of state.aisles) {
|
||||
const point = aisle.points.find(p => p.uuid === pointUuid);
|
||||
if (point) {
|
||||
point.position = position;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
setLayer: (pointUuid: string, layer: number) => set((state) => {
|
||||
for (const aisle of state.aisles) {
|
||||
const point = aisle.points.find(p => p.uuid === pointUuid);
|
||||
if (point) {
|
||||
point.layer = layer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
setMaterial: (aisleUuid: string, material: string) => 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;
|
||||
}
|
||||
}),
|
||||
|
||||
setWidth: (aisleUuid: string, width: number) => set((state) => {
|
||||
const aisle = state.aisles.find(a => a.uuid === aisleUuid);
|
||||
if (aisle) {
|
||||
aisle.type.width = width;
|
||||
}
|
||||
}),
|
||||
|
||||
getAisleById: (uuid) => {
|
||||
return get().aisles.find((a) => a.uuid === uuid);
|
||||
},
|
||||
}))
|
||||
);
|
||||
25
app/src/types/builderTypes.d.ts
vendored
25
app/src/types/builderTypes.d.ts
vendored
@@ -28,4 +28,27 @@ interface Asset {
|
||||
}
|
||||
};
|
||||
|
||||
type Assets = Asset[];
|
||||
type Assets = Asset[];
|
||||
|
||||
|
||||
interface Point {
|
||||
uuid: string;
|
||||
position: [number, number, number];
|
||||
layer: number;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
interface Aisle {
|
||||
uuid: string;
|
||||
points: [Point, Point];
|
||||
type: AisleType;
|
||||
}
|
||||
|
||||
type Aisles = Aisle[];
|
||||
@@ -158,7 +158,7 @@ export type RoofConfig = {
|
||||
export type AisleConfig = {
|
||||
width: number;
|
||||
height: number;
|
||||
defaultColor: number;
|
||||
defaultColor: string;
|
||||
};
|
||||
|
||||
export type ZoneConfig = {
|
||||
@@ -345,7 +345,7 @@ export const roofConfig: RoofConfig = {
|
||||
export const aisleConfig: AisleConfig = {
|
||||
width: 0.1, // Width of the aisles
|
||||
height: 0.01, // Height of the aisles
|
||||
defaultColor: 0xE2AC09, // Default color of the aisles
|
||||
defaultColor: '#E2AC09', // Default color of the aisles
|
||||
};
|
||||
|
||||
export const zoneConfig: ZoneConfig = {
|
||||
|
||||
Reference in New Issue
Block a user