refactor: Update aisle types and properties, integrate arc-aisle handling

This commit is contained in:
Jerald-Golden-B 2025-06-03 14:45:17 +05:30
parent da741ed6df
commit cba9edd7c4
9 changed files with 235 additions and 50 deletions

View File

@ -194,7 +194,7 @@ const AisleProperties: React.FC = () => {
} }
</> </>
); );
case 'junction-aisle': case 'junction-aisle': case 'arc-aisle':
return ( return (
<> <>
{aisleType && {aisleType &&
@ -206,7 +206,7 @@ const AisleProperties: React.FC = () => {
/> />
} }
</> </>
) );
default: default:
return null; return null;
} }

View File

@ -1,3 +1,4 @@
import ArcAisle from './aisleTypes/arcAisle';
import ArrowAisle from './aisleTypes/arrowAisle'; import ArrowAisle from './aisleTypes/arrowAisle';
import ArrowsAisle from './aisleTypes/arrowsAisle'; import ArrowsAisle from './aisleTypes/arrowsAisle';
import CircleAisle from './aisleTypes/circleAisle'; import CircleAisle from './aisleTypes/circleAisle';
@ -37,6 +38,10 @@ function AisleInstance({ aisle }: { readonly aisle: Aisle }) {
{aisle.type.aisleType === 'junction-aisle' && ( {aisle.type.aisleType === 'junction-aisle' && (
<JunctionAisle aisle={aisle} /> <JunctionAisle aisle={aisle} />
)} )}
{aisle.type.aisleType === 'arc-aisle' && (
<ArcAisle aisle={aisle} />
)}
</> </>
); );
} }

View File

@ -0,0 +1,103 @@
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'
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;

View File

