feat: implement aisle management features with creator and instances
This commit is contained in:
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user