first commit
This commit is contained in:
81
app/src/modules/builder/aisle/Instances/aisleInstances.tsx
Normal file
81
app/src/modules/builder/aisle/Instances/aisleInstances.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useAisleStore } from '../../../../store/builder/useAisleStore';
|
||||
import { useToggleView } from '../../../../store/builder/store';
|
||||
import AisleInstance from './instance/aisleInstance';
|
||||
import Point from '../../point/point';
|
||||
import { Html } from '@react-three/drei';
|
||||
import { Vector3 } from 'three';
|
||||
|
||||
function AisleInstances() {
|
||||
const { aisles } = useAisleStore();
|
||||
const { toggleView } = useToggleView();
|
||||
|
||||
const allPoints = useMemo(() => {
|
||||
const points: Point[] = [];
|
||||
const seenUuids = new Set<string>();
|
||||
|
||||
aisles.forEach(aisle => {
|
||||
aisle.points.forEach(point => {
|
||||
if (!seenUuids.has(point.pointUuid)) {
|
||||
seenUuids.add(point.pointUuid);
|
||||
points.push(point);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return points;
|
||||
}, [aisles]);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{toggleView &&
|
||||
<group name='Aisle-Points-Group'>
|
||||
{allPoints.map((point) => (
|
||||
<Point key={point.pointUuid} point={point} />
|
||||
))}
|
||||
</group>
|
||||
}
|
||||
|
||||
<group name='Aisles-Group'>
|
||||
{aisles.map((aisle) => {
|
||||
const textPosition = new Vector3().addVectors(new Vector3(...aisle.points[0].position), new Vector3(...aisle.points[1].position)).divideScalar(2);
|
||||
const distance = new Vector3(...aisle.points[0].position).distanceTo(new Vector3(...aisle.points[1].position));
|
||||
|
||||
return (
|
||||
< React.Fragment key={aisle.aisleUuid}>
|
||||
<AisleInstance aisle={aisle} key={aisle.aisleUuid} />
|
||||
|
||||
{toggleView &&
|
||||
<Html
|
||||
// data
|
||||
key={`${aisle.points[0].pointUuid}_${aisle.points[1].pointUuid}`}
|
||||
userData={aisle}
|
||||
position={[textPosition.x, 1, textPosition.z]}
|
||||
// class
|
||||
wrapperClass="distance-text-wrapper"
|
||||
className="distance-text"
|
||||
// other
|
||||
zIndexRange={[1, 0]}
|
||||
prepend
|
||||
sprite
|
||||
>
|
||||
<div
|
||||
key={aisle.aisleUuid}
|
||||
className={`distance ${aisle.aisleUuid}`}
|
||||
>
|
||||
{distance.toFixed(2)} m
|
||||
</div>
|
||||
</Html>
|
||||
}
|
||||
|
||||
</React.Fragment>
|
||||
)
|
||||
})}
|
||||
</group>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AisleInstances
|
||||
@@ -0,0 +1,49 @@
|
||||
import ArcAisle from './aisleTypes/arcAisle';
|
||||
import ArrowAisle from './aisleTypes/arrowAisle';
|
||||
import ArrowsAisle from './aisleTypes/arrowsAisle';
|
||||
import CircleAisle from './aisleTypes/circleAisle';
|
||||
import DashedAisle from './aisleTypes/dashedAisle';
|
||||
import DottedAisle from './aisleTypes/dottedAisle';
|
||||
import JunctionAisle from './aisleTypes/junctionAisle';
|
||||
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} />
|
||||
)}
|
||||
|
||||
{aisle.type.aisleType === 'dotted-aisle' && (
|
||||
<DottedAisle aisle={aisle} />
|
||||
)}
|
||||
|
||||
{aisle.type.aisleType === 'arrows-aisle' && (
|
||||
<ArrowsAisle aisle={aisle} />
|
||||
)}
|
||||
|
||||
{aisle.type.aisleType === 'arrow-aisle' && (
|
||||
<ArrowAisle aisle={aisle} />
|
||||
)}
|
||||
|
||||
{aisle.type.aisleType === 'circle-aisle' && (
|
||||
<CircleAisle aisle={aisle} />
|
||||
)}
|
||||
|
||||
{aisle.type.aisleType === 'junction-aisle' && (
|
||||
<JunctionAisle aisle={aisle} />
|
||||
)}
|
||||
|
||||
{aisle.type.aisleType === 'arc-aisle' && (
|
||||
<ArcAisle aisle={aisle} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default AisleInstance;
|
||||
@@ -0,0 +1,104 @@
|
||||
import * as THREE from 'three';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { Extrude } from '@react-three/drei';
|
||||
import * as Constants from '../../../../../../types/world/worldConstants';
|
||||
import { useToolMode } from '../../../../../../store/builder/store';
|
||||
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
|
||||
|
||||
function ArcAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const aisleRef = useRef<THREE.Group>(null);
|
||||
const { toolMode } = useToolMode();
|
||||
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
|
||||
|
||||
const arc = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arc-aisle') return null;
|
||||
|
||||
const start = new THREE.Vector3(...aisle.points[0].position);
|
||||
const end = new THREE.Vector3(...aisle.points[1].position);
|
||||
const width = aisle.type.aisleWidth || 0.5;
|
||||
const isFlipped = aisle.type.isFlipped || false;
|
||||
|
||||
const direction = new THREE.Vector3().subVectors(end, start);
|
||||
const length = direction.length();
|
||||
direction.normalize();
|
||||
|
||||
const perpendicular = new THREE.Vector3(-direction.z, 0, direction.x).normalize();
|
||||
if (!isFlipped) perpendicular.negate();
|
||||
|
||||
const arcHeight = length * 0.25;
|
||||
const midPoint = new THREE.Vector3().lerpVectors(start, end, 0.5);
|
||||
const controlPoint = new THREE.Vector3().copy(midPoint).addScaledVector(perpendicular, arcHeight);
|
||||
|
||||
const widthOffset = perpendicular.clone().multiplyScalar(width / 2);
|
||||
|
||||
const p1 = new THREE.Vector3().copy(start).add(widthOffset);
|
||||
const p2 = new THREE.Vector3().copy(end).add(widthOffset);
|
||||
const p3 = new THREE.Vector3().copy(end).sub(widthOffset);
|
||||
const p4 = new THREE.Vector3().copy(start).sub(widthOffset);
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
shape.moveTo(p1.x, p1.z);
|
||||
|
||||
shape.quadraticCurveTo(
|
||||
controlPoint.x + widthOffset.x,
|
||||
controlPoint.z + widthOffset.z,
|
||||
p2.x, p2.z
|
||||
);
|
||||
|
||||
shape.lineTo(p3.x, p3.z);
|
||||
|
||||
shape.quadraticCurveTo(
|
||||
controlPoint.x - widthOffset.x,
|
||||
controlPoint.z - widthOffset.z,
|
||||
p4.x, p4.z
|
||||
);
|
||||
|
||||
shape.lineTo(p1.x, p1.z);
|
||||
|
||||
return {
|
||||
shape,
|
||||
position: new THREE.Vector3(0, 0, 0),
|
||||
rotationY: 0
|
||||
};
|
||||
}, [aisle]);
|
||||
|
||||
const handleClick = () => {
|
||||
if (toolMode === 'move' && !hoveredPoint) {
|
||||
setSelectedAisle(aisleRef.current);
|
||||
}
|
||||
}
|
||||
|
||||
if (!arc) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
name='Arc-Aisle'
|
||||
uuid={aisle.aisleUuid}
|
||||
ref={aisleRef}
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
userData={aisle}
|
||||
onClick={handleClick}
|
||||
onPointerMissed={() => {
|
||||
setSelectedAisle(null);
|
||||
}}
|
||||
>
|
||||
<Extrude
|
||||
args={[arc.shape, {
|
||||
depth: 0.01,
|
||||
bevelEnabled: false,
|
||||
curveSegments: 32
|
||||
}]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default ArcAisle;
|
||||
@@ -0,0 +1,89 @@
|
||||
import * as THREE from 'three';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { Extrude } from '@react-three/drei';
|
||||
import * as Constants from '../../../../../../types/world/worldConstants';
|
||||
import { useToolMode } from '../../../../../../store/builder/store';
|
||||
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
|
||||
|
||||
function ArrowAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const aisleRef = useRef<THREE.Group>(null);
|
||||
const { toolMode } = useToolMode();
|
||||
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
|
||||
|
||||
const arrow = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrow-aisle') return null;
|
||||
|
||||
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 direction = new THREE.Vector3().subVectors(end, start);
|
||||
const length = direction.length();
|
||||
direction.normalize();
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
const arrowHeadLength = width * 2;
|
||||
const shaftLength = length - arrowHeadLength;
|
||||
|
||||
if (shaftLength > 0) {
|
||||
shape.moveTo(0, 0);
|
||||
shape.lineTo(width, -arrowHeadLength);
|
||||
shape.lineTo(width / 2, -arrowHeadLength);
|
||||
shape.lineTo(width / 2, -length);
|
||||
shape.lineTo(-width / 2, -length);
|
||||
shape.lineTo(-width / 2, -arrowHeadLength);
|
||||
shape.lineTo(-width, -arrowHeadLength);
|
||||
shape.lineTo(0, 0);
|
||||
} else {
|
||||
shape.moveTo(0, 0);
|
||||
shape.lineTo(width, -length);
|
||||
shape.lineTo(-width, -length);
|
||||
shape.lineTo(0, 0);
|
||||
}
|
||||
|
||||
shape.closePath();
|
||||
|
||||
const position = end;
|
||||
const angle = Math.atan2(direction.x, direction.z);
|
||||
|
||||
return { shape, position, rotationY: angle };
|
||||
}, [aisle]);
|
||||
|
||||
const handleClick = () => {
|
||||
if (toolMode === 'move' && !hoveredPoint) {
|
||||
setSelectedAisle(aisleRef.current);
|
||||
}
|
||||
}
|
||||
|
||||
if (!arrow) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
name='Arrow-Aisle'
|
||||
uuid={aisle.aisleUuid}
|
||||
ref={aisleRef}
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
userData={aisle}
|
||||
onClick={handleClick}
|
||||
onPointerMissed={() => {
|
||||
setSelectedAisle(null);
|
||||
}}
|
||||
>
|
||||
<group position={[arrow.position.x, arrow.position.z, 0]} rotation={[0, 0, -arrow.rotationY]}>
|
||||
<Extrude
|
||||
args={[arrow.shape, { depth: 0.01, bevelEnabled: false }]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
</group>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default ArrowAisle;
|
||||
@@ -0,0 +1,94 @@
|
||||
import * as THREE from 'three';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { Extrude } from '@react-three/drei';
|
||||
import * as Constants from '../../../../../../types/world/worldConstants';
|
||||
import { useToolMode } from '../../../../../../store/builder/store';
|
||||
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
|
||||
|
||||
function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const aisleRef = useRef<THREE.Group>(null);
|
||||
const { toolMode } = useToolMode();
|
||||
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
|
||||
|
||||
const arrows = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrows-aisle') 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 arrowLength = aisle.type.aisleLength || 0.6;
|
||||
const spacing = aisle.type.gapLength || 0.6;
|
||||
|
||||
const direction = new THREE.Vector3().subVectors(end, start);
|
||||
const length = direction.length();
|
||||
direction.normalize();
|
||||
|
||||
const count = Math.floor((length + spacing) / (arrowLength + spacing));
|
||||
|
||||
const arrowShapes: { shape: THREE.Shape; position: THREE.Vector3; rotationY: number }[] = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const initialOffset = arrowLength;
|
||||
const center = new THREE.Vector3().copy(start).addScaledVector(direction, initialOffset + i * (arrowLength + spacing));
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
const w = width * 0.8;
|
||||
const h = arrowLength;
|
||||
|
||||
shape.moveTo(0, 0);
|
||||
shape.lineTo(w, h * 0.6);
|
||||
shape.lineTo(w * 0.4, h * 0.6);
|
||||
shape.lineTo(w * 0.4, h);
|
||||
shape.lineTo(-w * 0.4, h);
|
||||
shape.lineTo(-w * 0.4, h * 0.6);
|
||||
shape.lineTo(-w, h * 0.6);
|
||||
shape.lineTo(0, 0);
|
||||
|
||||
const angle = Math.atan2(direction.x, direction.z) + Math.PI;
|
||||
|
||||
arrowShapes.push({ shape, position: center, rotationY: angle });
|
||||
}
|
||||
|
||||
return arrowShapes;
|
||||
}, [aisle]);
|
||||
|
||||
const handleClick = () => {
|
||||
if (toolMode === 'move' && !hoveredPoint) {
|
||||
setSelectedAisle(aisleRef.current);
|
||||
}
|
||||
}
|
||||
|
||||
if (arrows.length === 0) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
name='Arrows-Aisle'
|
||||
uuid={aisle.aisleUuid}
|
||||
ref={aisleRef}
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
userData={aisle}
|
||||
onClick={handleClick}
|
||||
onPointerMissed={() => {
|
||||
setSelectedAisle(null);
|
||||
}}
|
||||
>
|
||||
{arrows.map(({ shape, position, rotationY }, index) => (
|
||||
<group key={index} position={[position.x, position.z, 0]} rotation={[0, 0, -rotationY]}>
|
||||
<Extrude
|
||||
args={[shape, { depth: 0.01, bevelEnabled: false }]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
</group>
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default ArrowsAisle;
|
||||
@@ -0,0 +1,80 @@
|
||||
import * as THREE from 'three';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { Extrude } from '@react-three/drei';
|
||||
import * as Constants from '../../../../../../types/world/worldConstants';
|
||||
import { useToolMode } from '../../../../../../store/builder/store';
|
||||
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
|
||||
|
||||
function CircleAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const aisleRef = useRef<THREE.Group>(null);
|
||||
const { toolMode } = useToolMode();
|
||||
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
|
||||
|
||||
const circle = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'circle-aisle') return null;
|
||||
|
||||
const center = new THREE.Vector3(...aisle.points[0].position);
|
||||
const widthCenter = new THREE.Vector3(...aisle.points[1].position);
|
||||
const width = aisle.type.aisleWidth || 0.1;
|
||||
|
||||
const middleRadius = center.distanceTo(widthCenter);
|
||||
|
||||
const innerRadius = Math.max(0, middleRadius - width / 2);
|
||||
const outerRadius = middleRadius + width / 2;
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
shape.absarc(center.x, center.z, outerRadius, 0, Math.PI * 2, false);
|
||||
|
||||
if (innerRadius > 0) {
|
||||
const hole = new THREE.Path();
|
||||
hole.absarc(center.x, center.z, innerRadius, 0, Math.PI * 2, true);
|
||||
shape.holes.push(hole);
|
||||
}
|
||||
|
||||
return {
|
||||
shape,
|
||||
rotationY: 0
|
||||
};
|
||||
}, [aisle]);
|
||||
|
||||
const handleClick = () => {
|
||||
if (toolMode === 'move' && !hoveredPoint) {
|
||||
setSelectedAisle(aisleRef.current);
|
||||
}
|
||||
}
|
||||
|
||||
if (!circle) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
name='Circle-Aisle'
|
||||
uuid={aisle.aisleUuid}
|
||||
ref={aisleRef}
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
userData={aisle}
|
||||
onClick={handleClick}
|
||||
onPointerMissed={() => {
|
||||
setSelectedAisle(null);
|
||||
}}
|
||||
>
|
||||
<Extrude
|
||||
args={[circle.shape, {
|
||||
depth: 0.01,
|
||||
bevelEnabled: false,
|
||||
steps: 1,
|
||||
curveSegments: 64
|
||||
}]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default CircleAisle;
|
||||
@@ -0,0 +1,91 @@
|
||||
import * as THREE from 'three';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { Extrude } from '@react-three/drei';
|
||||
import * as Constants from '../../../../../../types/world/worldConstants';
|
||||
import { useToolMode } from '../../../../../../store/builder/store';
|
||||
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
|
||||
|
||||
function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const aisleRef = useRef<THREE.Group>(null);
|
||||
const { toolMode } = useToolMode();
|
||||
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
|
||||
|
||||
const shapes = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dashed-aisle') 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 dashLength = aisle.type.dashLength || 0.5;
|
||||
const gapLength = aisle.type.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 + gapLength) / (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]);
|
||||
|
||||
const handleClick = () => {
|
||||
if (toolMode === 'move' && !hoveredPoint) {
|
||||
setSelectedAisle(aisleRef.current);
|
||||
}
|
||||
}
|
||||
|
||||
if (shapes.length === 0) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
name='Dashed-Aisle'
|
||||
uuid={aisle.aisleUuid}
|
||||
ref={aisleRef}
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
userData={aisle}
|
||||
onClick={handleClick}
|
||||
onPointerMissed={() => {
|
||||
setSelectedAisle(null);
|
||||
}}
|
||||
>
|
||||
{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 DashedAisle;
|
||||
@@ -0,0 +1,78 @@
|
||||
import * as THREE from 'three';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { Extrude } from '@react-three/drei';
|
||||
import * as Constants from '../../../../../../types/world/worldConstants';
|
||||
import { useToolMode } from '../../../../../../store/builder/store';
|
||||
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
|
||||
|
||||
function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const aisleRef = useRef<THREE.Group>(null);
|
||||
const { toolMode } = useToolMode();
|
||||
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
|
||||
|
||||
const shapes = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dotted-aisle') return [];
|
||||
|
||||
const start = new THREE.Vector3(...aisle.points[0].position);
|
||||
const end = new THREE.Vector3(...aisle.points[1].position);
|
||||
const width = aisle.type.dotRadius || 0.1;
|
||||
const dotSpacing = aisle.type.gapLength || 0.5;
|
||||
const dotRadius = width * 0.6;
|
||||
|
||||
const totalLength = new THREE.Vector3().subVectors(end, start).length();
|
||||
const dotCount = Math.floor((totalLength + (dotSpacing / 2)) / 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]);
|
||||
|
||||
const handleClick = () => {
|
||||
if (toolMode === 'move' && !hoveredPoint) {
|
||||
setSelectedAisle(aisleRef.current);
|
||||
}
|
||||
}
|
||||
|
||||
if (shapes.length === 0) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
name='Dotted-Aisle'
|
||||
uuid={aisle.aisleUuid}
|
||||
ref={aisleRef}
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
userData={aisle}
|
||||
onClick={handleClick}
|
||||
onPointerMissed={() => {
|
||||
setSelectedAisle(null);
|
||||
}}
|
||||
>
|
||||
{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;
|
||||
@@ -0,0 +1,126 @@
|
||||
import * as THREE from 'three';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { Extrude } from '@react-three/drei';
|
||||
import * as Constants from '../../../../../../types/world/worldConstants';
|
||||
import { useToolMode } from '../../../../../../store/builder/store';
|
||||
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
|
||||
|
||||
function JunctionAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const aisleRef = useRef<THREE.Group>(null);
|
||||
const { toolMode } = useToolMode();
|
||||
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
|
||||
|
||||
const arrows = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'junction-aisle') return null;
|
||||
|
||||
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 isFlipped = aisle.type.isFlipped || false;
|
||||
|
||||
const direction = new THREE.Vector3().subVectors(end, start);
|
||||
const length = direction.length();
|
||||
direction.normalize();
|
||||
|
||||
const mainShape = new THREE.Shape();
|
||||
const arrowHeadLength = width * 2;
|
||||
const shaftLength = length - arrowHeadLength;
|
||||
|
||||
if (shaftLength > 0) {
|
||||
mainShape.moveTo(0, 0);
|
||||
mainShape.lineTo(width, -arrowHeadLength);
|
||||
mainShape.lineTo(width / 2, -arrowHeadLength);
|
||||
mainShape.lineTo(width / 2, -length);
|
||||
mainShape.lineTo(-width / 2, -length);
|
||||
mainShape.lineTo(-width / 2, -arrowHeadLength);
|
||||
mainShape.lineTo(-width, -arrowHeadLength);
|
||||
mainShape.lineTo(0, 0);
|
||||
} else {
|
||||
mainShape.moveTo(0, 0);
|
||||
mainShape.lineTo(width, -length);
|
||||
mainShape.lineTo(-width, -length);
|
||||
mainShape.lineTo(0, 0);
|
||||
}
|
||||
mainShape.closePath();
|
||||
|
||||
const secondaryLength = length / 4;
|
||||
const secondaryShape = new THREE.Shape();
|
||||
const secondaryHeadLength = width * 2;
|
||||
const secondaryShaftLength = secondaryLength - secondaryHeadLength;
|
||||
|
||||
if (secondaryShaftLength > 0) {
|
||||
secondaryShape.moveTo(0, 0);
|
||||
secondaryShape.lineTo(width / 2, 0);
|
||||
secondaryShape.lineTo(width / 2, secondaryShaftLength);
|
||||
secondaryShape.lineTo(width, secondaryShaftLength);
|
||||
secondaryShape.lineTo(0, secondaryLength);
|
||||
secondaryShape.lineTo(-width, secondaryShaftLength);
|
||||
secondaryShape.lineTo(-width / 2, secondaryShaftLength);
|
||||
secondaryShape.lineTo(-width / 2, 0);
|
||||
secondaryShape.lineTo(0, 0);
|
||||
} else {
|
||||
secondaryShape.moveTo(0, 0);
|
||||
secondaryShape.lineTo(width, 0);
|
||||
secondaryShape.lineTo(0, secondaryLength);
|
||||
secondaryShape.lineTo(-width, 0);
|
||||
secondaryShape.lineTo(0, 0);
|
||||
}
|
||||
secondaryShape.closePath();
|
||||
|
||||
const mainPosition = end;
|
||||
const mainAngle = Math.atan2(direction.x, direction.z);
|
||||
|
||||
const perpendicularDirection = isFlipped
|
||||
? new THREE.Vector3(direction.z, 0, -direction.x).normalize()
|
||||
: new THREE.Vector3(-direction.z, 0, direction.x).normalize();
|
||||
|
||||
const secondaryAngle = Math.atan2(perpendicularDirection.x, perpendicularDirection.z);
|
||||
|
||||
const secondaryPosition = new THREE.Vector3().lerpVectors(start, end, 0.75);
|
||||
|
||||
return [
|
||||
{ shape: mainShape, position: mainPosition, rotationY: mainAngle },
|
||||
{ shape: secondaryShape, position: secondaryPosition, rotationY: secondaryAngle + Math.PI }
|
||||
];
|
||||
}, [aisle]);
|
||||
|
||||
const handleClick = () => {
|
||||
if (toolMode === 'move' && !hoveredPoint) {
|
||||
setSelectedAisle(aisleRef.current);
|
||||
}
|
||||
}
|
||||
|
||||
if (!arrows) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
name='Junction-Aisle'
|
||||
uuid={aisle.aisleUuid}
|
||||
ref={aisleRef}
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
userData={aisle}
|
||||
onClick={handleClick}
|
||||
onPointerMissed={() => {
|
||||
setSelectedAisle(null);
|
||||
}}
|
||||
>
|
||||
{arrows.map((arrow, index) => (
|
||||
<group key={index} position={[arrow.position.x, arrow.position.z, 0]} rotation={[0, 0, -arrow.rotationY]}>
|
||||
<Extrude
|
||||
args={[arrow.shape, { depth: 0.01, bevelEnabled: false }]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
</group>
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default JunctionAisle;
|
||||
@@ -0,0 +1,72 @@
|
||||
import * as THREE from 'three';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { Extrude } from '@react-three/drei';
|
||||
import * as Constants from '../../../../../../types/world/worldConstants';
|
||||
import { useToolMode } from '../../../../../../store/builder/store';
|
||||
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
|
||||
|
||||
function SolidAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const aisleRef = useRef<THREE.Group>(null);
|
||||
const { toolMode } = useToolMode();
|
||||
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
|
||||
const shape = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'solid-aisle') return null;
|
||||
|
||||
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 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]);
|
||||
|
||||
const handleClick = () => {
|
||||
if (toolMode === 'move' && !hoveredPoint) {
|
||||
setSelectedAisle(aisleRef.current);
|
||||
}
|
||||
}
|
||||
|
||||
if (!shape) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
name='Solid-Aisle'
|
||||
uuid={aisle.aisleUuid}
|
||||
ref={aisleRef}
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
userData={aisle}
|
||||
onClick={handleClick}
|
||||
onPointerMissed={() => {
|
||||
setSelectedAisle(null);
|
||||
}}
|
||||
>
|
||||
<Extrude
|
||||
args={[shape, { depth: 0.01, bevelEnabled: false }]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default SolidAisle;
|
||||
318
app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx
Normal file
318
app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx
Normal file
@@ -0,0 +1,318 @@
|
||||
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 { useAisleStore } from '../../../../store/builder/useAisleStore';
|
||||
import ReferenceAisle from './referenceAisle';
|
||||
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
|
||||
import ReferencePoint from '../../point/reference/referencePoint';
|
||||
import { createAisleApi } from '../../../../services/factoryBuilder/aisle/createAisleApi';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
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 { addAisle, getAislePointById } = useAisleStore();
|
||||
const drag = useRef(false);
|
||||
const isLeftMouseDown = useRef(false);
|
||||
const { projectId } = useParams();
|
||||
|
||||
const [tempPoints, setTempPoints] = useState<Point[]>([]);
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const { aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, isFlipped, snappedPosition, snappedPoint } = useBuilderStore();
|
||||
|
||||
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: 'Aisle',
|
||||
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 = getAislePointById(intersects.object.uuid);
|
||||
if (point) {
|
||||
newPoint.pointUuid = point.pointUuid;
|
||||
newPoint.position = point.position;
|
||||
newPoint.layer = point.layer;
|
||||
}
|
||||
}
|
||||
|
||||
if (aisleType === 'solid-aisle') {
|
||||
|
||||
if (tempPoints.length === 0) {
|
||||
setTempPoints([newPoint]);
|
||||
setIsCreating(true);
|
||||
} else {
|
||||
const aisle: Aisle = {
|
||||
aisleUuid: THREE.MathUtils.generateUUID(),
|
||||
points: [tempPoints[0], newPoint],
|
||||
type: {
|
||||
aisleType: 'solid-aisle',
|
||||
aisleColor: aisleColor,
|
||||
aisleWidth: aisleWidth
|
||||
}
|
||||
};
|
||||
addAisle(aisle);
|
||||
if (projectId) {
|
||||
createAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId)
|
||||
}
|
||||
setTempPoints([newPoint]);
|
||||
}
|
||||
} else if (aisleType === 'dashed-aisle') {
|
||||
|
||||
if (tempPoints.length === 0) {
|
||||
setTempPoints([newPoint]);
|
||||
setIsCreating(true);
|
||||
} else {
|
||||
const aisle: Aisle = {
|
||||
aisleUuid: THREE.MathUtils.generateUUID(),
|
||||
points: [tempPoints[0], newPoint],
|
||||
type: {
|
||||
aisleType: 'dashed-aisle',
|
||||
aisleColor: aisleColor,
|
||||
aisleWidth: aisleWidth,
|
||||
dashLength: dashLength,
|
||||
gapLength: gapLength
|
||||
}
|
||||
};
|
||||
addAisle(aisle);
|
||||
if (projectId) {
|
||||
createAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId)
|
||||
}
|
||||
setTempPoints([newPoint]);
|
||||
}
|
||||
} else if (aisleType === 'dotted-aisle') {
|
||||
|
||||
if (tempPoints.length === 0) {
|
||||
setTempPoints([newPoint]);
|
||||
setIsCreating(true);
|
||||
} else {
|
||||
const aisle: Aisle = {
|
||||
aisleUuid: THREE.MathUtils.generateUUID(),
|
||||
points: [tempPoints[0], newPoint],
|
||||
type: {
|
||||
aisleType: 'dotted-aisle',
|
||||
aisleColor: aisleColor,
|
||||
dotRadius: dotRadius,
|
||||
gapLength: gapLength
|
||||
}
|
||||
};
|
||||
addAisle(aisle);
|
||||
if (projectId) {
|
||||
createAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId)
|
||||
}
|
||||
setTempPoints([newPoint]);
|
||||
}
|
||||
} else if (aisleType === 'arrow-aisle') {
|
||||
|
||||
if (tempPoints.length === 0) {
|
||||
setTempPoints([newPoint]);
|
||||
setIsCreating(true);
|
||||
} else {
|
||||
const aisle: Aisle = {
|
||||
aisleUuid: THREE.MathUtils.generateUUID(),
|
||||
points: [tempPoints[0], newPoint],
|
||||
type: {
|
||||
aisleType: 'arrow-aisle',
|
||||
aisleColor: aisleColor,
|
||||
aisleWidth: aisleWidth
|
||||
}
|
||||
};
|
||||
addAisle(aisle);
|
||||
if (projectId) {
|
||||
createAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId)
|
||||
}
|
||||
setTempPoints([newPoint]);
|
||||
}
|
||||
} else if (aisleType === 'arrows-aisle') {
|
||||
|
||||
if (tempPoints.length === 0) {
|
||||
setTempPoints([newPoint]);
|
||||
setIsCreating(true);
|
||||
} else {
|
||||
const aisle: Aisle = {
|
||||
aisleUuid: THREE.MathUtils.generateUUID(),
|
||||
points: [tempPoints[0], newPoint],
|
||||
type: {
|
||||
aisleType: 'arrows-aisle',
|
||||
aisleColor: aisleColor,
|
||||
aisleWidth: aisleWidth,
|
||||
aisleLength: aisleLength,
|
||||
gapLength: gapLength
|
||||
}
|
||||
};
|
||||
addAisle(aisle);
|
||||
if (projectId) {
|
||||
createAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId)
|
||||
}
|
||||
setTempPoints([newPoint]);
|
||||
}
|
||||
} else if (aisleType === 'arc-aisle') {
|
||||
|
||||
if (tempPoints.length === 0) {
|
||||
setTempPoints([newPoint]);
|
||||
setIsCreating(true);
|
||||
} else {
|
||||
const aisle: Aisle = {
|
||||
aisleUuid: THREE.MathUtils.generateUUID(),
|
||||
points: [tempPoints[0], newPoint],
|
||||
type: {
|
||||
aisleType: 'arc-aisle',
|
||||
aisleColor: aisleColor,
|
||||
aisleWidth: aisleWidth,
|
||||
isFlipped: isFlipped
|
||||
}
|
||||
};
|
||||
addAisle(aisle);
|
||||
if (projectId) {
|
||||
createAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId)
|
||||
}
|
||||
setTempPoints([newPoint]);
|
||||
}
|
||||
} else if (aisleType === 'circle-aisle') {
|
||||
|
||||
if (tempPoints.length === 0) {
|
||||
setTempPoints([newPoint]);
|
||||
setIsCreating(true);
|
||||
} else {
|
||||
const aisle: Aisle = {
|
||||
aisleUuid: THREE.MathUtils.generateUUID(),
|
||||
points: [tempPoints[0], newPoint],
|
||||
type: {
|
||||
aisleType: 'circle-aisle',
|
||||
aisleColor: aisleColor,
|
||||
aisleWidth: aisleWidth
|
||||
}
|
||||
};
|
||||
addAisle(aisle);
|
||||
if (projectId) {
|
||||
createAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId)
|
||||
}
|
||||
setTempPoints([newPoint]);
|
||||
}
|
||||
} else if (aisleType === 'junction-aisle') {
|
||||
|
||||
if (tempPoints.length === 0) {
|
||||
setTempPoints([newPoint]);
|
||||
setIsCreating(true);
|
||||
} else {
|
||||
const aisle: Aisle = {
|
||||
aisleUuid: THREE.MathUtils.generateUUID(),
|
||||
points: [tempPoints[0], newPoint],
|
||||
type: {
|
||||
aisleType: 'junction-aisle',
|
||||
aisleColor: aisleColor,
|
||||
aisleWidth: aisleWidth,
|
||||
isFlipped: isFlipped
|
||||
}
|
||||
};
|
||||
addAisle(aisle);
|
||||
if (projectId) {
|
||||
createAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId)
|
||||
}
|
||||
setTempPoints([newPoint]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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 {
|
||||
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, addAisle, getAislePointById, aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, snappedPosition, snappedPoint]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{toggleView &&
|
||||
<>
|
||||
<group name='Aisle-Reference-Points-Group'>
|
||||
{tempPoints.map((point) => (
|
||||
<ReferencePoint key={point.pointUuid} point={point} />
|
||||
))}
|
||||
</group>
|
||||
|
||||
{tempPoints.length > 0 &&
|
||||
<ReferenceAisle tempPoints={tempPoints} />
|
||||
}
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default AisleCreator;
|
||||
774
app/src/modules/builder/aisle/aisleCreator/referenceAisle.tsx
Normal file
774
app/src/modules/builder/aisle/aisleCreator/referenceAisle.tsx
Normal file
@@ -0,0 +1,774 @@
|
||||
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, Html } from '@react-three/drei';
|
||||
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
|
||||
import { useDirectionalSnapping } from '../../point/helpers/useDirectionalSnapping';
|
||||
import { usePointSnapping } from '../../point/helpers/usePointSnapping';
|
||||
|
||||
interface ReferenceAisleProps {
|
||||
tempPoints: Point[];
|
||||
}
|
||||
|
||||
function ReferenceAisle({ tempPoints }: Readonly<ReferenceAisleProps>) {
|
||||
const { aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, isFlipped, 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 [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 { checkSnapForAisle } = usePointSnapping({ uuid: 'temp-aisle', pointType: 'Aisle', position: directionalSnap.position || [0, 0, 0] });
|
||||
|
||||
useFrame(() => {
|
||||
if (toolMode === "Aisle" && 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 = checkSnapForAisle([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;
|
||||
|
||||
if (aisleType === 'solid-aisle') {
|
||||
setTempAisle({
|
||||
aisleUuid: 'temp-aisle',
|
||||
points: [
|
||||
tempPoints[0],
|
||||
{
|
||||
pointUuid: 'temp-point',
|
||||
pointType: 'Aisle',
|
||||
position: finalPosition.current,
|
||||
layer: activeLayer
|
||||
}
|
||||
],
|
||||
type: {
|
||||
aisleType: aisleType,
|
||||
aisleColor: aisleColor,
|
||||
aisleWidth: aisleWidth
|
||||
}
|
||||
});
|
||||
} else if (aisleType === 'dashed-aisle') {
|
||||
setTempAisle({
|
||||
aisleUuid: 'temp-aisle',
|
||||
points: [
|
||||
tempPoints[0],
|
||||
{
|
||||
pointUuid: 'temp-point',
|
||||
pointType: 'Aisle',
|
||||
position: finalPosition.current,
|
||||
layer: activeLayer
|
||||
}
|
||||
],
|
||||
type: {
|
||||
aisleType: aisleType,
|
||||
aisleColor: aisleColor,
|
||||
aisleWidth: aisleWidth,
|
||||
dashLength: dashLength,
|
||||
gapLength: gapLength
|
||||
}
|
||||
});
|
||||
} else if (aisleType === 'dotted-aisle') {
|
||||
setTempAisle({
|
||||
aisleUuid: 'temp-aisle',
|
||||
points: [
|
||||
tempPoints[0],
|
||||
{
|
||||
pointUuid: 'temp-point',
|
||||
pointType: 'Aisle',
|
||||
position: finalPosition.current,
|
||||
layer: activeLayer
|
||||
}
|
||||
],
|
||||
type: {
|
||||
aisleType: aisleType,
|
||||
aisleColor: aisleColor,
|
||||
dotRadius: dotRadius,
|
||||
gapLength: gapLength
|
||||
}
|
||||
});
|
||||
} else if (aisleType === 'arrow-aisle' || aisleType === 'circle-aisle') {
|
||||
setTempAisle({
|
||||
aisleUuid: 'temp-aisle',
|
||||
points: [
|
||||
tempPoints[0],
|
||||
{
|
||||
pointUuid: 'temp-point',
|
||||
pointType: 'Aisle',
|
||||
position: finalPosition.current,
|
||||
layer: activeLayer
|
||||
}
|
||||
],
|
||||
type: {
|
||||
aisleType: aisleType,
|
||||
aisleColor: aisleColor,
|
||||
aisleWidth: aisleWidth,
|
||||
}
|
||||
});
|
||||
} else if (aisleType === 'arrows-aisle') {
|
||||
setTempAisle({
|
||||
aisleUuid: 'temp-aisle',
|
||||
points: [
|
||||
tempPoints[0],
|
||||
{
|
||||
pointUuid: 'temp-point',
|
||||
pointType: 'Aisle',
|
||||
position: finalPosition.current,
|
||||
layer: activeLayer
|
||||
}
|
||||
],
|
||||
type: {
|
||||
aisleType: aisleType,
|
||||
aisleColor: aisleColor,
|
||||
aisleWidth: aisleWidth,
|
||||
aisleLength: aisleLength,
|
||||
gapLength: gapLength
|
||||
}
|
||||
});
|
||||
} else if (aisleType === 'junction-aisle' || aisleType === 'arc-aisle') {
|
||||
setTempAisle({
|
||||
aisleUuid: 'temp-aisle',
|
||||
points: [
|
||||
tempPoints[0],
|
||||
{
|
||||
pointUuid: 'temp-point',
|
||||
pointType: 'Aisle',
|
||||
position: finalPosition.current,
|
||||
layer: activeLayer
|
||||
}
|
||||
],
|
||||
type: {
|
||||
aisleType: aisleType,
|
||||
aisleColor: aisleColor,
|
||||
aisleWidth: aisleWidth,
|
||||
isFlipped: isFlipped,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (tempAisle !== null) {
|
||||
setTempAisle(null);
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setTempAisle(null);
|
||||
}, [toolMode, toggleView, tempPoints.length, aisleType, aisleWidth, aisleColor]);
|
||||
|
||||
if (!tempAisle) return null;
|
||||
|
||||
const renderAisle = () => {
|
||||
switch (aisleType) {
|
||||
case 'solid-aisle':
|
||||
return <SolidAisle aisle={tempAisle} />;
|
||||
case 'dashed-aisle':
|
||||
return <DashedAisle aisle={tempAisle} />;
|
||||
case 'dotted-aisle':
|
||||
return <DottedAisle aisle={tempAisle} />;
|
||||
case 'arrow-aisle':
|
||||
return <ArrowAisle aisle={tempAisle} />;
|
||||
case 'arrows-aisle':
|
||||
return <ArrowsAisle aisle={tempAisle} />;
|
||||
case 'circle-aisle':
|
||||
return <CircleAisle aisle={tempAisle} />;
|
||||
case 'junction-aisle':
|
||||
return <JunctionAisle aisle={tempAisle} />;
|
||||
case 'arc-aisle':
|
||||
return <ArcAisle aisle={tempAisle} />
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const textPosition = new THREE.Vector3().addVectors(new THREE.Vector3(...tempAisle.points[0].position), new THREE.Vector3(...tempAisle.points[1].position)).divideScalar(2);
|
||||
const distance = new THREE.Vector3(...tempAisle.points[0].position).distanceTo(new THREE.Vector3(...tempAisle.points[1].position));
|
||||
|
||||
const rendertext = () => {
|
||||
return (
|
||||
<>
|
||||
{toggleView &&
|
||||
<Html
|
||||
key={tempAisle.aisleUuid}
|
||||
userData={tempAisle}
|
||||
position={[textPosition.x, 1, textPosition.z]}
|
||||
wrapperClass="distance-text-wrapper"
|
||||
className="distance-text"
|
||||
zIndexRange={[1, 0]}
|
||||
prepend
|
||||
sprite
|
||||
>
|
||||
<div
|
||||
key={tempAisle.aisleUuid}
|
||||
className={`distance ${tempAisle.aisleUuid}`}
|
||||
>
|
||||
{distance.toFixed(2)} m
|
||||
</div>
|
||||
</Html>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<group name='Aisle-Reference-Group'>
|
||||
{renderAisle()}
|
||||
{rendertext()}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default ReferenceAisle;
|
||||
|
||||
function SolidAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
|
||||
const shape = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'solid-aisle') return null;
|
||||
|
||||
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 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.aisleColor || '#ffffff'} side={THREE.DoubleSide} />
|
||||
</Extrude>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const shapes = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dashed-aisle') 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 dashLength = aisle.type.dashLength || 0.5;
|
||||
const gapLength = aisle.type.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 + gapLength) / (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.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const shapes = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dotted-aisle') return [];
|
||||
|
||||
const start = new THREE.Vector3(...aisle.points[0].position);
|
||||
const end = new THREE.Vector3(...aisle.points[1].position);
|
||||
const width = aisle.type.dotRadius || 0.1;
|
||||
const dotSpacing = aisle.type.gapLength || 0.5;
|
||||
const dotRadius = width * 0.6;
|
||||
|
||||
const totalLength = new THREE.Vector3().subVectors(end, start).length();
|
||||
const dotCount = Math.floor((totalLength + (dotSpacing / 2)) / 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>
|
||||
);
|
||||
}
|
||||
|
||||
function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const arrows = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrows-aisle') 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 arrowLength = aisle.type.aisleLength || 0.6;
|
||||
const spacing = aisle.type.gapLength || 0.6;
|
||||
|
||||
const direction = new THREE.Vector3().subVectors(end, start);
|
||||
const length = direction.length();
|
||||
direction.normalize();
|
||||
|
||||
const count = Math.floor((length + spacing) / (arrowLength + spacing));
|
||||
|
||||
const arrowShapes: { shape: THREE.Shape; position: THREE.Vector3; rotationY: number }[] = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const initialOffset = arrowLength;
|
||||
const center = new THREE.Vector3().copy(start).addScaledVector(direction, initialOffset + i * (arrowLength + spacing));
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
const w = width * 0.8;
|
||||
const h = arrowLength;
|
||||
|
||||
shape.moveTo(0, 0);
|
||||
shape.lineTo(w, h * 0.6);
|
||||
shape.lineTo(w * 0.4, h * 0.6);
|
||||
shape.lineTo(w * 0.4, h);
|
||||
shape.lineTo(-w * 0.4, h);
|
||||
shape.lineTo(-w * 0.4, h * 0.6);
|
||||
shape.lineTo(-w, h * 0.6);
|
||||
shape.lineTo(0, 0);
|
||||
|
||||
const angle = Math.atan2(direction.x, direction.z) + Math.PI;
|
||||
|
||||
arrowShapes.push({ shape, position: center, rotationY: angle });
|
||||
}
|
||||
|
||||
return arrowShapes;
|
||||
}, [aisle]);
|
||||
|
||||
if (arrows.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]}
|
||||
>
|
||||
{arrows.map(({ shape, position, rotationY }, index) => (
|
||||
<group key={index} position={[position.x, position.z, 0]} rotation={[0, 0, -rotationY]}>
|
||||
<Extrude
|
||||
args={[shape, { depth: 0.01, bevelEnabled: false }]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
</group>
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
function ArrowAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const arrow = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrow-aisle') return null;
|
||||
|
||||
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 direction = new THREE.Vector3().subVectors(end, start);
|
||||
const length = direction.length();
|
||||
direction.normalize();
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
const arrowHeadLength = width * 2;
|
||||
const shaftLength = length - arrowHeadLength;
|
||||
|
||||
if (shaftLength > 0) {
|
||||
shape.moveTo(0, 0);
|
||||
shape.lineTo(width, -arrowHeadLength);
|
||||
shape.lineTo(width / 2, -arrowHeadLength);
|
||||
shape.lineTo(width / 2, -length);
|
||||
shape.lineTo(-width / 2, -length);
|
||||
shape.lineTo(-width / 2, -arrowHeadLength);
|
||||
shape.lineTo(-width, -arrowHeadLength);
|
||||
shape.lineTo(0, 0);
|
||||
} else {
|
||||
shape.moveTo(0, 0);
|
||||
shape.lineTo(width, -length);
|
||||
shape.lineTo(-width, -length);
|
||||
shape.lineTo(0, 0);
|
||||
}
|
||||
|
||||
shape.closePath();
|
||||
|
||||
const position = end;
|
||||
const angle = Math.atan2(direction.x, direction.z);
|
||||
|
||||
return { shape, position, rotationY: angle };
|
||||
}, [aisle]);
|
||||
|
||||
if (!arrow) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
>
|
||||
<group position={[arrow.position.x, arrow.position.z, 0]} rotation={[0, 0, -arrow.rotationY]}>
|
||||
<Extrude
|
||||
args={[arrow.shape, { depth: 0.01, bevelEnabled: false }]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
</group>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
function CircleAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const circle = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'circle-aisle') return null;
|
||||
|
||||
const center = new THREE.Vector3(...aisle.points[0].position);
|
||||
const widthCenter = new THREE.Vector3(...aisle.points[1].position);
|
||||
const width = aisle.type.aisleWidth || 0.1;
|
||||
|
||||
const middleRadius = center.distanceTo(widthCenter);
|
||||
|
||||
const innerRadius = Math.max(0, middleRadius - width / 2);
|
||||
const outerRadius = middleRadius + width / 2;
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
shape.absarc(center.x, center.z, outerRadius, 0, Math.PI * 2, false);
|
||||
|
||||
if (innerRadius > 0) {
|
||||
const hole = new THREE.Path();
|
||||
hole.absarc(center.x, center.z, innerRadius, 0, Math.PI * 2, true);
|
||||
shape.holes.push(hole);
|
||||
}
|
||||
|
||||
return {
|
||||
shape,
|
||||
rotationY: 0
|
||||
};
|
||||
}, [aisle]);
|
||||
|
||||
if (!circle) 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={[circle.shape, {
|
||||
depth: 0.01,
|
||||
bevelEnabled: false,
|
||||
steps: 1,
|
||||
curveSegments: 64
|
||||
}]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
function JunctionAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const arrows = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'junction-aisle') return null;
|
||||
|
||||
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 isFlipped = aisle.type.isFlipped || false;
|
||||
|
||||
const direction = new THREE.Vector3().subVectors(end, start);
|
||||
const length = direction.length();
|
||||
direction.normalize();
|
||||
|
||||
const mainShape = new THREE.Shape();
|
||||
const arrowHeadLength = width * 2;
|
||||
const shaftLength = length - arrowHeadLength;
|
||||
|
||||
if (shaftLength > 0) {
|
||||
mainShape.moveTo(0, 0);
|
||||
mainShape.lineTo(width, -arrowHeadLength);
|
||||
mainShape.lineTo(width / 2, -arrowHeadLength);
|
||||
mainShape.lineTo(width / 2, -length);
|
||||
mainShape.lineTo(-width / 2, -length);
|
||||
mainShape.lineTo(-width / 2, -arrowHeadLength);
|
||||
mainShape.lineTo(-width, -arrowHeadLength);
|
||||
mainShape.lineTo(0, 0);
|
||||
} else {
|
||||
mainShape.moveTo(0, 0);
|
||||
mainShape.lineTo(width, -length);
|
||||
mainShape.lineTo(-width, -length);
|
||||
mainShape.lineTo(0, 0);
|
||||
}
|
||||
mainShape.closePath();
|
||||
|
||||
const secondaryLength = length / 4;
|
||||
const secondaryShape = new THREE.Shape();
|
||||
const secondaryHeadLength = width * 2;
|
||||
const secondaryShaftLength = secondaryLength - secondaryHeadLength;
|
||||
|
||||
if (secondaryShaftLength > 0) {
|
||||
secondaryShape.moveTo(0, 0);
|
||||
secondaryShape.lineTo(width / 2, 0);
|
||||
secondaryShape.lineTo(width / 2, secondaryShaftLength);
|
||||
secondaryShape.lineTo(width, secondaryShaftLength);
|
||||
secondaryShape.lineTo(0, secondaryLength);
|
||||
secondaryShape.lineTo(-width, secondaryShaftLength);
|
||||
secondaryShape.lineTo(-width / 2, secondaryShaftLength);
|
||||
secondaryShape.lineTo(-width / 2, 0);
|
||||
secondaryShape.lineTo(0, 0);
|
||||
} else {
|
||||
secondaryShape.moveTo(0, 0);
|
||||
secondaryShape.lineTo(width, 0);
|
||||
secondaryShape.lineTo(0, secondaryLength);
|
||||
secondaryShape.lineTo(-width, 0);
|
||||
secondaryShape.lineTo(0, 0);
|
||||
}
|
||||
secondaryShape.closePath();
|
||||
|
||||
const mainPosition = end;
|
||||
const mainAngle = Math.atan2(direction.x, direction.z);
|
||||
|
||||
const perpendicularDirection = isFlipped
|
||||
? new THREE.Vector3(direction.z, 0, -direction.x).normalize()
|
||||
: new THREE.Vector3(-direction.z, 0, direction.x).normalize();
|
||||
|
||||
const secondaryAngle = Math.atan2(perpendicularDirection.x, perpendicularDirection.z);
|
||||
|
||||
const secondaryPosition = new THREE.Vector3().lerpVectors(start, end, 0.75);
|
||||
|
||||
return [
|
||||
{ shape: mainShape, position: mainPosition, rotationY: mainAngle },
|
||||
{
|
||||
shape: secondaryShape,
|
||||
position: secondaryPosition,
|
||||
rotationY: secondaryAngle + Math.PI
|
||||
}
|
||||
];
|
||||
}, [aisle]);
|
||||
|
||||
if (!arrows) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
>
|
||||
{arrows.map((arrow, index) => (
|
||||
<group key={index} position={[arrow.position.x, arrow.position.z, 0]} rotation={[0, 0, -arrow.rotationY]}>
|
||||
<Extrude
|
||||
args={[arrow.shape, { depth: 0.01, bevelEnabled: false }]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
</group>
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
function ArcAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||
const arc = useMemo(() => {
|
||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arc-aisle') return null;
|
||||
|
||||
const start = new THREE.Vector3(...aisle.points[0].position);
|
||||
const end = new THREE.Vector3(...aisle.points[1].position);
|
||||
const width = aisle.type.aisleWidth || 0.5;
|
||||
const isFlipped = aisle.type.isFlipped || false;
|
||||
|
||||
const direction = new THREE.Vector3().subVectors(end, start);
|
||||
const length = direction.length();
|
||||
direction.normalize();
|
||||
|
||||
const perpendicular = new THREE.Vector3(-direction.z, 0, direction.x).normalize();
|
||||
if (!isFlipped) perpendicular.negate();
|
||||
|
||||
const arcHeight = length * 0.25;
|
||||
const midPoint = new THREE.Vector3().lerpVectors(start, end, 0.5);
|
||||
const controlPoint = new THREE.Vector3().copy(midPoint).addScaledVector(perpendicular, arcHeight);
|
||||
|
||||
const widthOffset = perpendicular.clone().multiplyScalar(width / 2);
|
||||
|
||||
const p1 = new THREE.Vector3().copy(start).add(widthOffset);
|
||||
const p2 = new THREE.Vector3().copy(end).add(widthOffset);
|
||||
const p3 = new THREE.Vector3().copy(end).sub(widthOffset);
|
||||
const p4 = new THREE.Vector3().copy(start).sub(widthOffset);
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
shape.moveTo(p1.x, p1.z);
|
||||
|
||||
shape.quadraticCurveTo(
|
||||
controlPoint.x + widthOffset.x,
|
||||
controlPoint.z + widthOffset.z,
|
||||
p2.x, p2.z
|
||||
);
|
||||
|
||||
shape.lineTo(p3.x, p3.z);
|
||||
|
||||
shape.quadraticCurveTo(
|
||||
controlPoint.x - widthOffset.x,
|
||||
controlPoint.z - widthOffset.z,
|
||||
p4.x, p4.z
|
||||
);
|
||||
|
||||
shape.lineTo(p1.x, p1.z);
|
||||
|
||||
return {
|
||||
shape,
|
||||
position: new THREE.Vector3(0, 0, 0),
|
||||
rotationY: 0
|
||||
};
|
||||
}, [aisle]);
|
||||
|
||||
|
||||
if (!arc) 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={[arc.shape, {
|
||||
depth: 0.01,
|
||||
bevelEnabled: false,
|
||||
curveSegments: 32
|
||||
}]}
|
||||
receiveShadow
|
||||
castShadow
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={aisle.type.aisleColor || '#ffffff'}
|
||||
side={THREE.DoubleSide}
|
||||
/>
|
||||
</Extrude>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
35
app/src/modules/builder/aisle/aislesGroup.tsx
Normal file
35
app/src/modules/builder/aisle/aislesGroup.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useEffect } from 'react'
|
||||
import AisleCreator from './aisleCreator/aisleCreator'
|
||||
import AisleInstances from './Instances/aisleInstances'
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { getAisleApi } from '../../../services/factoryBuilder/aisle/getAisleApi';
|
||||
import { useAisleStore } from '../../../store/builder/useAisleStore';
|
||||
|
||||
function AislesGroup() {
|
||||
const { projectId } = useParams();
|
||||
const { setAisles } = useAisleStore();
|
||||
useEffect(() => {
|
||||
const fetchAisle = async () => {
|
||||
if (projectId) {
|
||||
const aisles = await getAisleApi(projectId);
|
||||
setAisles(aisles);
|
||||
}
|
||||
}
|
||||
fetchAisle()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
||||
<>
|
||||
|
||||
<AisleCreator />
|
||||
|
||||
|
||||
<AisleInstances />
|
||||
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default AislesGroup
|
||||
Reference in New Issue
Block a user