@ -118,24 +118,6 @@ function AisleCreator() {
addAisle(aisle); addAisle(aisle);
setTempPoints([newPoint]); setTempPoints([newPoint]);
} }
} else if (aisleType === 'stripped-aisle') {
if (tempPoints.length === 0) {
setTempPoints([newPoint]);
setIsCreating(true);
} else {
const aisle: Aisle = {
aisleUuid: THREE.MathUtils.generateUUID(),
points: [tempPoints[0], newPoint],
type: {
aisleType: 'stripped-aisle',
aisleColor: aisleColor,
aisleWidth: aisleWidth
}
};
addAisle(aisle);
setTempPoints([newPoint]);
}
} else if (aisleType === 'dotted-aisle') { } else if (aisleType === 'dotted-aisle') {
if (tempPoints.length === 0) { if (tempPoints.length === 0) {
@ -194,7 +176,24 @@ function AisleCreator() {
setTempPoints([newPoint]); setTempPoints([newPoint]);
} }
} else if (aisleType === 'arc-aisle') { } else if (aisleType === 'arc-aisle') {
console.log('Creating 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);
setTempPoints([newPoint]);
}
} else if (aisleType === 'circle-aisle') { } else if (aisleType === 'circle-aisle') {
if (tempPoints.length === 0) { if (tempPoints.length === 0) {

View File

@ -58,7 +58,7 @@ function ReferenceAisle({ tempPoints }: Readonly<ReferenceAisleProps>) {
if (!finalPosition.current) return; if (!finalPosition.current) return;
if (aisleType === 'solid-aisle' || aisleType === 'stripped-aisle') { if (aisleType === 'solid-aisle') {
setTempAisle({ setTempAisle({
aisleUuid: 'temp-aisle', aisleUuid: 'temp-aisle',
points: [ points: [
@ -153,9 +153,7 @@ function ReferenceAisle({ tempPoints }: Readonly<ReferenceAisleProps>) {
gapLength: gapLength gapLength: gapLength
} }
}); });
} else if (aisleType === 'arc-aisle') { } else if (aisleType === 'junction-aisle' || aisleType === 'arc-aisle') {
console.log();
} else if (aisleType === 'junction-aisle') {
setTempAisle({ setTempAisle({
aisleUuid: 'temp-aisle', aisleUuid: 'temp-aisle',
points: [ points: [
@ -203,6 +201,8 @@ function ReferenceAisle({ tempPoints }: Readonly<ReferenceAisleProps>) {
return <CircleAisle aisle={tempAisle} />; return <CircleAisle aisle={tempAisle} />;
case 'junction-aisle': case 'junction-aisle':
return <JunctionAisle aisle={tempAisle} />; return <JunctionAisle aisle={tempAisle} />;
case 'arc-aisle':
return <ArcAisle aisle={tempAisle} />
default: default:
return null; return null;
} }
@ -211,7 +211,6 @@ function ReferenceAisle({ tempPoints }: Readonly<ReferenceAisleProps>) {
const textPosition = new THREE.Vector3().addVectors(new THREE.Vector3(...tempAisle.points[0].position), new THREE.Vector3(...tempAisle.points[1].position)).divideScalar(2); 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 distance = new THREE.Vector3(...tempAisle.points[0].position).distanceTo(new THREE.Vector3(...tempAisle.points[1].position));
const rendertext = () => { const rendertext = () => {
return ( return (
<> <>
@ -706,3 +705,82 @@ function JunctionAisle({ aisle }: { readonly aisle: Aisle }) {
</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>
);
}

View File

@ -33,8 +33,13 @@ const BoundingBox = ({ boundingBoxRef, isPerAsset = true }: BoundingBoxProps) =>
if (selectedAssets.length === 0) return []; if (selectedAssets.length === 0) return [];
if (isPerAsset) { if (isPerAsset) {
return selectedAssets.map((obj: any) => { return selectedAssets.map((obj: THREE.Object3D) => {
const box = new THREE.Box3().setFromObject(obj.clone()); const position = obj.position;
const rotation = obj.getWorldQuaternion(new THREE.Quaternion());
const clone = obj.clone();
clone.position.set(0, 0, 0);
clone.rotation.set(0, 0, 0);
const box = new THREE.Box3().setFromObject(clone);
const size = new THREE.Vector3(); const size = new THREE.Vector3();
const center = new THREE.Vector3(); const center = new THREE.Vector3();
box.getSize(size); box.getSize(size);
@ -46,7 +51,8 @@ const BoundingBox = ({ boundingBoxRef, isPerAsset = true }: BoundingBoxProps) =>
return { return {
points: getBoxLines(min, max), points: getBoxLines(min, max),
position: center.toArray(), position: [position.x, center.y, position.z],
rotation: rotation.toArray(),
size: size.toArray(), size: size.toArray(),
}; };
}); });
@ -66,6 +72,7 @@ const BoundingBox = ({ boundingBoxRef, isPerAsset = true }: BoundingBoxProps) =>
{ {
points: getBoxLines(min, max), points: getBoxLines(min, max),
position: center.toArray(), position: center.toArray(),
rotation: [0, 0, 0, 1],
size: size.toArray(), size: size.toArray(),
}, },
]; ];
@ -75,7 +82,10 @@ const BoundingBox = ({ boundingBoxRef, isPerAsset = true }: BoundingBoxProps) =>
return ( return (
<> <>
{boxes.map((box: any, index: number) => ( {boxes.map((box: any, index: number) => (
<group key={index} name="SelectionGroupBoundingBoxLine"> <group
key={index}
name="SelectionGroupBoundingBoxLine"
>
<Line <Line
name="SelectionGroupBoundingBox" name="SelectionGroupBoundingBox"
depthWrite={false} depthWrite={false}
@ -83,12 +93,15 @@ const BoundingBox = ({ boundingBoxRef, isPerAsset = true }: BoundingBoxProps) =>
color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"} color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"}
lineWidth={2.7} lineWidth={2.7}
segments segments
position={[box.position[0], 0, box.position[2]]}
quaternion={new THREE.Quaternion(...box.rotation)}
/> />
<mesh <mesh
name="SelectionGroupBoundingLine" name="SelectionGroupBoundingLine"
ref={index === 0 ? boundingBoxRef : null} ref={index === 0 ? boundingBoxRef : null}
visible={false} visible={false}
position={box.position} position={box.position}
quaternion={new THREE.Quaternion(...box.rotation)}
> >
<boxGeometry args={box.size} /> <boxGeometry args={box.size} />
<meshBasicMaterial /> <meshBasicMaterial />

View File

@ -309,7 +309,7 @@ const SelectionControls: React.FC = () => {
<> <>
<group name="SelectionGroup"> <group name="SelectionGroup">
<group ref={selectionGroup} name="selectionAssetGroup"> <group ref={selectionGroup} name="selectionAssetGroup">
<BoundingBox boundingBoxRef={boundingBoxRef} /> <BoundingBox boundingBoxRef={boundingBoxRef} isPerAsset />
</group> </group>
</group> </group>

View File

@ -18,7 +18,6 @@ interface AisleStore {
aisleUuid: string, aisleUuid: string,
props: { aisleWidth?: number; dashLength?: number; gapLength?: number } props: { aisleWidth?: number; dashLength?: number; gapLength?: number }
) => void; ) => void;
setStrippedAisleWidth: (aisleUuid: string, width: number) => void;
setDottedAisleProperties: ( setDottedAisleProperties: (
aisleUuid: string, aisleUuid: string,
props: { dotRadius?: number; gapLength?: number } props: { dotRadius?: number; gapLength?: number }
@ -28,7 +27,7 @@ interface AisleStore {
aisleUuid: string, aisleUuid: string,
props: { aisleWidth?: number; aisleLength?: number; gapLength?: number } props: { aisleWidth?: number; aisleLength?: number; gapLength?: number }
) => void; ) => void;
setArcAisleWidth: (aisleUuid: string, width: number) => void; setArcAisleWidth: (aisleUuid: string, props: { aisleWidth?: number; isFlipped: boolean; }) => void;
setCircleAisleWidth: (aisleUuid: string, width: number) => void; setCircleAisleWidth: (aisleUuid: string, width: number) => void;
setJunctionAisleProperties: (aisleUuid: string, props: { aisleWidth?: number; isFlipped: boolean; }) => void; setJunctionAisleProperties: (aisleUuid: string, props: { aisleWidth?: number; isFlipped: boolean; }) => void;
@ -73,7 +72,6 @@ export const useAisleStore = create<AisleStore>()(
return true; return true;
}); });
}); });
console.log('removedAisles: ', removedAisles);
return removedAisles; return removedAisles;
}, },
@ -119,13 +117,6 @@ export const useAisleStore = create<AisleStore>()(
} }
}), }),
setStrippedAisleWidth: (aisleUuid, width) => set((state) => {
const aisle = state.aisles.find(a => a.aisleUuid === aisleUuid);
if (aisle && aisle.type.aisleType === 'stripped-aisle') {
aisle.type.aisleWidth = width;
}
}),
setDottedAisleProperties: (aisleUuid, props) => set((state) => { setDottedAisleProperties: (aisleUuid, props) => set((state) => {
const aisle = state.aisles.find(a => a.aisleUuid === aisleUuid); const aisle = state.aisles.find(a => a.aisleUuid === aisleUuid);
if (aisle && aisle.type.aisleType === 'dotted-aisle') { if (aisle && aisle.type.aisleType === 'dotted-aisle') {
@ -150,10 +141,11 @@ export const useAisleStore = create<AisleStore>()(
} }
}), }),
setArcAisleWidth: (aisleUuid, width) => set((state) => { setArcAisleWidth: (aisleUuid, props) => set((state) => {
const aisle = state.aisles.find(a => a.aisleUuid === aisleUuid); const aisle = state.aisles.find(a => a.aisleUuid === aisleUuid);
if (aisle && aisle.type.aisleType === 'arc-aisle') { if (aisle && aisle.type.aisleType === 'arc-aisle') {
aisle.type.aisleWidth = width; if (props.aisleWidth !== undefined) aisle.type.aisleWidth = props.aisleWidth;
if (props.isFlipped !== undefined) aisle.type.isFlipped = props.isFlipped;
} }
}), }),

View File

@ -59,12 +59,6 @@ interface DashedAisle {
gapLength: number; gapLength: number;
} }
interface StrippedAisle {
aisleType: 'stripped-aisle';
aisleColor: AisleColors;
aisleWidth: number;
}
interface DottedAisle { interface DottedAisle {
aisleType: 'dotted-aisle'; aisleType: 'dotted-aisle';
aisleColor: AisleColors; aisleColor: AisleColors;
@ -90,6 +84,7 @@ interface ArcAisle {
aisleType: 'arc-aisle'; aisleType: 'arc-aisle';
aisleColor: AisleColors; aisleColor: AisleColors;
aisleWidth: number; aisleWidth: number;
isFlipped: boolean;
} }
interface CircleAisle { interface CircleAisle {
@ -105,7 +100,7 @@ interface JunctionAisle {
isFlipped: boolean; isFlipped: boolean;
} }
type AisleType = SolidAisle | DashedAisle | StrippedAisle | DottedAisle | ArrowAisle | ArrowsAisle | ArcAisle | CircleAisle | JunctionAisle; type AisleType = SolidAisle | DashedAisle | DottedAisle | ArrowAisle | ArrowsAisle | ArcAisle | CircleAisle | JunctionAisle;
interface Aisle { interface Aisle {
aisleUuid: string; aisleUuid: string;