Dwinzo_dev/app/src/modules/builder/aisle/aisleCreator/referenceAisle.tsx

415 lines
16 KiB
TypeScript

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';
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, 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);
// Calculate directional snap based on current and previous points
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' || aisleType === 'stripped-aisle') {
setTempAisle({
uuid: 'temp-aisle',
points: [
tempPoints[0],
{
uuid: 'temp-point',
pointType: 'Aisle',
position: finalPosition.current,
layer: activeLayer
}
],
type: {
aisleType: aisleType,
aisleColor: aisleColor,
aisleWidth: aisleWidth
}
});
} else if (aisleType === 'dashed-aisle') {
setTempAisle({
uuid: 'temp-aisle',
points: [
tempPoints[0],
{
uuid: '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({
uuid: 'temp-aisle',
points: [
tempPoints[0],
{
uuid: 'temp-point',
pointType: 'Aisle',
position: finalPosition.current,
layer: activeLayer
}
],
type: {
aisleType: aisleType,
aisleColor: aisleColor,
dotRadius: dotRadius,
gapLength: gapLength
}
});
} else if (aisleType === 'arrow-aisle') {
console.log();
} else if (aisleType === 'arrows-aisle') {
setTempAisle({
uuid: 'temp-aisle',
points: [
tempPoints[0],
{
uuid: 'temp-point',
pointType: 'Aisle',
position: finalPosition.current,
layer: activeLayer
}
],
type: {
aisleType: aisleType,
aisleColor: aisleColor,
aisleWidth: aisleWidth,
aisleLength: aisleLength,
gapLength: gapLength
}
});
} else if (aisleType === 'arc-aisle') {
console.log();
} else if (aisleType === 'circle-aisle') {
console.log();
} else if (aisleType === 'junction-aisle') {
console.log();
}
}
} 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 'arrows-aisle':
return <ArrowsAisle aisle={tempAisle} />
default:
return null;
}
};
return (
<group name='Aisle-Reference-Group'>
{renderAisle()}
</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>
);
}