feat: Refactor aisle rendering components to use Instances for improved performance and rendering efficiency
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { useToggleView } from '../../../../store/builder/store';
|
import { useToggleView } from '../../../../store/builder/store';
|
||||||
import AisleInstance from './instance/aisleInstance';
|
import AisleInstance from './instance/aisleInstance';
|
||||||
import Point from '../../point/point';
|
import Point from '../../point/point';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import { Extrude } from '@react-three/drei';
|
import { Instances, Instance } from '@react-three/drei';
|
||||||
import * as Constants from '../../../../../../types/world/worldConstants';
|
import * as Constants from '../../../../../../types/world/worldConstants';
|
||||||
import { useToolMode } from '../../../../../../store/builder/store';
|
import { useToolMode } from '../../../../../../store/builder/store';
|
||||||
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
|
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
|
||||||
@@ -10,8 +10,16 @@ function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
|
|||||||
const { toolMode } = useToolMode();
|
const { toolMode } = useToolMode();
|
||||||
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
|
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
|
||||||
|
|
||||||
const arrows = useMemo(() => {
|
const { arrowGeometry, arrowInstances } = useMemo(() => {
|
||||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrows-aisle') return [];
|
const result = {
|
||||||
|
arrowGeometry: null as THREE.ExtrudeGeometry | null,
|
||||||
|
arrowInstances: [] as {
|
||||||
|
position: [number, number, number];
|
||||||
|
rotation: [number, number, number];
|
||||||
|
}[],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrows-aisle') return result;
|
||||||
|
|
||||||
const start = new THREE.Vector3(...aisle.points[0].position);
|
const start = new THREE.Vector3(...aisle.points[0].position);
|
||||||
const end = new THREE.Vector3(...aisle.points[1].position);
|
const end = new THREE.Vector3(...aisle.points[1].position);
|
||||||
@@ -24,69 +32,67 @@ function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
|
|||||||
direction.normalize();
|
direction.normalize();
|
||||||
|
|
||||||
const count = Math.floor((length + spacing) / (arrowLength + spacing));
|
const count = Math.floor((length + spacing) / (arrowLength + spacing));
|
||||||
|
const angle = Math.atan2(direction.x, direction.z) + Math.PI;
|
||||||
|
|
||||||
const arrowShapes: { shape: THREE.Shape; position: THREE.Vector3; rotationY: number }[] = [];
|
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);
|
||||||
|
|
||||||
|
result.arrowGeometry = new THREE.ExtrudeGeometry(shape, {
|
||||||
|
depth: 0.01,
|
||||||
|
bevelEnabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
result.arrowGeometry.rotateX(Math.PI / 2);
|
||||||
|
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
const initialOffset = arrowLength;
|
const offset = arrowLength + i * (arrowLength + spacing);
|
||||||
const center = new THREE.Vector3().copy(start).addScaledVector(direction, initialOffset + i * (arrowLength + spacing));
|
const center = new THREE.Vector3().copy(start).addScaledVector(direction, offset);
|
||||||
|
|
||||||
const shape = new THREE.Shape();
|
result.arrowInstances.push({
|
||||||
const w = width * 0.8;
|
position: [center.x, 0, center.z],
|
||||||
const h = arrowLength;
|
rotation: [0, angle, 0],
|
||||||
|
});
|
||||||
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;
|
return result;
|
||||||
}, [aisle]);
|
}, [aisle]);
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (toolMode === 'move' && !hoveredPoint) {
|
if (toolMode === 'move' && !hoveredPoint) {
|
||||||
setSelectedAisle(aisleRef.current);
|
setSelectedAisle(aisleRef.current);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (arrows.length === 0) return null;
|
if (!arrowGeometry || arrowInstances.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group
|
<group
|
||||||
name='Arrows-Aisle'
|
name="Arrows-Aisle"
|
||||||
uuid={aisle.aisleUuid}
|
uuid={aisle.aisleUuid}
|
||||||
ref={aisleRef}
|
ref={aisleRef}
|
||||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||||
rotation={[Math.PI / 2, 0, 0]}
|
|
||||||
userData={aisle}
|
userData={aisle}
|
||||||
onDoubleClick={handleClick}
|
onDoubleClick={handleClick}
|
||||||
onPointerMissed={() => {
|
onPointerMissed={() => {
|
||||||
setSelectedAisle(null);
|
setSelectedAisle(null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{arrows.map(({ shape, position, rotationY }, index) => (
|
<Instances geometry={arrowGeometry}>
|
||||||
<group key={index} position={[position.x, position.z, 0]} rotation={[0, 0, -rotationY]}>
|
<meshStandardMaterial color={aisle.type.aisleColor || '#ffffff'} side={THREE.DoubleSide} />
|
||||||
<Extrude
|
{arrowInstances.map(({ position, rotation }, i) => (
|
||||||
args={[shape, { depth: 0.01, bevelEnabled: false }]}
|
<Instance key={i} position={position} rotation={rotation} />
|
||||||
receiveShadow
|
))}
|
||||||
castShadow
|
</Instances>
|
||||||
>
|
|
||||||
<meshStandardMaterial
|
|
||||||
color={aisle.type.aisleColor || '#ffffff'}
|
|
||||||
side={THREE.DoubleSide}
|
|
||||||
/>
|
|
||||||
</Extrude>
|
|
||||||
</group>
|
|
||||||
))}
|
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import { Extrude } from '@react-three/drei';
|
import { Instances, Instance } from '@react-three/drei';
|
||||||
import * as Constants from '../../../../../../types/world/worldConstants';
|
import * as Constants from '../../../../../../types/world/worldConstants';
|
||||||
import { useToolMode } from '../../../../../../store/builder/store';
|
import { useToolMode } from '../../../../../../store/builder/store';
|
||||||
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
|
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
|
||||||
@@ -10,80 +10,69 @@ function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
|
|||||||
const { toolMode } = useToolMode();
|
const { toolMode } = useToolMode();
|
||||||
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
|
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
|
||||||
|
|
||||||
const shapes = useMemo(() => {
|
const dashInstances = useMemo(() => {
|
||||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dashed-aisle') return [];
|
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dashed-aisle') return [];
|
||||||
|
|
||||||
const start = new THREE.Vector3(...aisle.points[0].position);
|
const start = new THREE.Vector3(...aisle.points[0].position);
|
||||||
const end = new THREE.Vector3(...aisle.points[1].position);
|
const end = new THREE.Vector3(...aisle.points[1].position);
|
||||||
|
|
||||||
const width = aisle.type.aisleWidth || 0.1;
|
const width = aisle.type.aisleWidth || 0.1;
|
||||||
const dashLength = aisle.type.dashLength || 0.5;
|
const dashLength = aisle.type.dashLength || 0.5;
|
||||||
const gapLength = aisle.type.gapLength || 0.3;
|
const gapLength = aisle.type.gapLength || 0.3;
|
||||||
|
|
||||||
const direction = new THREE.Vector3().subVectors(end, start).normalize();
|
const totalVec = new THREE.Vector3().subVectors(end, start);
|
||||||
const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize();
|
const totalLength = totalVec.length();
|
||||||
|
const direction = totalVec.clone().normalize();
|
||||||
const totalLength = new THREE.Vector3().subVectors(end, start).length();
|
|
||||||
const segmentCount = Math.floor((totalLength + gapLength) / (dashLength + gapLength));
|
const segmentCount = Math.floor((totalLength + gapLength) / (dashLength + gapLength));
|
||||||
|
|
||||||
const shapes = [];
|
const instances = [];
|
||||||
const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize();
|
|
||||||
|
|
||||||
for (let i = 0; i < segmentCount; i++) {
|
for (let i = 0; i < segmentCount; i++) {
|
||||||
const segmentStart = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * (dashLength + gapLength));
|
const center = start.clone().addScaledVector(direction, i * (dashLength + gapLength) + dashLength / 2);
|
||||||
const segmentEnd = new THREE.Vector3().copy(segmentStart).addScaledVector(directionNormalized, dashLength);
|
|
||||||
|
|
||||||
const leftStart = new THREE.Vector3().copy(segmentStart).addScaledVector(perp, width / 2);
|
const rotationY = Math.atan2(direction.x, direction.z);
|
||||||
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();
|
instances.push({
|
||||||
shape.moveTo(leftStart.x, leftStart.z);
|
position: [center.x, 0, center.z] as [number, number, number],
|
||||||
shape.lineTo(leftEnd.x, leftEnd.z);
|
scale: [width, 0.001, dashLength] as [number, number, number],
|
||||||
shape.lineTo(rightEnd.x, rightEnd.z);
|
rotation: [0, rotationY, 0] as [number, number, number],
|
||||||
shape.lineTo(rightStart.x, rightStart.z);
|
});
|
||||||
shape.closePath();
|
|
||||||
|
|
||||||
shapes.push(shape);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return shapes;
|
return instances;
|
||||||
}, [aisle]);
|
}, [aisle]);
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (toolMode === 'move' && !hoveredPoint) {
|
if (toolMode === 'move' && !hoveredPoint) {
|
||||||
setSelectedAisle(aisleRef.current);
|
setSelectedAisle(aisleRef.current);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (shapes.length === 0) return null;
|
if (dashInstances.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group
|
<group
|
||||||
name='Dashed-Aisle'
|
name="Dashed-Aisle"
|
||||||
uuid={aisle.aisleUuid}
|
uuid={aisle.aisleUuid}
|
||||||
ref={aisleRef}
|
ref={aisleRef}
|
||||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||||
rotation={[Math.PI / 2, 0, 0]}
|
|
||||||
userData={aisle}
|
|
||||||
onDoubleClick={handleClick}
|
onDoubleClick={handleClick}
|
||||||
onPointerMissed={() => {
|
onPointerMissed={() => {
|
||||||
setSelectedAisle(null);
|
setSelectedAisle(null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{shapes.map((shape, index) => (
|
<Instances frustumCulled={false}>
|
||||||
<Extrude
|
<boxGeometry args={[1, 1, 1]} />
|
||||||
key={index}
|
<meshStandardMaterial color={aisle.type.aisleColor || '#ffffff'} />
|
||||||
args={[shape, { depth: 0.01, bevelEnabled: false }]}
|
{dashInstances.map((inst, i) => (
|
||||||
receiveShadow
|
<Instance
|
||||||
castShadow
|
key={i}
|
||||||
>
|
position={inst.position}
|
||||||
<meshStandardMaterial
|
scale={inst.scale}
|
||||||
color={aisle.type.aisleColor || '#ffffff'}
|
rotation={inst.rotation}
|
||||||
side={THREE.DoubleSide}
|
|
||||||
/>
|
/>
|
||||||
</Extrude>
|
))}
|
||||||
))}
|
</Instances>
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import { Extrude } from '@react-three/drei';
|
import { Instance, Instances } from '@react-three/drei';
|
||||||
import * as Constants from '../../../../../../types/world/worldConstants';
|
import * as Constants from '../../../../../../types/world/worldConstants';
|
||||||
import { useToolMode } from '../../../../../../store/builder/store';
|
import { useToolMode } from '../../../../../../store/builder/store';
|
||||||
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
|
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
|
||||||
@@ -10,31 +10,20 @@ function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
|
|||||||
const { toolMode } = useToolMode();
|
const { toolMode } = useToolMode();
|
||||||
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
|
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
|
||||||
|
|
||||||
const shapes = useMemo(() => {
|
const dotPositions = useMemo(() => {
|
||||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dotted-aisle') return [];
|
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dotted-aisle') return [];
|
||||||
|
|
||||||
const start = new THREE.Vector3(...aisle.points[0].position);
|
const start = new THREE.Vector3(...aisle.points[0].position);
|
||||||
const end = new THREE.Vector3(...aisle.points[1].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 dotSpacing = aisle.type.gapLength || 0.5;
|
||||||
const dotRadius = width * 0.6;
|
|
||||||
|
|
||||||
const totalLength = new THREE.Vector3().subVectors(end, start).length();
|
const totalLength = new THREE.Vector3().subVectors(end, start).length();
|
||||||
const dotCount = Math.floor((totalLength + (dotSpacing / 2)) / dotSpacing);
|
const dotCount = Math.floor((totalLength + (dotSpacing / 2)) / dotSpacing);
|
||||||
|
|
||||||
const shapes = [];
|
|
||||||
const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize();
|
const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize();
|
||||||
|
|
||||||
for (let i = 0; i < dotCount; i++) {
|
return Array.from({ length: dotCount }, (_, i) => {
|
||||||
const dotCenter = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * dotSpacing + dotSpacing / 2);
|
return 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]);
|
}, [aisle]);
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
@@ -43,7 +32,14 @@ function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shapes.length === 0) return null;
|
const { dotRadius, color } = useMemo(() => {
|
||||||
|
return {
|
||||||
|
dotRadius: aisle.type.aisleType === 'dotted-aisle' ? ((aisle.type as any).dotRadius || 0.1) * 0.6 : 0.06,
|
||||||
|
color: aisle.type.aisleColor || '#ffffff'
|
||||||
|
};
|
||||||
|
}, [aisle]);
|
||||||
|
|
||||||
|
if (dotPositions.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group
|
<group
|
||||||
@@ -51,26 +47,24 @@ function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
|
|||||||
uuid={aisle.aisleUuid}
|
uuid={aisle.aisleUuid}
|
||||||
ref={aisleRef}
|
ref={aisleRef}
|
||||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||||
rotation={[Math.PI / 2, 0, 0]}
|
rotation={[0, 0, 0]}
|
||||||
userData={aisle}
|
userData={aisle}
|
||||||
onDoubleClick={handleClick}
|
onDoubleClick={handleClick}
|
||||||
onPointerMissed={() => {
|
onPointerMissed={() => {
|
||||||
setSelectedAisle(null);
|
setSelectedAisle(null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{shapes.map((shape, index) => (
|
<Instances frustumCulled={false}>
|
||||||
<Extrude
|
<circleGeometry args={[dotRadius, 32]} />
|
||||||
key={index}
|
<meshStandardMaterial color={color} side={THREE.DoubleSide} />
|
||||||
args={[shape, { depth: 0.01, bevelEnabled: false }]}
|
{dotPositions.map((position, index) => (
|
||||||
receiveShadow
|
<Instance
|
||||||
castShadow
|
key={index}
|
||||||
>
|
position={[position.x, 0, position.z]}
|
||||||
<meshStandardMaterial
|
rotation={[-Math.PI / 2, 0, 0]}
|
||||||
color={aisle.type.aisleColor || '#ffffff'}
|
|
||||||
side={THREE.DoubleSide}
|
|
||||||
/>
|
/>
|
||||||
</Extrude>
|
))}
|
||||||
))}
|
</Instances>
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as THREE from 'three';
|
|||||||
import { useFrame, useThree } from '@react-three/fiber';
|
import { useFrame, useThree } from '@react-three/fiber';
|
||||||
import { useActiveLayer, useToolMode, useToggleView } from '../../../../store/builder/store';
|
import { useActiveLayer, useToolMode, useToggleView } from '../../../../store/builder/store';
|
||||||
import * as Constants from '../../../../types/world/worldConstants';
|
import * as Constants from '../../../../types/world/worldConstants';
|
||||||
import { Extrude, Html } from '@react-three/drei';
|
import { Extrude, Html, Instance, Instances } from '@react-three/drei';
|
||||||
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
|
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
|
||||||
import { useDirectionalSnapping } from '../../point/helpers/useDirectionalSnapping';
|
import { useDirectionalSnapping } from '../../point/helpers/useDirectionalSnapping';
|
||||||
import { usePointSnapping } from '../../point/helpers/usePointSnapping';
|
import { usePointSnapping } from '../../point/helpers/usePointSnapping';
|
||||||
@@ -289,125 +289,119 @@ function SolidAisle({ aisle }: { readonly aisle: Aisle }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
|
function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||||
const shapes = useMemo(() => {
|
|
||||||
|
const dashInstances = useMemo(() => {
|
||||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dashed-aisle') return [];
|
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dashed-aisle') return [];
|
||||||
|
|
||||||
const start = new THREE.Vector3(...aisle.points[0].position);
|
const start = new THREE.Vector3(...aisle.points[0].position);
|
||||||
const end = new THREE.Vector3(...aisle.points[1].position);
|
const end = new THREE.Vector3(...aisle.points[1].position);
|
||||||
|
|
||||||
const width = aisle.type.aisleWidth || 0.1;
|
const width = aisle.type.aisleWidth || 0.1;
|
||||||
const dashLength = aisle.type.dashLength || 0.5;
|
const dashLength = aisle.type.dashLength || 0.5;
|
||||||
const gapLength = aisle.type.gapLength || 0.3;
|
const gapLength = aisle.type.gapLength || 0.3;
|
||||||
|
|
||||||
const direction = new THREE.Vector3().subVectors(end, start).normalize();
|
const totalVec = new THREE.Vector3().subVectors(end, start);
|
||||||
const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize();
|
const totalLength = totalVec.length();
|
||||||
|
const direction = totalVec.clone().normalize();
|
||||||
const totalLength = new THREE.Vector3().subVectors(end, start).length();
|
|
||||||
const segmentCount = Math.floor((totalLength + gapLength) / (dashLength + gapLength));
|
const segmentCount = Math.floor((totalLength + gapLength) / (dashLength + gapLength));
|
||||||
|
|
||||||
const shapes = [];
|
const instances = [];
|
||||||
const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize();
|
|
||||||
|
|
||||||
for (let i = 0; i < segmentCount; i++) {
|
for (let i = 0; i < segmentCount; i++) {
|
||||||
const segmentStart = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * (dashLength + gapLength));
|
const center = start.clone().addScaledVector(direction, i * (dashLength + gapLength) + dashLength / 2);
|
||||||
const segmentEnd = new THREE.Vector3().copy(segmentStart).addScaledVector(directionNormalized, dashLength);
|
|
||||||
|
|
||||||
const leftStart = new THREE.Vector3().copy(segmentStart).addScaledVector(perp, width / 2);
|
const rotationY = Math.atan2(direction.x, direction.z);
|
||||||
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();
|
instances.push({
|
||||||
shape.moveTo(leftStart.x, leftStart.z);
|
position: [center.x, 0, center.z] as [number, number, number],
|
||||||
shape.lineTo(leftEnd.x, leftEnd.z);
|
scale: [width, 0.001, dashLength] as [number, number, number],
|
||||||
shape.lineTo(rightEnd.x, rightEnd.z);
|
rotation: [0, rotationY, 0] as [number, number, number],
|
||||||
shape.lineTo(rightStart.x, rightStart.z);
|
});
|
||||||
shape.closePath();
|
|
||||||
|
|
||||||
shapes.push(shape);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return shapes;
|
return instances;
|
||||||
}, [aisle]);
|
}, [aisle]);
|
||||||
|
|
||||||
if (shapes.length === 0) return null;
|
if (dashInstances.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group
|
<group
|
||||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||||
rotation={[Math.PI / 2, 0, 0]}
|
|
||||||
>
|
>
|
||||||
{shapes.map((shape, index) => (
|
<Instances frustumCulled={false}>
|
||||||
<Extrude
|
<boxGeometry args={[1, 1, 1]} />
|
||||||
key={index}
|
<meshStandardMaterial color={aisle.type.aisleColor || '#ffffff'} />
|
||||||
args={[shape, { depth: 0.01, bevelEnabled: false }]}
|
{dashInstances.map((inst, i) => (
|
||||||
receiveShadow
|
<Instance
|
||||||
castShadow
|
key={i}
|
||||||
>
|
position={inst.position}
|
||||||
<meshStandardMaterial
|
scale={inst.scale}
|
||||||
color={aisle.type.aisleColor || '#ffffff'}
|
rotation={inst.rotation}
|
||||||
side={THREE.DoubleSide}
|
|
||||||
/>
|
/>
|
||||||
</Extrude>
|
))}
|
||||||
))}
|
</Instances>
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
|
function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||||
const shapes = useMemo(() => {
|
|
||||||
|
const dotPositions = useMemo(() => {
|
||||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dotted-aisle') return [];
|
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dotted-aisle') return [];
|
||||||
|
|
||||||
const start = new THREE.Vector3(...aisle.points[0].position);
|
const start = new THREE.Vector3(...aisle.points[0].position);
|
||||||
const end = new THREE.Vector3(...aisle.points[1].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 dotSpacing = aisle.type.gapLength || 0.5;
|
||||||
const dotRadius = width * 0.6;
|
|
||||||
|
|
||||||
const totalLength = new THREE.Vector3().subVectors(end, start).length();
|
const totalLength = new THREE.Vector3().subVectors(end, start).length();
|
||||||
const dotCount = Math.floor((totalLength + (dotSpacing / 2)) / dotSpacing);
|
const dotCount = Math.floor((totalLength + (dotSpacing / 2)) / dotSpacing);
|
||||||
|
|
||||||
const shapes = [];
|
|
||||||
const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize();
|
const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize();
|
||||||
|
|
||||||
for (let i = 0; i < dotCount; i++) {
|
return Array.from({ length: dotCount }, (_, i) => {
|
||||||
const dotCenter = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * dotSpacing + dotSpacing / 2);
|
return 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]);
|
}, [aisle]);
|
||||||
|
|
||||||
if (shapes.length === 0) return null;
|
const { dotRadius, color } = useMemo(() => {
|
||||||
|
return {
|
||||||
|
dotRadius: aisle.type.aisleType === 'dotted-aisle' ? ((aisle.type as any).dotRadius || 0.1) * 0.6 : 0.06,
|
||||||
|
color: aisle.type.aisleColor || '#ffffff'
|
||||||
|
};
|
||||||
|
}, [aisle]);
|
||||||
|
|
||||||
|
if (dotPositions.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group
|
<group
|
||||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
||||||
rotation={[Math.PI / 2, 0, 0]}
|
|
||||||
>
|
>
|
||||||
{shapes.map((shape, index) => (
|
<Instances frustumCulled={false}>
|
||||||
<Extrude
|
<circleGeometry args={[dotRadius, 32]} />
|
||||||
key={index}
|
<meshStandardMaterial color={color} side={THREE.DoubleSide} />
|
||||||
args={[shape, { depth: 0.01, bevelEnabled: false }]}
|
{dotPositions.map((position, index) => (
|
||||||
receiveShadow
|
<Instance
|
||||||
castShadow
|
key={index}
|
||||||
>
|
position={[position.x, 0, position.z]}
|
||||||
<meshStandardMaterial
|
rotation={[-Math.PI / 2, 0, 0]}
|
||||||
color={aisle.type.aisleColor || '#ffffff'}
|
|
||||||
side={THREE.DoubleSide}
|
|
||||||
/>
|
/>
|
||||||
</Extrude>
|
))}
|
||||||
))}
|
</Instances>
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
|
function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
|
||||||
const arrows = useMemo(() => {
|
|
||||||
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrows-aisle') return [];
|
const { arrowGeometry, arrowInstances } = useMemo(() => {
|
||||||
|
const result = {
|
||||||
|
arrowGeometry: null as THREE.ExtrudeGeometry | null,
|
||||||
|
arrowInstances: [] as {
|
||||||
|
position: [number, number, number];
|
||||||
|
rotation: [number, number, number];
|
||||||
|
}[],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrows-aisle') return result;
|
||||||
|
|
||||||
const start = new THREE.Vector3(...aisle.points[0].position);
|
const start = new THREE.Vector3(...aisle.points[0].position);
|
||||||
const end = new THREE.Vector3(...aisle.points[1].position);
|
const end = new THREE.Vector3(...aisle.points[1].position);
|
||||||
@@ -420,55 +414,53 @@ function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
|
|||||||
direction.normalize();
|
direction.normalize();
|
||||||
|
|
||||||
const count = Math.floor((length + spacing) / (arrowLength + spacing));
|
const count = Math.floor((length + spacing) / (arrowLength + spacing));
|
||||||
|
const angle = Math.atan2(direction.x, direction.z) + Math.PI;
|
||||||
|
|
||||||
const arrowShapes: { shape: THREE.Shape; position: THREE.Vector3; rotationY: number }[] = [];
|
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);
|
||||||
|
|
||||||
|
result.arrowGeometry = new THREE.ExtrudeGeometry(shape, {
|
||||||
|
depth: 0.01,
|
||||||
|
bevelEnabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
result.arrowGeometry.rotateX(Math.PI / 2);
|
||||||
|
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
const initialOffset = arrowLength;
|
const offset = arrowLength + i * (arrowLength + spacing);
|
||||||
const center = new THREE.Vector3().copy(start).addScaledVector(direction, initialOffset + i * (arrowLength + spacing));
|
const center = new THREE.Vector3().copy(start).addScaledVector(direction, offset);
|
||||||
|
|
||||||
const shape = new THREE.Shape();
|
result.arrowInstances.push({
|
||||||
const w = width * 0.8;
|
position: [center.x, 0, center.z],
|
||||||
const h = arrowLength;
|
rotation: [0, angle, 0],
|
||||||
|
});
|
||||||
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;
|
return result;
|
||||||
}, [aisle]);
|
}, [aisle]);
|
||||||
|
|
||||||
if (arrows.length === 0) return null;
|
if (!arrowGeometry || arrowInstances.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group
|
<group
|
||||||
position={[0, (aisle.points[0].layer - 1) * Constants.wallConfig.height + 0.01, 0]}
|
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) => (
|
<Instances geometry={arrowGeometry} limit={100}>
|
||||||
<group key={index} position={[position.x, position.z, 0]} rotation={[0, 0, -rotationY]}>
|
<meshStandardMaterial color={aisle.type.aisleColor || '#ffffff'} side={THREE.DoubleSide} />
|
||||||
<Extrude
|
{arrowInstances.map(({ position, rotation }, i) => (
|
||||||
args={[shape, { depth: 0.01, bevelEnabled: false }]}
|
<Instance key={i} position={position} rotation={rotation} />
|
||||||
receiveShadow
|
))}
|
||||||
castShadow
|
</Instances>
|
||||||
>
|
|
||||||
<meshStandardMaterial
|
|
||||||
color={aisle.type.aisleColor || '#ffffff'}
|
|
||||||
side={THREE.DoubleSide}
|
|
||||||
/>
|
|
||||||
</Extrude>
|
|
||||||
</group>
|
|
||||||
))}
|
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ export default function PolygonGenerator({
|
|||||||
aisle.type.aisleType === "arc-aisle"
|
aisle.type.aisleType === "arc-aisle"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
arcAndCircleResult.forEach((arc) => {
|
arcAndCircleResult.forEach((arc) => {
|
||||||
const arcGroup = scene.getObjectByProperty("uuid", arc.aisleUuid);
|
const arcGroup = scene.getObjectByProperty("uuid", arc.aisleUuid);
|
||||||
if (!arcGroup) return;
|
if (!arcGroup) return;
|
||||||
@@ -60,7 +59,6 @@ export default function PolygonGenerator({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const wallPoints: THREE.Vector3[][] = walls
|
const wallPoints: THREE.Vector3[][] = walls
|
||||||
.map((wall) =>
|
.map((wall) =>
|
||||||
wall.points.map((pt) => new THREE.Vector3(pt.position[0], pt.position[1], pt.position[2]))
|
wall.points.map((pt) => new THREE.Vector3(pt.position[0], pt.position[1], pt.position[2]))
|
||||||
|
|||||||
Reference in New Issue
Block a user