feat: Refactor aisle rendering components to use Instances for improved performance and rendering efficiency

This commit is contained in:
2025-08-01 09:59:50 +05:30
parent 43dbf9dfaf
commit 0804c22a0b
6 changed files with 198 additions and 219 deletions

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo } from 'react';
import React, { useMemo } from 'react';
import { useToggleView } from '../../../../store/builder/store';
import AisleInstance from './instance/aisleInstance';
import Point from '../../point/point';

View File

@@ -1,6 +1,6 @@
import * as THREE from 'three';
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 { useToolMode } from '../../../../../../store/builder/store';
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
@@ -10,8 +10,16 @@ function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
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 end = new THREE.Vector3(...aisle.points[1].position);
@@ -24,69 +32,67 @@ function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
direction.normalize();
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++) {
const initialOffset = arrowLength;
const center = new THREE.Vector3().copy(start).addScaledVector(direction, initialOffset + i * (arrowLength + spacing));
const offset = arrowLength + i * (arrowLength + spacing);
const center = new THREE.Vector3().copy(start).addScaledVector(direction, offset);
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 });
result.arrowInstances.push({
position: [center.x, 0, center.z],
rotation: [0, angle, 0],
});
}
return arrowShapes;
return result;
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
}
}
};
if (arrows.length === 0) return null;
if (!arrowGeometry || arrowInstances.length === 0) return null;
return (
<group
name='Arrows-Aisle'
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}
onDoubleClick={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>
))}
<Instances geometry={arrowGeometry}>
<meshStandardMaterial color={aisle.type.aisleColor || '#ffffff'} side={THREE.DoubleSide} />
{arrowInstances.map(({ position, rotation }, i) => (
<Instance key={i} position={position} rotation={rotation} />
))}
</Instances>
</group>
);
}

View File

@@ -1,6 +1,6 @@
import * as THREE from 'three';
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 { useToolMode } from '../../../../../../store/builder/store';
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
@@ -10,82 +10,71 @@ function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const shapes = useMemo(() => {
const dashInstances = 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 totalVec = new THREE.Vector3().subVectors(end, start);
const totalLength = totalVec.length();
const direction = totalVec.clone().normalize();
const segmentCount = Math.floor((totalLength + gapLength) / (dashLength + gapLength));
const shapes = [];
const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize();
const instances = [];
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 center = start.clone().addScaledVector(direction, i * (dashLength + gapLength) + dashLength / 2);
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 rotationY = Math.atan2(direction.x, direction.z);
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);
instances.push({
position: [center.x, 0, center.z] as [number, number, number],
scale: [width, 0.001, dashLength] as [number, number, number],
rotation: [0, rotationY, 0] as [number, number, number],
});
}
return shapes;
return instances;
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
}
}
};
if (shapes.length === 0) return null;
if (dashInstances.length === 0) return null;
return (
<group
name='Dashed-Aisle'
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}
onDoubleClick={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}
<Instances frustumCulled={false}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={aisle.type.aisleColor || '#ffffff'} />
{dashInstances.map((inst, i) => (
<Instance
key={i}
position={inst.position}
scale={inst.scale}
rotation={inst.rotation}
/>
</Extrude>
))}
))}
</Instances>
</group>
);
}
export default DashedAisle;
export default DashedAisle;

View File

@@ -1,6 +1,6 @@
import * as THREE from 'three';
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 { useToolMode } from '../../../../../../store/builder/store';
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
@@ -10,31 +10,20 @@ function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const shapes = useMemo(() => {
const dotPositions = 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;
return Array.from({ length: dotCount }, (_, i) => {
return new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * dotSpacing + dotSpacing / 2);
});
}, [aisle]);
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 (
<group
@@ -51,26 +47,24 @@ function DottedAisle({ aisle }: { readonly aisle: 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]}
rotation={[0, 0, 0]}
userData={aisle}
onDoubleClick={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}
<Instances frustumCulled={false}>
<circleGeometry args={[dotRadius, 32]} />
<meshStandardMaterial color={color} side={THREE.DoubleSide} />
{dotPositions.map((position, index) => (
<Instance
key={index}
position={[position.x, 0, position.z]}
rotation={[-Math.PI / 2, 0, 0]}
/>
</Extrude>
))}
))}
</Instances>
</group>
);
}

