diff --git a/DOCS.md b/DOCS.md index c58909a..56be61d 100644 --- a/DOCS.md +++ b/DOCS.md @@ -4,4 +4,14 @@ SOCKET MESSAGING: // PORT CHANGE IN .env localhost:9091/storeEvent?client_id=1&message={"me": "update"} // CONNECTION: - ws://localhost:9091/ws/1 \ No newline at end of file + ws://localhost:9091/ws/1 + +DATABASE LOGS TYPE: + 1 - INFO + 2 - WARNING + 3 - ERROR + 4 - ADMIN_ACTION + 5 - USER_ACTION + ... + 10 - RESERV + diff --git a/Makefile b/Makefile index 409fffc..98091ad 100644 --- a/Makefile +++ b/Makefile @@ -3,29 +3,29 @@ build: - docker compose build + docker-compose build dev: build - docker compose up -d --force-recreate + docker-compose up -d --force-recreate devf: dev - docker compose logs -f + docker-compose logs -f up: - docker compose up -d --force-recreate + docker-compose up -d --force-recreate upf: up - docker compose logs -f + docker-compose logs -f logs: - docker compose logs -f + docker-compose logs -f stop: echo "!!!!!!!!!!!!!!!!!EXTERMINATUS!!!!!!!!!!!!!!!!!" - docker compose stop + docker-compose stop start: - docker compose start + docker-compose start drop: docker-compose down --volumes \ No newline at end of file diff --git a/ToDO.md.save b/ToDO.md.save new file mode 100644 index 0000000..a328d40 --- /dev/null +++ b/ToDO.md.save @@ -0,0 +1,4 @@ + + +!1 => Написать хук для воспроизведения симуляции +!2 => diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index c489771..cbaa1ed 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "dsn-application", "version": "0.1.0", "dependencies": { + "@mui/material": "^6.1.3", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -2445,6 +2446,60 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/cache": { + "version": "11.13.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", + "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/serialize": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", + "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "node_modules/@emotion/utils": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz", + "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -3425,6 +3480,213 @@ "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", "license": "MIT" }, + "node_modules/@mui/core-downloads-tracker": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.3.tgz", + "integrity": "sha512-ajMUgdfhTb++rwqj134Cq9f4SRN8oXUqMRnY72YBnXiXai3olJLLqETheRlq3MM8wCKrbq7g6j7iWL1VvP44VQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/material": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.3.tgz", + "integrity": "sha512-loV5MBoMKLrK80JeWINmQ1A4eWoLv51O2dBPLJ260IAhupkB3Wol8lEQTEvvR2vO3o6xRHuXe1WaQEP6N3riqg==", + "dependencies": { + "@babel/runtime": "^7.25.6", + "@mui/core-downloads-tracker": "^6.1.3", + "@mui/system": "^6.1.3", + "@mui/types": "^7.2.18", + "@mui/utils": "^6.1.3", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^18.3.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^6.1.3", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/@mui/private-theming": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.3.tgz", + "integrity": "sha512-XK5OYCM0x7gxWb/WBEySstBmn+dE3YKX7U7jeBRLm6vHU5fGUd7GiJWRirpivHjOK9mRH6E1MPIVd+ze5vguKQ==", + "dependencies": { + "@babel/runtime": "^7.25.6", + "@mui/utils": "^6.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.3.tgz", + "integrity": "sha512-i4yh9m+eMZE3cNERpDhVr6Wn73Yz6C7MH0eE2zZvw8d7EFkIJlCQNZd1xxGZqarD2DDq2qWHcjIOucWGhxACtA==", + "dependencies": { + "@babel/runtime": "^7.25.6", + "@emotion/cache": "^11.13.1", + "@emotion/serialize": "^1.3.2", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.3.tgz", + "integrity": "sha512-ILaD9UsLTBLjMcep3OumJMXh1PYr7aqnkHm/L47bH46+YmSL1zWAX6tWG8swEQROzW2GvYluEMp5FreoxOOC6w==", + "dependencies": { + "@babel/runtime": "^7.25.6", + "@mui/private-theming": "^6.1.3", + "@mui/styled-engine": "^6.1.3", + "@mui/types": "^7.2.18", + "@mui/utils": "^6.1.3", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.18", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.18.tgz", + "integrity": "sha512-uvK9dWeyCJl/3ocVnTOS6nlji/Knj8/tVqVX03UVTpdmTJYu/s4jtDd9Kvv0nRGE0CUSNW1UYAci7PYypjealg==", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.3.tgz", + "integrity": "sha512-4JBpLkjprlKjN10DGb1aiy/ii9TKbQ601uSHtAmYFAS879QZgAD7vRnv/YBE4iBbc7NXzFgbQMCOFrupXWekIA==", + "dependencies": { + "@babel/runtime": "^7.25.6", + "@mui/types": "^7.2.18", + "@types/prop-types": "^15.7.13", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -3549,6 +3811,15 @@ } } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@remix-run/router": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz", @@ -4769,6 +5040,14 @@ "@types/react": "*" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -6611,6 +6890,14 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -7638,6 +7925,15 @@ "utila": "~0.4" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -16404,6 +16700,21 @@ } } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -17951,6 +18262,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index 597268c..0cfa176 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@mui/material": "^6.1.3", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/src/frontend/src/Services/WebsocketHook.js b/src/frontend/src/Services/WebsocketHook.js index ad2aa59..ee50100 100644 --- a/src/frontend/src/Services/WebsocketHook.js +++ b/src/frontend/src/Services/WebsocketHook.js @@ -27,7 +27,7 @@ const useWebsocketConnection = (userToken, roomHash) => { return prevState; }); } catch (error) { - console.error('Error parsing WebSocket message:', error); + console.error('Error parsing WebSocket message:', error, event.data); } }; diff --git a/src/frontend/src/pages/Dashboard.js b/src/frontend/src/pages/Dashboard.js index 05ffb14..3d3abe5 100644 --- a/src/frontend/src/pages/Dashboard.js +++ b/src/frontend/src/pages/Dashboard.js @@ -14,11 +14,12 @@ import { ModalJson } from './Modal/MJson'; import { ModalMap } from './Modal/MMap'; import { CtxMenu } from './ContextMenu/CtxMenu'; import { ProgramInterface } from './MainScreen/ProgramInterface'; - +import { SimulatorInterface } from './SimScreen/SimScreen'; const Dashboard = () => { const mountRef = useRef(null); // Указатель монтирования const sceneRef = useRef(null); // Указатель на сцену + const cameraRef = useRef(null); // Указатель на камеру const {websocketStruct, sendMessage} = useWebsocketConnection(getCustomCookie("userToken"), 1); const [heightData, setHeightData] = useState([new HeightPoint()]); // Высоты const [formData, setFormData] = useState({ x: '', y: '', height: '' }); // Форма для ввода данных карты @@ -29,7 +30,6 @@ const Dashboard = () => { const [selectedBaseStation, setSelectedBaseStation] = useState(null); // Состояние для выбранной базовой станции const [mapSettings, setMapSettings] = useState({maxHeight: 20, coordX: 0, coordY: 0, }); // Настройки карты const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0 }); // контекстное меню сцены - const [objectType, setObjectType] = useState(1); // 1 = Drone, 2 = Base Station const [formObjectData, setFormObjectData] = useState({ name: "", @@ -52,6 +52,204 @@ const Dashboard = () => { meshName: "", // Название сети }); const [searchTerm, setSearchTerm] = useState(""); + const [isSimulationRunning, setIsSimulationRunning] = useState(false); + + // Рекурсивная функция для поиска объекта среди детей + const containsObjectRecursively = (parent, target) => { + if (parent === target) { + return true; + } + for (let child of parent.children) { + if (containsObjectRecursively(child, target)) { + return true; + } + } + return false; + }; + + // Обработка websocket + useEffect(() => { + if (websocketStruct && websocketStruct.modulation) { + const modulation = websocketStruct.modulation; + // Парсинг карты + const map = modulation.map; + setMapSettings({ + name: map.name, + minBound: map.map.MinBound, + maxBound: map.map.MaxBound, + heightData: map.map.HeightData, + maxHeight: 20, + }); + setHeightData(map.map.HeightData.flatMap((row, x) => row.map((height, y) => new HeightPoint(x, y, height)))); + + // Парсинг объектов + const parsedDrones = []; + const parsedBaseStations = []; + modulation.objects.forEach((obj) => { + if (obj.type === 1) { + const drone = new Drone(obj.name); + drone.setPosition(...obj.coords); + parsedDrones.push(drone); + } else if (obj.type === 2) { + const base = new BaseStation(obj.name); + base.setPosition(...obj.coords); + parsedBaseStations.push(base); + } + }); + setDrones(parsedDrones); + setBaseStation(parsedBaseStations); + } + }, [websocketStruct]); + + useEffect(() => { + if (!mountRef.current) return; + let width = mountRef.current.clientWidth; + let height = mountRef.current.clientHeight; + // Создаем объект сцены + const scene = new THREE.Scene(); + scene.background = new THREE.Color(0xffffff); + // Сохраняем ссылку на сцену + sceneRef.current = scene; + // Создаем камеру + const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 2000); + camera.position.set(0, 80, 120); + cameraRef.current = camera; + + const renderer = new THREE.WebGLRenderer(); + renderer.setSize(width, height); + mountRef.current.appendChild(renderer.domElement); + + // Плоскость для высотной карты :: TODO :: W/H/Ws/Hs change + const geometry = new THREE.PlaneGeometry(200, 200, 20, 20); + const vertices = geometry.attributes.position.array; + const colors = []; + const color = new THREE.Color(); + + const minHeight = Math.min(...heightData.map((point) => point.height)); + const maxHeight = Math.max(mapSettings.maxHeight, ...heightData.map((point) => point.height)); + + for (let i = 0; i < vertices.length; i += 3) { + const x = Math.floor(i / 3) % 5; + const y = Math.floor(i / (3 * 5)); + + const heightPoint = heightData.find((point) => point.x === x && point.y === y); + const heightValue = heightPoint ? heightPoint.height : 0; + + vertices[i + 2] = heightValue; + + const normalizedHeight = (heightValue - minHeight) / (maxHeight - minHeight); + color.setHSL(0.7 * (1 - normalizedHeight), 1, 0.5); + colors.push(color.r, color.g, color.b); + } + + geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3)); + + const material = new THREE.MeshBasicMaterial({ + vertexColors: true, + side: THREE.DoubleSide, + wireframe: false, + }); + + const mesh = new THREE.Mesh(geometry, material); + mesh.rotation.x = -Math.PI / 2; + scene.add(mesh); + + const axesHelper = new THREE.AxesHelper(100); + scene.add(axesHelper); + + const controls = new OrbitControls(camera, renderer.domElement); + controls.enableDamping = true; + + const light = new THREE.DirectionalLight(0xffffff, 1); + light.position.set(0, 50, 50).normalize(); + scene.add(light); + + const handleMouseMove = (event) => { + let x = event.x; + let y = event.y; + setMouseState({ x: x, y: y, z: 0 }); + } + const animate = () => { + requestAnimationFrame(animate); + controls.update(); + renderer.render(scene, camera); + }; + + renderer.domElement.addEventListener('click', handleClick); + renderer.domElement.addEventListener('mousemove', handleMouseMove); + + animate(); + drones.forEach(droneData => { + scene.add(droneData.getObject()); + }); + baseStation.forEach(b => { + scene.add(b.getObject()); + }); + + // Обработка нажатия ПКМ + renderer.domElement.addEventListener('contextmenu', (event) => { + event.preventDefault(); + setContextMenu({ + visible: true, + x: event.clientX, + y: event.clientY, + }); + }); + return () => { + if (mountRef.current) { + mountRef.current.removeChild(renderer.domElement); + } + }; + }, [heightData, mapSettings]); + + // Обработка кликов на сцене + const handleClick = (event) => { + // Рассчитываем координаты мыши в нормализованной системе координат и округляем до целого числа + const mouse = new THREE.Vector2(); + mouse.x = (event.clientX / window.innerWidth) * 2 - 1; + mouse.y = - (event.clientY / window.innerHeight) * 2 + 1; + console.log('Mouse coordinates:', mouse); + + // Создаем объект Raycaster для проверки пересечений + const raycaster = new THREE.Raycaster(); + const rayHelper = new THREE.ArrowHelper(raycaster.ray.direction, raycaster.ray.origin, 100, 0xff4444); + sceneRef.current.add(rayHelper); + raycaster.setFromCamera(mouse, cameraRef.current); + + // Проверяем пересечения с объектами дронов + const intersects = raycaster.intersectObjects(drones.map(drone => drone.getObject()), true); + console.log('Intersections:', intersects); + console.log('Drones:', drones.map(drone => [drone.getObject(), drone.getObject().position, drone.name])); + + if (intersects.length > 0) { + const selected = intersects[0].object; + console.log('Clicked on:', selected); // Лог для проверки объекта + + // Поиск дрона рекурсивно + const drone = drones.find(drone => containsObjectRecursively(drone.getObject(), selected)); + + if (drone) { + // Сбрасываем цвет предыдущего выбранного дрона + if (selectedDrone) { + drone.getObject().visible = true; + } + // Устанавливаем цвет выбранного дрона + drone.getObject().visible = false; + console.log(drone); + setSelectedDrone(drone); + } + } else { + // Сбрасываем выбор, если ни один дрон не был выбран + if (selectedDrone) { + selectedDrone.getObject().visible = true; + setSelectedDrone(null); + } + } + }; + + // Остальной код компонента остается неизменным + + const handleSearch = () => { const foundDrone = drones.find(drone => drone.name.toLowerCase() === searchTerm.toLowerCase()); const foundBaseStation = baseStation.find(base => base.name.toLowerCase() === searchTerm.toLowerCase()); @@ -122,204 +320,6 @@ const Dashboard = () => { isModalSearchOpen: false, }); }; - // Обработка websocket - useEffect(() => { - if (websocketStruct && websocketStruct.modulation) { - const modulation = websocketStruct.modulation; - // Парсинг карты - const map = modulation.map; - setMapSettings({ - name: map.name, - minBound: map.map.MinBound, - maxBound: map.map.MaxBound, - heightData: map.map.HeightData, - maxHeight: 20, - }); - setHeightData(map.map.HeightData.flatMap((row, x) => row.map((height, y) => new HeightPoint(x, y, height)))); - - // Парсинг объектов - const parsedDrones = []; - const parsedBaseStations = []; - modulation.objects.forEach((obj) => { - if (obj.type === 1) { - const drone = new Drone(obj.name); - drone.setPosition(...obj.coords); - parsedDrones.push(drone); - } else if (obj.type === 2) { - const base = new BaseStation(obj.name); - base.setPosition(...obj.coords); - parsedBaseStations.push(base); - } - }); - setDrones(parsedDrones); - setBaseStation(parsedBaseStations); - } - }, [websocketStruct]); - - useEffect(() => { - - if (!mountRef.current) return; - let width = mountRef.current.clientWidth; - let height = mountRef.current.clientHeight; - // Создаем объект сцены - const scene = new THREE.Scene(); - scene.background = new THREE.Color(0xffffff); - // Сохраняем ссылку на сцену - sceneRef.current = scene; - // Создаем камеру - const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 2000); - camera.position.set(0, 80, 120); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize(width, height); - mountRef.current.appendChild(renderer.domElement); - - // Плоскость для высотной карты :: TODO :: W/H/Ws/Hs change - const geometry = new THREE.PlaneGeometry(200, 200, 20, 20); - const vertices = geometry.attributes.position.array; - const colors = []; - const color = new THREE.Color(); - - const minHeight = Math.min(...heightData.map((point) => point.height)); - const maxHeight = Math.max(mapSettings.maxHeight, ...heightData.map((point) => point.height)); - - for (let i = 0; i < vertices.length; i += 3) { - const x = Math.floor(i / 3) % 5; - const y = Math.floor(i / (3 * 5)); - - const heightPoint = heightData.find((point) => point.x === x && point.y === y); - const heightValue = heightPoint ? heightPoint.height : 0; - - vertices[i + 2] = heightValue; - - const normalizedHeight = (heightValue - minHeight) / (maxHeight - minHeight); - color.setHSL(0.7 * (1 - normalizedHeight), 1, 0.5); - colors.push(color.r, color.g, color.b); - } - - geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3)); - - const material = new THREE.MeshBasicMaterial({ - vertexColors: true, - side: THREE.DoubleSide, - wireframe: false, - }); - - const mesh = new THREE.Mesh(geometry, material); - mesh.rotation.x = -Math.PI / 2; - scene.add(mesh); - - const axesHelper = new THREE.AxesHelper(100); - scene.add(axesHelper); - - const controls = new OrbitControls(camera, renderer.domElement); - controls.enableDamping = true; - - const light = new THREE.DirectionalLight(0xffffff, 1); - light.position.set(0, 50, 50).normalize(); - scene.add(light); -// Обработка кликов на сцене -const handleClick = (event) => { - // Рассчитываем координаты мыши в нормализованной системе координат и округляем до целого числа - const mouse = new THREE.Vector2(); - mouse.x = (event.clientX / window.innerWidth) * 2 - 1; - mouse.y = - (event.clientY / window.innerHeight) * 2 + 1; - console.log('Mouse coordinates:', mouse); - - // Создаем объект Raycaster для проверки пересечений - const raycaster = new THREE.Raycaster(); - const rayHelper = new THREE.ArrowHelper(raycaster.ray.direction, raycaster.ray.origin, 100, 0xff4444); - scene.add(rayHelper); - raycaster.setFromCamera(mouse, camera); - - // Проверяем пересечения с объектами дронов - const intersects = raycaster.intersectObjects(drones.map(drone => drone.getObject()), true); - console.log('Intersections:', intersects); - console.log('Drones:', drones.map(drone => [drone.getObject(), drone.getObject().position, drone.name])); - - if (intersects.length > 0) { - const selected = intersects[0].object; - console.log('Clicked on:', selected); // Лог для проверки объекта - - // Поиск дрона рекурсивно - const drone = drones.find(drone => containsObjectRecursively(drone.getObject(), selected)); - - if (drone) { - // Сбрасываем цвет предыдущего выбранного дрона - if (selectedDrone) { - drone.getObject().visible = true; - // selectedDrone.getObject().material.color.set(0xff0000); - } - // Устанавливаем цвет выбранного дрона - // drone.getObject().material.color.set(0xff1111); - drone.getObject().visible = false; - console.log(drone); - setSelectedDrone(drone); - } - } else { - // Сбрасываем выбор, если ни один дрон не был выбран - if (selectedDrone) { - // selectedDrone.getObject().material.color.set(0xff0000); - selectedDrone .getObject().visible = true; - - setSelectedDrone(null); - } - } -}; - -// Рекурсивная функция для поиска объекта среди детей -const containsObjectRecursively = (parent, target) => { - if (parent === target) { - return true; - } - for (let child of parent.children) { - if (containsObjectRecursively(child, target)) { - return true; - } - } - return false; -}; - - - - const handleMouseMove = (event) => { - let x = event.x; - let y = event.y; - setMouseState({ x: x, y: y, z: 0 }); - } - const animate = () => { - requestAnimationFrame(animate); - controls.update(); - renderer.render(scene, camera); - }; - - renderer.domElement.addEventListener('click', handleClick); - renderer.domElement.addEventListener('mousemove', handleMouseMove); - - - animate(); - drones.forEach(droneData => { - scene.add(droneData.getObject()); - }); - baseStation.forEach(b => { - scene.add(b.getObject()); - }); - - // Обработка нажатия ПКМ - renderer.domElement.addEventListener('contextmenu', (event) => { - event.preventDefault(); - setContextMenu({ - visible: true, - x: event.clientX, - y: event.clientY, - }); - }); - return () => { - if (mountRef.current) { - mountRef.current.removeChild(renderer.domElement); - } - }; - }, [heightData, mapSettings]); // Добавление дрона в сцену const handleAddDrone = () => { @@ -418,12 +418,8 @@ const containsObjectRecursively = (parent, target) => { }; // Открытие контекстного меню const handleContextMenuClick = (action) => { - if (action === 'add_base'){ - handleAddBaseStation(); - } - else if (action === 'add') { - handleAddDrone(); - } else if (action === 'save') { + if (action === 'add_base'){ handleAddBaseStation();} + else if (action === 'add') { handleAddDrone(); } else if (action === 'save') { console.log('Сохранить промежуточный результат'); } setContextMenu({ ...contextMenu, visible: false }); @@ -471,16 +467,35 @@ const containsObjectRecursively = (parent, target) => { handleInputChange={handleInputChange} mapSettings={mapSettings} handleSettingsChange={handleSettingsChange}/> - +
{/* Окно программы */} -Вы точно хотите удалить объект X?
+Выберите или переташите файл для загрузки
- - - +export const ModalJson = ({ isModalOpen, closeAllModals }) => { + return ( +Выберите или переташите файл для загрузки
+ +