- Added useRedoHandler to manage redo actions, including socket communication for wall, floor, zone, and aisle updates. - Added useUndoHandler to manage undo actions, reversing the effects of previous actions with corresponding socket updates. - Created UndoRedo2DControls component to handle keyboard shortcuts for undo (Ctrl+Z) and redo (Ctrl+Y). - Established a Zustand store (useUndoRedo2DStore) to maintain undo and redo stacks, with methods for pushing, popping, and peeking actions.
716 lines
29 KiB
TypeScript
716 lines
29 KiB
TypeScript
import * as THREE from 'three';
|
|
import * as Constants from '../../../types/world/worldConstants';
|
|
import { useRef, useState, useEffect, useMemo } from 'react';
|
|
import { useSocketStore, useToolMode } from '../../../store/builder/store';
|
|
import { DragControls } from '@react-three/drei';
|
|
import { useThree } from '@react-three/fiber';
|
|
import { useBuilderStore } from '../../../store/builder/useBuilderStore';
|
|
import { useSelectedPoints } from '../../../store/simulation/useSimulationStore';
|
|
import { usePointSnapping } from './helpers/usePointSnapping';
|
|
import { useParams } from 'react-router-dom';
|
|
import { useVersionContext } from '../version/versionContext';
|
|
import { useSceneContext } from '../../scene/sceneContext';
|
|
|
|
// import { upsertAisleApi } from '../../../services/factoryBuilder/aisle/upsertAisleApi';
|
|
// import { deleteAisleApi } from '../../../services/factoryBuilder/aisle/deleteAisleApi';
|
|
// import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi';
|
|
// import { deleteWallApi } from '../../../services/factoryBuilder/wall/deleteWallApi';
|
|
// import { upsertFloorApi } from '../../../services/factoryBuilder/floor/upsertFloorApi';
|
|
// import { deleteFloorApi } from '../../../services/factoryBuilder/floor/deleteFloorApi';
|
|
// import { upsertZoneApi } from '../../../services/factoryBuilder/zone/upsertZoneApi';
|
|
// import { deleteZoneApi } from '../../../services/factoryBuilder/zone/deleteZoneApi';
|
|
|
|
import { getUserData } from '../../../functions/getUserData';
|
|
import { handleCanvasCursors } from '../../../utils/mouseUtils/handleCanvasCursors';
|
|
|
|
function Point({ point }: { readonly point: Point }) {
|
|
const materialRef = useRef<THREE.ShaderMaterial>(null);
|
|
const { raycaster, camera, pointer } = useThree();
|
|
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
|
const [isHovered, setIsHovered] = useState(false);
|
|
const [isSelected, setIsSelected] = useState(false);
|
|
const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
|
|
const { socket } = useSocketStore();
|
|
const { toolMode } = useToolMode();
|
|
const { aisleStore, wallStore, floorStore, zoneStore, undoRedo2DStore } = useSceneContext();
|
|
const { push2D } = undoRedo2DStore();
|
|
const { setPosition: setAislePosition, removePoint: removeAislePoint, getAislesByPointId } = aisleStore();
|
|
const { setPosition: setWallPosition, removePoint: removeWallPoint, getWallsByPointId } = wallStore();
|
|
const { setPosition: setFloorPosition, removePoint: removeFloorPoint, getFloorsByPointId } = floorStore();
|
|
const { setPosition: setZonePosition, removePoint: removeZonePoint, getZonesByPointId } = zoneStore();
|
|
const { snapAislePoint, snapAisleAngle, snapWallPoint, snapWallAngle, snapFloorPoint, snapFloorAngle, snapZonePoint, snapZoneAngle } = usePointSnapping({ uuid: point.pointUuid, pointType: point.pointType, position: point.position });
|
|
const { hoveredPoint, hoveredLine, setHoveredPoint } = useBuilderStore();
|
|
const { selectedPoints } = useSelectedPoints();
|
|
const { userId, organization } = getUserData();
|
|
const { selectedVersionStore } = useVersionContext();
|
|
const { selectedVersion } = selectedVersionStore();
|
|
const { projectId } = useParams();
|
|
const boxScale: [number, number, number] = Constants.pointConfig.boxScale;
|
|
const colors = getColor(point);
|
|
|
|
const [initialPositions, setInitialPositions] = useState<{
|
|
aisles?: Aisle[],
|
|
walls?: Wall[],
|
|
floors?: Floor[],
|
|
zones?: Zone[]
|
|
}>({});
|
|
|
|
useEffect(() => {
|
|
handleCanvasCursors('default');
|
|
}, [toolMode])
|
|
|
|
function getColor(point: Point) {
|
|
if (point.pointType === 'Aisle') {
|
|
return {
|
|
defaultInnerColor: Constants.pointConfig.defaultInnerColor,
|
|
defaultOuterColor: Constants.pointConfig.aisleOuterColor,
|
|
defaultDeleteColor: Constants.pointConfig.deleteColor,
|
|
}
|
|
} else if (point.pointType === 'Floor') {
|
|
return {
|
|
defaultInnerColor: Constants.pointConfig.defaultInnerColor,
|
|
defaultOuterColor: Constants.pointConfig.floorOuterColor,
|
|
defaultDeleteColor: Constants.pointConfig.deleteColor,
|
|
}
|
|
} else if (point.pointType === 'Wall') {
|
|
return {
|
|
defaultInnerColor: Constants.pointConfig.defaultInnerColor,
|
|
defaultOuterColor: Constants.pointConfig.wallOuterColor,
|
|
defaultDeleteColor: Constants.pointConfig.deleteColor,
|
|
}
|
|
} else if (point.pointType === 'Zone') {
|
|
return {
|
|
defaultInnerColor: Constants.pointConfig.defaultInnerColor,
|
|
defaultOuterColor: Constants.pointConfig.zoneOuterColor,
|
|
defaultDeleteColor: Constants.pointConfig.deleteColor,
|
|
}
|
|
} else {
|
|
return {
|
|
defaultInnerColor: Constants.pointConfig.defaultInnerColor,
|
|
defaultOuterColor: Constants.pointConfig.defaultOuterColor,
|
|
defaultDeleteColor: Constants.pointConfig.deleteColor,
|
|
}
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (materialRef.current && (toolMode === 'move' || toolMode === '2D-Delete')) {
|
|
let innerColor;
|
|
let outerColor;
|
|
if (isHovered) {
|
|
innerColor = toolMode === '2D-Delete' ? colors.defaultDeleteColor : colors.defaultOuterColor;
|
|
outerColor = toolMode === '2D-Delete' ? colors.defaultDeleteColor : colors.defaultOuterColor;
|
|
} else {
|
|
innerColor = colors.defaultInnerColor;
|
|
outerColor = colors.defaultOuterColor;
|
|
}
|
|
materialRef.current.uniforms.uInnerColor.value.set(innerColor);
|
|
materialRef.current.uniforms.uOuterColor.value.set(outerColor);
|
|
materialRef.current.uniformsNeedUpdate = true;
|
|
} else if (materialRef.current && toolMode !== 'move') {
|
|
materialRef.current.uniforms.uInnerColor.value.set(colors.defaultInnerColor);
|
|
materialRef.current.uniforms.uOuterColor.value.set(colors.defaultOuterColor);
|
|
materialRef.current.uniformsNeedUpdate = true;
|
|
}
|
|
}, [isHovered, colors.defaultInnerColor, colors.defaultOuterColor, colors.defaultDeleteColor, toolMode]);
|
|
|
|
const uniforms = useMemo(() => ({
|
|
uOuterColor: { value: new THREE.Color(colors.defaultOuterColor) },
|
|
uInnerColor: { value: new THREE.Color(colors.defaultInnerColor) },
|
|
}), [colors.defaultInnerColor, colors.defaultOuterColor]);
|
|
|
|
const handleDrag = (point: Point) => {
|
|
if (toolMode === 'move' && isHovered && dragOffset) {
|
|
raycaster.setFromCamera(pointer, camera);
|
|
const intersectionPoint = new THREE.Vector3();
|
|
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
|
|
|
if (hit) {
|
|
handleCanvasCursors('grabbing');
|
|
const positionWithOffset = new THREE.Vector3().addVectors(hit, dragOffset);
|
|
const newPosition: [number, number, number] = [positionWithOffset.x, positionWithOffset.y, positionWithOffset.z];
|
|
|
|
if (point.pointType === 'Aisle') {
|
|
const aisleSnapped = snapAisleAngle(newPosition);
|
|
const finalSnapped = snapAislePoint(aisleSnapped.position);
|
|
setAislePosition(point.pointUuid, finalSnapped.position);
|
|
} else if (point.pointType === 'Wall') {
|
|
const wallSnapped = snapWallAngle(newPosition);
|
|
const finalSnapped = snapWallPoint(wallSnapped.position);
|
|
setWallPosition(point.pointUuid, finalSnapped.position);
|
|
} else if (point.pointType === 'Floor') {
|
|
const floorSnapped = snapFloorAngle(newPosition);
|
|
const finalSnapped = snapFloorPoint(floorSnapped.position);
|
|
setFloorPosition(point.pointUuid, finalSnapped.position);
|
|
} else if (point.pointType === 'Zone') {
|
|
const zoneSnapped = snapZoneAngle(newPosition);
|
|
const finalSnapped = snapZonePoint(zoneSnapped.position);
|
|
setZonePosition(point.pointUuid, finalSnapped.position);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleDragStart = (point: Point) => {
|
|
raycaster.setFromCamera(pointer, camera);
|
|
const intersectionPoint = new THREE.Vector3();
|
|
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
|
|
|
if (hit) {
|
|
const currentPosition = new THREE.Vector3(...point.position);
|
|
const offset = new THREE.Vector3().subVectors(currentPosition, hit);
|
|
setDragOffset(offset);
|
|
|
|
if (point.pointType === 'Aisle') {
|
|
const aisles = getAislesByPointId(point.pointUuid);
|
|
setInitialPositions({ aisles });
|
|
} else if (point.pointType === 'Wall') {
|
|
const walls = getWallsByPointId(point.pointUuid);
|
|
setInitialPositions({ walls });
|
|
} else if (point.pointType === 'Floor') {
|
|
const floors = getFloorsByPointId(point.pointUuid);
|
|
setInitialPositions({ floors });
|
|
} else if (point.pointType === 'Zone') {
|
|
const zones = getZonesByPointId(point.pointUuid);
|
|
setInitialPositions({ zones });
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleDragEnd = (point: Point) => {
|
|
handleCanvasCursors('default');
|
|
setDragOffset(null);
|
|
if (toolMode !== 'move') return;
|
|
|
|
if (point.pointType === 'Aisle') {
|
|
const updatedAisles = getAislesByPointId(point.pointUuid);
|
|
if (updatedAisles.length > 0 && projectId) {
|
|
updatedAisles.forEach((updatedAisle) => {
|
|
|
|
// API
|
|
|
|
// upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '');
|
|
|
|
// SOCKET
|
|
|
|
socket.emit('v1:model-aisle:add', {
|
|
projectId: projectId,
|
|
versionId: selectedVersion?.versionId || '',
|
|
userId: userId,
|
|
organization: organization,
|
|
aisleUuid: updatedAisle.aisleUuid,
|
|
points: updatedAisle.points,
|
|
type: updatedAisle.type
|
|
})
|
|
})
|
|
|
|
if (initialPositions.aisles && initialPositions.aisles.length > 0) {
|
|
const updatedPoints = initialPositions.aisles.map((aisle) => ({
|
|
type: "Aisle" as const,
|
|
lineData: aisle,
|
|
newData: updatedAisles.find(a => a.aisleUuid === aisle.aisleUuid),
|
|
timeStamp: new Date().toISOString(),
|
|
}));
|
|
|
|
push2D({
|
|
type: 'Draw',
|
|
actions: [{
|
|
actionType: 'Lines-Update',
|
|
points: updatedPoints,
|
|
}]
|
|
});
|
|
}
|
|
}
|
|
} else if (point.pointType === 'Wall') {
|
|
const updatedWalls = getWallsByPointId(point.pointUuid);
|
|
if (updatedWalls && updatedWalls.length > 0 && projectId) {
|
|
updatedWalls.forEach((updatedWall) => {
|
|
|
|
// API
|
|
|
|
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
|
|
|
|
// SOCKET
|
|
|
|
const data = {
|
|
wallData: updatedWall,
|
|
projectId: projectId,
|
|
versionId: selectedVersion?.versionId || '',
|
|
userId: userId,
|
|
organization: organization
|
|
}
|
|
|
|
socket.emit('v1:model-Wall:add', data);
|
|
});
|
|
}
|
|
|
|
if (initialPositions.walls && initialPositions.walls.length > 0) {
|
|
const updatedPoints = initialPositions.walls.map((wall) => ({
|
|
type: "Wall" as const,
|
|
lineData: wall,
|
|
newData: updatedWalls.find(w => w.wallUuid === wall.wallUuid),
|
|
timeStamp: new Date().toISOString(),
|
|
}));
|
|
|
|
push2D({
|
|
type: 'Draw',
|
|
actions: [{
|
|
actionType: 'Lines-Update',
|
|
points: updatedPoints,
|
|
}]
|
|
});
|
|
}
|
|
} else if (point.pointType === 'Floor') {
|
|
const updatedFloors = getFloorsByPointId(point.pointUuid);
|
|
if (updatedFloors && updatedFloors.length > 0 && projectId) {
|
|
updatedFloors.forEach((updatedFloor) => {
|
|
|
|
// API
|
|
|
|
// upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
|
|
|
|
// SOCKET
|
|
|
|
const data = {
|
|
floorData: updatedFloor,
|
|
projectId: projectId,
|
|
versionId: selectedVersion?.versionId || '',
|
|
userId: userId,
|
|
organization: organization
|
|
}
|
|
|
|
socket.emit('v1:model-Floor:add', data);
|
|
});
|
|
}
|
|
|
|
if (initialPositions.floors && initialPositions.floors.length > 0) {
|
|
const updatedPoints = initialPositions.floors.map((floor) => ({
|
|
type: "Floor" as const,
|
|
lineData: floor,
|
|
newData: updatedFloors.find(f => f.floorUuid === floor.floorUuid),
|
|
timeStamp: new Date().toISOString(),
|
|
}));
|
|
|
|
push2D({
|
|
type: 'Draw',
|
|
actions: [{
|
|
actionType: 'Lines-Update',
|
|
points: updatedPoints,
|
|
}]
|
|
});
|
|
}
|
|
} else if (point.pointType === 'Zone') {
|
|
const updatedZones = getZonesByPointId(point.pointUuid);
|
|
if (updatedZones && updatedZones.length > 0 && projectId) {
|
|
updatedZones.forEach((updatedZone) => {
|
|
|
|
// API
|
|
|
|
// upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone);
|
|
|
|
// SOCKET
|
|
|
|
const data = {
|
|
zoneData: updatedZone,
|
|
projectId: projectId,
|
|
versionId: selectedVersion?.versionId || '',
|
|
userId: userId,
|
|
organization: organization
|
|
}
|
|
|
|
socket.emit('v1:zone:add', data);
|
|
});
|
|
}
|
|
|
|
if (initialPositions.zones && initialPositions.zones.length > 0) {
|
|
const updatedPoints = initialPositions.zones.map((zone) => ({
|
|
type: "Zone" as const,
|
|
lineData: zone,
|
|
newData: updatedZones.find(z => z.zoneUuid === zone.zoneUuid),
|
|
timeStamp: new Date().toISOString(),
|
|
}));
|
|
|
|
push2D({
|
|
type: 'Draw',
|
|
actions: [{
|
|
actionType: 'Lines-Update',
|
|
points: updatedPoints,
|
|
}]
|
|
});
|
|
}
|
|
}
|
|
|
|
setInitialPositions({});
|
|
}
|
|
|
|
const handlePointClick = (point: Point) => {
|
|
if (toolMode === '2D-Delete') {
|
|
if (point.pointType === 'Aisle') {
|
|
const removedAisles = removeAislePoint(point.pointUuid);
|
|
setHoveredPoint(null);
|
|
if (removedAisles.length > 0) {
|
|
removedAisles.forEach(aisle => {
|
|
if (projectId) {
|
|
|
|
// API
|
|
|
|
// deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || '');
|
|
|
|
// SOCKET
|
|
|
|
const data = {
|
|
projectId: projectId,
|
|
versionId: selectedVersion?.versionId || '',
|
|
userId: userId,
|
|
organization: organization,
|
|
aisleUuid: aisle.aisleUuid
|
|
}
|
|
|
|
socket.emit('v1:model-aisle:delete', data);
|
|
|
|
}
|
|
});
|
|
|
|
const removedAislesData = removedAisles.map((aisle) => ({
|
|
type: "Aisle" as const,
|
|
lineData: aisle,
|
|
timeStamp: new Date().toISOString(),
|
|
}));
|
|
|
|
push2D({
|
|
type: 'Draw',
|
|
actions: [
|
|
{
|
|
actionType: 'Lines-Delete',
|
|
points: removedAislesData
|
|
}
|
|
]
|
|
});
|
|
}
|
|
}
|
|
if (point.pointType === 'Wall') {
|
|
const removedWalls = removeWallPoint(point.pointUuid);
|
|
if (removedWalls.length > 0) {
|
|
setHoveredPoint(null);
|
|
removedWalls.forEach(wall => {
|
|
if (projectId) {
|
|
|
|
// API
|
|
|
|
// deleteWallApi(projectId, selectedVersion?.versionId || '', wall.wallUuid);
|
|
|
|
// SOCKET
|
|
|
|
const data = {
|
|
wallUuid: wall.wallUuid,
|
|
projectId: projectId,
|
|
versionId: selectedVersion?.versionId || '',
|
|
userId: userId,
|
|
organization: organization
|
|
}
|
|
|
|
socket.emit('v1:model-Wall:delete', data);
|
|
}
|
|
});
|
|
|
|
const removedWallsData = removedWalls.map((wall) => ({
|
|
type: "Wall" as const,
|
|
lineData: wall,
|
|
timeStamp: new Date().toISOString(),
|
|
}));
|
|
|
|
push2D({
|
|
type: 'Draw',
|
|
actions: [
|
|
{
|
|
actionType: 'Lines-Delete',
|
|
points: removedWallsData
|
|
}
|
|
]
|
|
});
|
|
}
|
|
}
|
|
if (point.pointType === 'Floor') {
|
|
const Floors = getFloorsByPointId(point.pointUuid);
|
|
const { removedFloors, updatedFloors } = removeFloorPoint(point.pointUuid);
|
|
setHoveredPoint(null);
|
|
if (removedFloors.length > 0) {
|
|
removedFloors.forEach(floor => {
|
|
if (projectId) {
|
|
|
|
// API
|
|
|
|
// deleteFloorApi(projectId, selectedVersion?.versionId || '', floor.floorUuid);
|
|
|
|
// SOCKET
|
|
|
|
const data = {
|
|
floorUuid: floor.floorUuid,
|
|
projectId: projectId,
|
|
versionId: selectedVersion?.versionId || '',
|
|
userId: userId,
|
|
organization: organization
|
|
}
|
|
|
|
socket.emit('v1:model-Floor:delete', data);
|
|
}
|
|
});
|
|
|
|
const removedFloorsData = removedFloors.map((floor) => ({
|
|
type: "Floor" as const,
|
|
lineData: floor,
|
|
timeStamp: new Date().toISOString(),
|
|
}));
|
|
|
|
push2D({
|
|
type: 'Draw',
|
|
actions: [
|
|
{
|
|
actionType: 'Lines-Delete',
|
|
points: removedFloorsData
|
|
}
|
|
]
|
|
});
|
|
}
|
|
if (updatedFloors.length > 0) {
|
|
updatedFloors.forEach(floor => {
|
|
if (projectId) {
|
|
|
|
// API
|
|
|
|
// upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
|
|
|
|
// SOCKET
|
|
|
|
const data = {
|
|
floorData: floor,
|
|
projectId: projectId,
|
|
versionId: selectedVersion?.versionId || '',
|
|
userId: userId,
|
|
organization: organization
|
|
}
|
|
|
|
socket.emit('v1:model-Floor:add', data);
|
|
}
|
|
});
|
|
|
|
const updatedFloorsData = updatedFloors.map((floor) => ({
|
|
type: "Floor" as const,
|
|
lineData: Floors.find(f => f.floorUuid === floor.floorUuid) || floor,
|
|
newData: floor,
|
|
timeStamp: new Date().toISOString(),
|
|
}));
|
|
|
|
push2D({
|
|
type: 'Draw',
|
|
actions: [
|
|
{
|
|
actionType: 'Lines-Update',
|
|
points: updatedFloorsData
|
|
}
|
|
]
|
|
});
|
|
}
|
|
}
|
|
if (point.pointType === 'Zone') {
|
|
const Zones = getZonesByPointId(point.pointUuid);
|
|
const { removedZones, updatedZones } = removeZonePoint(point.pointUuid);
|
|
setHoveredPoint(null);
|
|
if (removedZones.length > 0) {
|
|
removedZones.forEach(zone => {
|
|
if (projectId) {
|
|
|
|
// API
|
|
|
|
// deleteZoneApi(projectId, selectedVersion?.versionId || '', zone.zoneUuid);
|
|
|
|
// SOCKET
|
|
|
|
const data = {
|
|
zoneUuid: zone.zoneUuid,
|
|
projectId: projectId,
|
|
versionId: selectedVersion?.versionId || '',
|
|
userId: userId,
|
|
organization: organization
|
|
}
|
|
|
|
socket.emit('v1:zone:delete', data);
|
|
}
|
|
});
|
|
|
|
const removedZonesData = removedZones.map((zone) => ({
|
|
type: "Zone" as const,
|
|
lineData: zone,
|
|
timeStamp: new Date().toISOString(),
|
|
}));
|
|
|
|
push2D({
|
|
type: 'Draw',
|
|
actions: [
|
|
{
|
|
actionType: 'Lines-Delete',
|
|
points: removedZonesData
|
|
}
|
|
]
|
|
});
|
|
}
|
|
if (updatedZones.length > 0) {
|
|
updatedZones.forEach(zone => {
|
|
if (projectId) {
|
|
|
|
// API
|
|
|
|
// upsertZoneApi(projectId, selectedVersion?.versionId || '', zone);
|
|
|
|
// SOCKET
|
|
|
|
const data = {
|
|
zoneData: zone,
|
|
projectId: projectId,
|
|
versionId: selectedVersion?.versionId || '',
|
|
userId: userId,
|
|
organization: organization
|
|
}
|
|
|
|
socket.emit('v1:zone:add', data);
|
|
}
|
|
});
|
|
|
|
const updatedZonesData = updatedZones.map((zone) => ({
|
|
type: "Zone" as const,
|
|
lineData: Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone,
|
|
newData: zone,
|
|
timeStamp: new Date().toISOString(),
|
|
}));
|
|
|
|
push2D({
|
|
type: 'Draw',
|
|
actions: [
|
|
{
|
|
actionType: 'Lines-Update',
|
|
points: updatedZonesData
|
|
}
|
|
]
|
|
});
|
|
}
|
|
}
|
|
handleCanvasCursors('default');
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (hoveredPoint && hoveredPoint.pointUuid !== point.pointUuid) {
|
|
setIsHovered(false);
|
|
}
|
|
}, [hoveredPoint])
|
|
|
|
useEffect(() => {
|
|
if (selectedPoints.length > 0 && selectedPoints.some((selectedPoint) => (selectedPoint.userData.pointUuid && selectedPoint.userData.pointUuid === point.pointUuid))) {
|
|
setIsSelected(true);
|
|
} else {
|
|
setIsSelected(false);
|
|
}
|
|
}, [selectedPoints])
|
|
|
|
if (!point) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{!isSelected ?
|
|
<DragControls
|
|
axisLock='y'
|
|
autoTransform={false}
|
|
onDragStart={() => handleDragStart(point)}
|
|
onDrag={() => handleDrag(point)}
|
|
onDragEnd={() => handleDragEnd(point)}
|
|
>
|
|
<mesh
|
|
key={point.pointUuid}
|
|
uuid={point.pointUuid}
|
|
name={`${point.pointType}-Point`}
|
|
position={[...point.position]}
|
|
onClick={() => {
|
|
handlePointClick(point);
|
|
}}
|
|
onPointerOver={(e) => {
|
|
if (!hoveredPoint && selectedPoints.length === 0 && e.buttons === 0 && !e.ctrlKey) {
|
|
setHoveredPoint(point);
|
|
setIsHovered(true);
|
|
if (toolMode === 'move') {
|
|
handleCanvasCursors('grab');
|
|
}
|
|
}
|
|
}}
|
|
onPointerOut={() => {
|
|
if (hoveredPoint) {
|
|
setHoveredPoint(null);
|
|
if (!hoveredLine) {
|
|
handleCanvasCursors('default');
|
|
}
|
|
}
|
|
setIsHovered(false)
|
|
}}
|
|
userData={point}
|
|
>
|
|
<boxGeometry args={boxScale} />
|
|
<shaderMaterial
|
|
ref={materialRef}
|
|
uniforms={uniforms}
|
|
vertexShader={
|
|
`
|
|
varying vec2 vUv;
|
|
|
|
void main() {
|
|
vUv = uv;
|
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
}
|
|
`
|
|
}
|
|
fragmentShader={
|
|
`
|
|
varying vec2 vUv;
|
|
uniform vec3 uOuterColor;
|
|
uniform vec3 uInnerColor;
|
|
|
|
void main() {
|
|
// Define the size of the white square as a proportion of the face
|
|
float borderThickness = 0.2; // Adjust this value for border thickness
|
|
if (vUv.x > borderThickness && vUv.x < 1.0 - borderThickness && vUv.y > borderThickness && vUv.y < 1.0 - borderThickness) {
|
|
gl_FragColor = vec4(uInnerColor, 1.0); // Inner square
|
|
} else {
|
|
gl_FragColor = vec4(uOuterColor, 1.0); // Border
|
|
}
|
|
}
|
|
`
|
|
}
|
|
/>
|
|
</mesh>
|
|
</DragControls>
|
|
:
|
|
<group
|
|
key={point.pointUuid}
|
|
uuid={point.pointUuid}
|
|
name={`${point.pointType}-Point`}
|
|
position={[point.position[0], 0.3, point.position[2]]}
|
|
userData={point}
|
|
rotation={[Math.PI / 2, 0, 0]}
|
|
>
|
|
<mesh>
|
|
<torusGeometry args={[0.4, 0.1, 2, 16]} />
|
|
<meshBasicMaterial color="#6F42C1" />
|
|
</mesh>
|
|
|
|
<mesh position={[0, 0, 0]}>
|
|
<sphereGeometry args={[0.3, 8, 16]} />
|
|
<meshBasicMaterial color="white" />
|
|
</mesh>
|
|
</group>
|
|
|
|
}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default Point; |