View File

@@ -3,7 +3,7 @@ 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 { Extrude, Html, Instance, Instances } from '@react-three/drei';
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
import { useDirectionalSnapping } from '../../point/helpers/useDirectionalSnapping';
import { usePointSnapping } from '../../point/helpers/usePointSnapping';
@@ -289,125 +289,119 @@ function SolidAisle({ 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 [];
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 totalVec = new THREE.Vector3().subVectors(end, start);
const totalLength = totalVec.length();
const direction = totalVec.clone().normalize();
const segmentCount = Math.floor((totalLength + gapLength) / (dashLength + gapLength));
const shapes = [];
const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize();
const instances = [];
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 center = start.clone().addScaledVector(direction, i * (dashLength + gapLength) + dashLength / 2);
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 rotationY = Math.atan2(direction.x, direction.z);
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);
instances.push({
position: [center.x, 0, center.z] as [number, number, number],
scale: [width, 0.001, dashLength] as [number, number, number],
rotation: [0, rotationY, 0] as [number, number, number],
});
}
return shapes;
return instances;
}, [aisle]);
if (shapes.length === 0) return null;
if (dashInstances.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}
<Instances frustumCulled={false}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={aisle.type.aisleColor || '#ffffff'} />
{dashInstances.map((inst, i) => (
<Instance
key={i}
position={inst.position}
scale={inst.scale}
rotation={inst.rotation}
/>
</Extrude>
))}
))}
</Instances>
</group>
);
}
function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
const shapes = useMemo(() => {
const dotPositions = 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;
return Array.from({ length: dotCount }, (_, i) => {
return new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * dotSpacing + dotSpacing / 2);
});
}, [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 (
<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}
<Instances frustumCulled={false}>
<circleGeometry args={[dotRadius, 32]} />
<meshStandardMaterial color={color} side={THREE.DoubleSide} />
{dotPositions.map((position, index) => (
<Instance
key={index}
position={[position.x, 0, position.z]}
rotation={[-Math.PI / 2, 0, 0]}
/>
</Extrude>
))}
))}
</Instances>
</group>
);
}
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 end = new THREE.Vector3(...aisle.points[1].position);
@@ -420,55 +414,53 @@ function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
direction.normalize();
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++) {
const initialOffset = arrowLength;
const center = new THREE.Vector3().copy(start).addScaledVector(direction, initialOffset + i * (arrowLength + spacing));
const offset = arrowLength + i * (arrowLength + spacing);
const center = new THREE.Vector3().copy(start).addScaledVector(direction, offset);
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 });
result.arrowInstances.push({
position: [center.x, 0, center.z],
rotation: [0, angle, 0],
});
}
return arrowShapes;
return result;
}, [aisle]);
if (arrows.length === 0) return null;
if (!arrowGeometry || arrowInstances.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>
))}
<Instances geometry={arrowGeometry} limit={100}>
<meshStandardMaterial color={aisle.type.aisleColor || '#ffffff'} side={THREE.DoubleSide} />
{arrowInstances.map(({ position, rotation }, i) => (
<Instance key={i} position={position} rotation={rotation} />
))}
</Instances>
</group>
);
}

View File

@@ -40,7 +40,6 @@ export default function PolygonGenerator({
aisle.type.aisleType === "arc-aisle"
)
arcAndCircleResult.forEach((arc) => {
const arcGroup = scene.getObjectByProperty("uuid", arc.aisleUuid);
if (!arcGroup) return;
@@ -60,7 +59,6 @@ export default function PolygonGenerator({
});
});
const wallPoints: THREE.Vector3[][] = walls
.map((wall) =>
wall.points.map((pt) => new THREE.Vector3(pt.position[0], pt.position[1], pt.position[2]))