From 40b9b116c1eb3eb704d12d42664b155d939eb65a Mon Sep 17 00:00:00 2001 From: IDONTSUDO Date: Mon, 19 Feb 2024 11:48:08 +0000 Subject: [PATCH] =?UTF-8?q?Scene=20builder:=20=D0=BF=D1=80=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8=D0=BF=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D0=BC=D0=BE=D0=B3=D0=BE=20=D0=BE=D0=B1?= =?UTF-8?q?=D1=8A=D0=B5=D0=BA=D1=82=D0=B0=20=D0=BA=20=D1=81=D0=B5=D1=82?= =?UTF-8?q?=D0=BA=D0=B5=20=D0=B8=20=D0=B4=D1=80=D1=83=D0=B3=D0=B8=D0=BC=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA=D1=82=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 2 +- server/package.json | 12 +- server/src/main.ts | 2 + ui/package.json | 7 + ui/src/core/extensions/extensions.ts | 7 + ui/src/core/extensions/number.ts | 8 + ui/src/core/extensions/object.ts | 7 + ui/src/core/model/core_vector3.ts | 17 ++ .../core/repository/core_three_repository.ts | 59 ++++++- ui/src/core/store/base_store.ts | 2 - ui/src/core/ui/text/text.tsx | 15 ++ .../behavior_tree_builder_screen.tsx | 106 +++++++++++ .../behavior_tree_builder_store.tsx | 61 +++++++ .../presentation/ui/editor/background.css | 19 ++ .../ui/editor/custom_background.tsx | 11 ++ .../ui/editor/custom_connection.tsx | 36 ++++ .../presentation/ui/editor/custom_node.tsx | 164 ++++++++++++++++++ .../presentation/ui/editor/custom_socket.tsx | 23 +++ .../presentation/ui/editor/editor.tsx | 89 ++++++++++ .../presentation/ui/editor/editor_view.ts | 7 + .../presentation/ui/editor/style_node.tsx | 30 ++++ .../presentation/ui/editor/vars.tsx | 3 + .../presentation/ui/skill_tree/skill_tree.tsx | 69 ++++++++ .../scene_manager/model/scene_view.ts | 1 + .../components/static_asset_item_view.tsx | 2 - .../presentation/scene_manager.tsx | 61 ++++--- .../presentation/scene_manager_store.ts | 14 +- .../stick_objects_marking_screen.tsx | 133 ++++++++++++++ .../stick_objects_marking_store.tsx | 164 ++++++++++++++++++ .../stick_objects_marking_three_repository.ts | 101 +++++++++++ ui/src/index.tsx | 7 +- 31 files changed, 1193 insertions(+), 46 deletions(-) create mode 100644 ui/src/core/extensions/number.ts create mode 100644 ui/src/core/extensions/object.ts create mode 100644 ui/src/core/model/core_vector3.ts create mode 100644 ui/src/core/ui/text/text.tsx create mode 100644 ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx create mode 100644 ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_store.tsx create mode 100644 ui/src/features/behavior_tree_builder/presentation/ui/editor/background.css create mode 100644 ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_background.tsx create mode 100644 ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_connection.tsx create mode 100644 ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_node.tsx create mode 100644 ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_socket.tsx create mode 100644 ui/src/features/behavior_tree_builder/presentation/ui/editor/editor.tsx create mode 100644 ui/src/features/behavior_tree_builder/presentation/ui/editor/editor_view.ts create mode 100644 ui/src/features/behavior_tree_builder/presentation/ui/editor/style_node.tsx create mode 100644 ui/src/features/behavior_tree_builder/presentation/ui/editor/vars.tsx create mode 100644 ui/src/features/behavior_tree_builder/presentation/ui/skill_tree/skill_tree.tsx create mode 100644 ui/src/features/stick_objects_marking/stick_objects_marking_screen.tsx create mode 100644 ui/src/features/stick_objects_marking/stick_objects_marking_store.tsx create mode 100644 ui/src/features/stick_objects_marking/stick_objects_marking_three_repository.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 30e0524..6581d96 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,6 +15,6 @@ "*ui": false, "*ui.*": false }, - "cSpell.words": ["antd", "Collada", "Contolls", "fileupload", "metadatas", "undici", "uuidv"], + "cSpell.words": ["antd", "Collada", "Contolls", "fileupload", "lerp", "metadatas", "undici", "uuidv"], "editor.rulers": [100] } diff --git a/server/package.json b/server/package.json index d062c30..3e5895a 100644 --- a/server/package.json +++ b/server/package.json @@ -7,7 +7,8 @@ "test:dev": "NODE_ENV=test_dev tsc-watch --onSuccess 'ts-node ./build/test/test.js'", "test:unit": "NODE_ENV=unit tsc-watch --onSuccess 'ts-node ./build/test/test.js'", "test:e2e": "NODE_ENV=e2e tsc-watch --onSuccess 'ts-node ./build/test/test.js'", - "dev": "NODE_ENV=dev tsc-watch --onSuccess 'ts-node ./build/src/main.js'" + "dev": "NODE_ENV=dev tsc-watch --onSuccess 'ts-node ./build/src/main.js'", + "build:stand": " " }, "author": "IDONTSUDO", "devDependencies": { @@ -27,7 +28,9 @@ "source-map-support": "latest", "ts-node": "^10.9.1", "tslint": "latest", - "typescript": "^5.1.6" + "typescript": "^5.1.6", + "node-watch": "^0.7.4", + "nodemon": "^3.0.1" }, "dependencies": { "@grpc/grpc-js": "^1.9.0", @@ -42,14 +45,13 @@ "md5": "^2.3.0", "mongoose": "^7.6.2", "mongoose-autopopulate": "^1.1.0", - "node-watch": "^0.7.4", - "nodemon": "^3.0.1", "reflect-metadata": "^0.1.13", "socket.io": "^4.7.2", "socket.io-client": "^4.7.2", "spark-md5": "^3.0.2", "ts-md5": "^1.3.1", "tsc-watch": "^6.0.4", - "uuid": "^9.0.1" + "uuid": "^9.0.1", + "pm2": "^5.3.1" } } diff --git a/server/src/main.ts b/server/src/main.ts index 3b91a44..f2682ca 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -4,9 +4,11 @@ import { SocketSubscriber } from "./core/controllers/socket_controller"; import { extensions } from "./core/extensions/extensions"; import { httpRoutes } from "./core/controllers/routes"; import { pipelineRealTimeService } from "./features/realtime/realtime_presentation"; +import { main } from "./p"; extensions(); const socketSubscribers = [new SocketSubscriber(pipelineRealTimeService, "realtime")]; new App(httpRoutes, socketSubscribers).listen(); +main(); diff --git a/ui/package.json b/ui/package.json index 0360fc5..70da4b2 100644 --- a/ui/package.json +++ b/ui/package.json @@ -21,15 +21,22 @@ "mobx-react-lite": "^4.0.4", "mobx-store-inheritance": "^1.0.6", "react": "^18.2.0", + "react-accessible-treeview": "^2.8.3", + "react-dnd": "^16.0.1", + "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.2.0", "react-i18next": "^13.3.1", "react-infinite-scroll-component": "^6.1.0", "react-router-dom": "^6.18.0", "react-scripts": "5.0.1", "reflect-metadata": "^0.1.13", + "rete-connection-plugin": "^2.0.0", + "rete-react-plugin": "^2.0.4", + "rete-render-utils": "^2.0.1", "sass": "^1.66.1", "serve": "^14.2.1", "socket.io-client": "^4.7.2", + "styled-components": "^6.1.8", "three": "^0.159.0", "three-stdlib": "^2.28.9", "three-transform-controls": "^1.0.4", diff --git a/ui/src/core/extensions/extensions.ts b/ui/src/core/extensions/extensions.ts index 57a9789..2b7998c 100644 --- a/ui/src/core/extensions/extensions.ts +++ b/ui/src/core/extensions/extensions.ts @@ -1,9 +1,11 @@ import { ArrayExtensions } from "./array"; import { MapExtensions } from "./map"; +import { NumberExtensions } from "./number"; import { StringExtensions } from "./string"; export type CallBackVoidFunction = (value: T) => void; export type CallBackStringVoidFunction = (value: string) => void; +export type CallBackEventTarget = (value: EventTarget) => void; declare global { interface Array { @@ -14,6 +16,9 @@ declare global { isNotEmpty(): boolean; hasIncludeElement(element: T): boolean; } + interface Number { + fromArray(): number[]; + } interface String { isEmpty(): boolean; isNotEmpty(): boolean; @@ -21,9 +26,11 @@ declare global { interface Map { addValueOrMakeCallback(key: K, value: V, callBack: CallBackVoidFunction): void; } + interface Vector3 {} } export const extensions = () => { ArrayExtensions(); StringExtensions(); + NumberExtensions(); MapExtensions(); }; diff --git a/ui/src/core/extensions/number.ts b/ui/src/core/extensions/number.ts new file mode 100644 index 0000000..565ced6 --- /dev/null +++ b/ui/src/core/extensions/number.ts @@ -0,0 +1,8 @@ +export const NumberExtensions = () => { + if (Number().fromArray === undefined) { + // eslint-disable-next-line no-extend-native + Number.prototype.fromArray = function () { + return Array.from(this.toString()).map((el) => Number(el)); + }; + } +}; diff --git a/ui/src/core/extensions/object.ts b/ui/src/core/extensions/object.ts new file mode 100644 index 0000000..acca8fd --- /dev/null +++ b/ui/src/core/extensions/object.ts @@ -0,0 +1,7 @@ +/* eslint-disable no-extend-native */ + +export const ObjectExtensionsIsKeyExists = (obj: any, keys: string[]): boolean => { + return true; +}; + +// {"objectThatSticksName":"cube2","objectThatSticksNamePoints":[{"x":25,"y":4.987889622413917,"z":10.504078531217838}],"objectsToWhichItSticksName":"cube1","objectsToWhichItSticksPoints":[{"x":5,"y":3.0783236330074963,"z":1.1333166084347885}]} diff --git a/ui/src/core/model/core_vector3.ts b/ui/src/core/model/core_vector3.ts new file mode 100644 index 0000000..c6e2b08 --- /dev/null +++ b/ui/src/core/model/core_vector3.ts @@ -0,0 +1,17 @@ +import { Vector3 } from "three"; + +export class CoreVector3 { + vector: Vector3; + constructor(vector: Vector3) { + this.vector = vector; + } + + static divideLineIntoEqualSegments(beginPoint: Vector3, endPoint: Vector3, segments: number): Vector3[] { + return Number(segments) + .fromArray() + .map((el) => new Vector3().lerpVectors(beginPoint, endPoint, (el * 1) / segments)); + } + add(vector: Vector3) { + return new CoreVector3(new Vector3(this.vector.x + vector.x, this.vector.y + vector.y, this.vector.z + vector.z)); + } +} diff --git a/ui/src/core/repository/core_three_repository.ts b/ui/src/core/repository/core_three_repository.ts index 5034b57..1d82d30 100644 --- a/ui/src/core/repository/core_three_repository.ts +++ b/ui/src/core/repository/core_three_repository.ts @@ -19,6 +19,11 @@ import { GridHelper, CameraHelper, Quaternion, + MeshBasicMaterial, + PlaneGeometry, + BoxGeometry, + BufferGeometry, + Line, } from "three"; import { TypedEvent } from "../helper/typed_event"; import { Result } from "../helper/result"; @@ -35,10 +40,12 @@ import { RobossemblerAssets, SceneSimpleObject, } from "../../features/scene_manager/model/robossembler_assets"; +import { CoreVector3 } from "../model/core_vector3"; export enum UserData { selectedObject = "selected_object", cameraInitialization = "camera_initialization", + objectForMagnetism = "object_for_magnetism", } interface IEventDraggingChange { @@ -93,7 +100,39 @@ export class CoreThreeRepository extends TypedEvent { this.init(); } + drawPoint(point: Vector3): Mesh { + var cube = new Mesh(new BoxGeometry(0.5, 0.5, 0.5), new MeshBasicMaterial({ color: 0x0095dd })); + cube.position.add(point); + this.scene.add(cube); + return cube; + } + makeCube(inc: number, vector?: Vector3, color?: string, size?: number) { + const cube = new Mesh( + new BoxGeometry(size ?? 10, size ?? 10, size ?? 10), + new MeshBasicMaterial({ color: color ?? 0x0095dd, transparent: true, opacity: 0.5 }) + ); + cube.userData[UserData.objectForMagnetism] = true; + cube.userData[UserData.selectedObject] = true; + cube.name = "cube" + String(inc); + if (vector) { + cube.position.copy(vector); + } + this.scene.add(cube); + } + makePoint(vector?: Vector3, color?: string, size?: number) { + const cube = new Mesh( + new BoxGeometry(size ?? 10, size ?? 10, size ?? 10), + new MeshBasicMaterial({ color: color ?? 0x0095dd, transparent: true, opacity: 0.5 }) + ); + cube.userData[UserData.objectForMagnetism] = true; + cube.userData[UserData.selectedObject] = true; + + if (vector) { + cube.position.copy(vector); + } + this.scene.add(cube); + } deleteSceneItem(item: BaseSceneItemModel) { const updateScene = this.scene; updateScene.children = item.deleteToScene(updateScene); @@ -203,12 +242,12 @@ export class CoreThreeRepository extends TypedEvent { } light() { - const directionalLight = new DirectionalLight(0xffffff, 0.2); - directionalLight.castShadow = true; - directionalLight.position.set(-1, 2, 4); - this.scene.add(directionalLight); const ambientLight = new AmbientLight(0xffffff, 0.7); this.scene.add(ambientLight); + + this.scene.add(new AmbientLight(0x222222)); + + this.scene.add(new DirectionalLight(0xffffff, 1)); } addListeners() { @@ -244,6 +283,18 @@ export class CoreThreeRepository extends TypedEvent { floor.userData = {}; floor.userData[UserData.cameraInitialization] = true; this.scene.add(floor); + + const planeMesh = new Mesh( + new PlaneGeometry(10, 10, 10, 10), + new MeshBasicMaterial({ color: 0x808080, wireframe: true }) + ); + planeMesh.userData[UserData.selectedObject] = true; + planeMesh.userData[UserData.objectForMagnetism] = true; + planeMesh.rotation.x = -Math.PI / 2; + this.makeCube(1); + this.makeCube(2, new Vector3(20, 0, 10), "yellow"); + + this.scene.add(planeMesh); } render() { diff --git a/ui/src/core/store/base_store.ts b/ui/src/core/store/base_store.ts index f771f62..7260dac 100644 --- a/ui/src/core/store/base_store.ts +++ b/ui/src/core/store/base_store.ts @@ -1,5 +1,3 @@ -// TODO(IDONTSUDO): нужно переписать все запросы под BaseStore - import { NavigateFunction } from "react-router-dom"; import { Result } from "../helper/result"; import { UiBaseError } from "../model/ui_base_error"; diff --git a/ui/src/core/ui/text/text.tsx b/ui/src/core/ui/text/text.tsx new file mode 100644 index 0000000..3b455e6 --- /dev/null +++ b/ui/src/core/ui/text/text.tsx @@ -0,0 +1,15 @@ +import * as React from "react"; + +export enum CoreTextType { + header, +} + +export interface ITextProps { + text: string; + type: CoreTextType; +} + +export function CoreText(props: ITextProps) { + if (props.type === CoreTextType.header) return
{props.text}
; + return
{props.text}
; +} diff --git a/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx b/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx new file mode 100644 index 0000000..a85c764 --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx @@ -0,0 +1,106 @@ +import * as React from "react"; +import { CoreText, CoreTextType } from "../../../core/ui/text/text"; + +import { useRete } from "rete-react-plugin"; +import { createEditor } from "./ui/editor/editor"; +import { SkillTree } from "./ui/skill_tree/skill_tree"; +import { BehaviorTreeBuilderStore } from "./behavior_tree_builder_store"; + +export const behaviorTreeBuilderScreenPath = "behavior/tree/screen/path"; + +const skills = { + name: "", + children: [ + { + name: "arm", + children: [{ name: "move to", interface: "Vector3", out: "vo" }], + }, + { + name: "image", + children: [ + { + name: "object detection", + interface: "image", + out: "BoundBox", + }, + { name: "people detection", interface: "image", out: "BoundBox" }, + ], + }, + ], +}; +export const behaviorTreeBuilderStore = new BehaviorTreeBuilderStore(); + +export function BehaviorTreeBuilderScreen() { + const [store] = React.useState(behaviorTreeBuilderStore); + const [ref] = useRete(createEditor); + React.useEffect(() => { + store.init(); + + store.dragZoneSetOffset( + // @ts-expect-error + ref.current.offsetTop, + // @ts-expect-error + ref.current.offsetLeft, + // @ts-expect-error + Number(String(ref.current.style.width).replaceAll("px", "")), + // @ts-expect-error + Number(String(ref.current.style.height).replaceAll("px", "")) + ); + + return () => { + store.dispose(); + }; + }, [store, ref]); + + return ( +
+
+ +
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+ ); +} diff --git a/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_store.tsx b/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_store.tsx new file mode 100644 index 0000000..8a2439b --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_store.tsx @@ -0,0 +1,61 @@ +import makeAutoObservable from "mobx-store-inheritance"; + +import { HttpError } from "../../../core/repository/http_repository"; +import { UiErrorState } from "../../../core/store/base_store"; +import { BtNodeView } from "./ui/editor/editor_view"; +interface I2DArea { + x: number; + y: number; + w: number; + h: number; +} +export class BehaviorTreeBuilderStore extends UiErrorState { + area?: I2DArea; + errorHandingStrategy: (error: HttpError) => void; + btNodeView: BtNodeView = new BtNodeView(); + constructor() { + super(); + makeAutoObservable(this); + } + canRun = true; + draw() {} + dragEnd = (e: EventTarget) => { + if (this.canRun) { + //@ts-expect-error + this.drawSkillCheck(e.x as number, e.y as number, e.target.innerText as string); + this.canRun = false; + setTimeout(() => { + this.canRun = true; + }, 1000); + } + }; + drawSkillCheck(x: number, y: number, name: string) { + const drawPoint = { x: x, y: y, w: 1, h: 1 }; + + if ( + drawPoint.x < this.area!.x + this.area!.w && + drawPoint.x + drawPoint.w > this.area!.x && + drawPoint.y < this.area!.y + this.area!.h && + drawPoint.y + drawPoint.h > this.area!.y + ) { + this.drawSkill(x, y - (this.area!.y + this.area!.h / 2), name); + } + } + drawSkill(x: number, y: number, name: string) { + this.btNodeView.emit({ + x: x, + y: y, + name: name, + }); + } + async init(): Promise {} + dragZoneSetOffset(offsetTop: number, offsetWidth: number, width: number, height: number) { + this.area = { + x: offsetTop, + y: offsetWidth, + w: width, + h: height, + }; + } + dispose() {} +} diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/editor/background.css b/ui/src/features/behavior_tree_builder/presentation/ui/editor/background.css new file mode 100644 index 0000000..6db054b --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/editor/background.css @@ -0,0 +1,19 @@ +.fill-area { + display: table; + z-index: -1; + position: absolute; + top: -320000px; + left: -320000px; + width: 640000px; + height: 640000px; +} + +.background { + background-color: #ffffff; + opacity: 1; + background-image: linear-gradient(#f1f1f1 3.2px, transparent 3.2px), + linear-gradient(90deg, #f1f1f1 3.2px, transparent 3.2px), linear-gradient(#f1f1f1 1.6px, transparent 1.6px), + linear-gradient(90deg, #f1f1f1 1.6px, #ffffff 1.6px); + background-size: 80px 80px, 80px 80px, 16px 16px, 16px 16px; + background-position: -3.2px -3.2px, -3.2px -3.2px, -1.6px -1.6px, -1.6px -1.6px; +} diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_background.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_background.tsx new file mode 100644 index 0000000..a3aaab0 --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_background.tsx @@ -0,0 +1,11 @@ +import { BaseSchemes } from "rete"; +import { AreaPlugin } from "rete-area-plugin"; + +export function addCustomBackground(area: AreaPlugin) { + const background = document.createElement("div"); + + background.classList.add("background"); + background.classList.add("fill-area"); + + area.area.content.add(background); +} diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_connection.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_connection.tsx new file mode 100644 index 0000000..a5696af --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_connection.tsx @@ -0,0 +1,36 @@ +import * as React from "react"; +import styled from "styled-components"; +import { ClassicScheme, Presets } from "rete-react-plugin"; + +const { useConnection } = Presets.classic; + +const Svg = styled.svg` + overflow: visible !important; + position: absolute; + pointer-events: none; + width: 9999px; + height: 9999px; +`; + +const Path = styled.path<{ styles?: (props: any) => any }>` + fill: none; + stroke-width: 5px; + stroke: black; + pointer-events: auto; + ${(props) => props.styles && props.styles(props)} +`; + +export function CustomConnection(props: { + data: ClassicScheme["Connection"] & { isLoop?: boolean }; + styles?: () => any; +}) { + const { path } = useConnection(); + + if (!path) return null; + + return ( + + + + ); +} diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_node.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_node.tsx new file mode 100644 index 0000000..b651623 --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_node.tsx @@ -0,0 +1,164 @@ +import * as React from "react"; +import { ClassicScheme, RenderEmit, Presets } from "rete-react-plugin"; +import styled, { css } from "styled-components"; +import { $nodewidth, $socketmargin, $socketsize } from "./vars"; + +const { RefSocket, RefControl } = Presets.classic; + +type NodeExtraData = { width?: number; height?: number }; + +export const NodeStyles = styled.div any }>` + background: black; + border: 2px solid grey; + border-radius: 10px; + cursor: pointer; + box-sizing: border-box; + width: ${(props) => (Number.isFinite(props.width) ? `${props.width}px` : `${$nodewidth}px`)}; + height: ${(props) => (Number.isFinite(props.height) ? `${props.height}px` : "auto")}; + padding-bottom: 6px; + position: relative; + user-select: none; + &:hover { + background: #333; + } + ${(props) => + props.selected && + css` + border-color: red; + `} + .title { + color: white; + font-family: sans-serif; + font-size: 18px; + padding: 8px; + } + .output { + text-align: right; + } + .input { + text-align: left; + } + .output-socket { + text-align: right; + margin-right: -1px; + display: inline-block; + } + .input-socket { + text-align: left; + margin-left: -1px; + display: inline-block; + } + .input-title, + .output-title { + vertical-align: middle; + color: white; + display: inline-block; + font-family: sans-serif; + font-size: 14px; + margin: ${$socketmargin}px; + line-height: ${$socketsize}px; + } + .input-control { + z-index: 1; + width: calc(100% - ${$socketsize + 2 * $socketmargin}px); + vertical-align: middle; + display: inline-block; + } + .control { + display: block; + padding: ${$socketmargin}px ${$socketsize / 2 + $socketmargin}px; + } + ${(props) => props.styles && props.styles(props)} +`; + +function sortByIndex(entries: T) { + entries.sort((a, b) => { + const ai = a[1]?.index || 0; + const bi = b[1]?.index || 0; + + return ai - bi; + }); +} + +type Props = { + data: S["Node"] & NodeExtraData; + styles?: () => any; + emit: RenderEmit; +}; +export type NodeComponent = (props: Props) => JSX.Element; + +export function CustomNode(props: Props) { + const inputs = Object.entries(props.data.inputs); + const outputs = Object.entries(props.data.outputs); + const controls = Object.entries(props.data.controls); + const selected = props.data.selected || false; + const { id, label, width, height } = props.data; + + sortByIndex(inputs); + sortByIndex(outputs); + sortByIndex(controls); + + return ( + +
{ + e.stopPropagation(); + console.log(">>>"); + }} + className="title" + data-testid="title" + > + {label} +
+ + {outputs.map( + ([key, output]) => + output && ( +
+
BODY
+
+ {output?.label} +
+ +
+ ) + )} + + {controls.map(([key, control]) => { + return control ? : null; + })} + {inputs.map( + ([key, input]) => + input && ( +
+ + {input && (!input.control || !input.showControl) && ( +
+ {input?.label} +
+ )} + {input?.control && input?.showControl && ( + + + + )} +
+ ) + )} +
+ ); +} diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_socket.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_socket.tsx new file mode 100644 index 0000000..fbbc456 --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_socket.tsx @@ -0,0 +1,23 @@ +import * as React from "react"; +import { ClassicPreset } from "rete"; +import styled from "styled-components"; +import { $socketsize } from "./vars"; + +const Styles = styled.div` + display: inline-block; + cursor: pointer; + border: 1px solid grey; + width: ${$socketsize}px; + height: ${$socketsize * 2}px; + vertical-align: middle; + background: #fff; + z-index: 2; + box-sizing: border-box; + &:hover { + background: #ddd; + } +`; + +export function CustomSocket(props: { data: T }) { + return ; +} diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/editor/editor.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/editor/editor.tsx new file mode 100644 index 0000000..44dbe3c --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/editor/editor.tsx @@ -0,0 +1,89 @@ +import { createRoot } from "react-dom/client"; +import { NodeEditor, GetSchemes, ClassicPreset } from "rete"; +import { AreaPlugin, AreaExtensions } from "rete-area-plugin"; +import { ConnectionPlugin, Presets as ConnectionPresets } from "rete-connection-plugin"; +import { ReactPlugin, Presets, ReactArea2D } from "rete-react-plugin"; +import { CustomNode } from "./custom_node"; +import { CustomSocket } from "./custom_socket"; +import { CustomConnection } from "./custom_connection"; +import { addCustomBackground } from "./custom_background"; +import { StyledNode } from "./style_node"; +import { behaviorTreeBuilderStore } from "../../behavior_tree_builder_screen"; + +type Schemes = GetSchemes>; +type AreaExtra = ReactArea2D; + +export async function createEditor(container: HTMLElement) { + const socket = new ClassicPreset.Socket("socket"); + + behaviorTreeBuilderStore.btNodeView.on(async (event) => { + setTimeout(async () => { + const node = new ClassicPreset.Node(event.name); + const { x, y } = areaContainer.area.pointer; + console.log(x, y); + await editor.addNode(node); + await areaContainer.translate(node.id, { x, y }); + }, 50); + }); + const editor = new NodeEditor(); + const areaContainer = new AreaPlugin(container); + const connection = new ConnectionPlugin(); + const render = new ReactPlugin({ createRoot }); + + AreaExtensions.selectableNodes(areaContainer, AreaExtensions.selector(), { + accumulating: AreaExtensions.accumulateOnCtrl(), + }); + + render.addPreset( + Presets.classic.setup({ + customize: { + node(context) { + if (context.payload.label === "Fully customized") { + return CustomNode; + } + if (context.payload.label === "Override styles") { + return StyledNode; + } + return Presets.classic.Node; + }, + socket(_context) { + return CustomSocket; + }, + connection(_context) { + return CustomConnection; + }, + }, + }) + ); + + connection.addPreset(ConnectionPresets.classic.setup()); + + addCustomBackground(areaContainer); + + editor.use(areaContainer); + areaContainer.use(connection); + areaContainer.use(render); + + AreaExtensions.simpleNodesOrder(areaContainer); + + const a = new ClassicPreset.Node("Override styles"); + + a.addOutput("a", new ClassicPreset.Output(socket)); + a.addInput("a", new ClassicPreset.Input(socket)); + await editor.addNode(a); + + const b = new ClassicPreset.Node("Fully customized"); + b.addOutput("a", new ClassicPreset.Output(socket)); + b.addInput("a", new ClassicPreset.Input(socket)); + await editor.addNode(b); + + await editor.addConnection(new ClassicPreset.Connection(a, "a", b, "a")); + + setTimeout(() => { + AreaExtensions.zoomAt(areaContainer, editor.getNodes()); + }, 100); + + return { + destroy: () => areaContainer.destroy(), + }; +} diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/editor/editor_view.ts b/ui/src/features/behavior_tree_builder/presentation/ui/editor/editor_view.ts new file mode 100644 index 0000000..027105b --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/editor/editor_view.ts @@ -0,0 +1,7 @@ +import { TypedEvent } from "../../../../../core/helper/typed_event"; +export interface BtDrawView { + x: number; + y: number; + name: string; +} +export class BtNodeView extends TypedEvent {} diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/editor/style_node.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/editor/style_node.tsx new file mode 100644 index 0000000..f0cb56b --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/editor/style_node.tsx @@ -0,0 +1,30 @@ +import { Presets } from "rete-react-plugin"; +import { css } from "styled-components"; +import "./background.css"; + +const styles = css<{ selected?: boolean }>` + background: #ebebeb; + border-color: #646464; + .title { + color: #646464; + } + &:hover { + background: #f2f2f2; + } + .output-socket { + margin-right: -1px; + } + .input-socket { + margin-left: -1px; + } + ${(props) => + props.selected && + css` + border-color: red; + `} +`; + +export function StyledNode(props: any) { + // eslint-disable-next-line react/jsx-pascal-case + return styles} {...props} />; +} diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/editor/vars.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/editor/vars.tsx new file mode 100644 index 0000000..c79b882 --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/editor/vars.tsx @@ -0,0 +1,3 @@ +export const $nodewidth = 200; +export const $socketmargin = 6; +export const $socketsize = 16; diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/skill_tree/skill_tree.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/skill_tree/skill_tree.tsx new file mode 100644 index 0000000..0acb353 --- /dev/null +++ b/ui/src/features/behavior_tree_builder/presentation/ui/skill_tree/skill_tree.tsx @@ -0,0 +1,69 @@ +import * as React from "react"; +import TreeView, { EventCallback, IBranchProps, INode, LeafProps, flattenTree } from "react-accessible-treeview"; +import { IFlatMetadata } from "react-accessible-treeview/dist/TreeView/utils"; +import { CallBackEventTarget } from "../../../../../core/extensions/extensions"; +export interface ISkillTreeProps { + skills: any; + dragEnd: CallBackEventTarget; +} + +interface IRefListerProps { + element: INode; + getNodeProps: (args?: { onClick?: EventCallback }) => IBranchProps | LeafProps; + handleExpand: EventCallback; + handleSelect: EventCallback; + dragEnd: CallBackEventTarget; +} + +export const RefListener = (props: IRefListerProps) => { + const canvasRef = React.useRef(null); + React.useEffect(() => { + if (canvasRef.current) { + canvasRef.current.addEventListener("dragend", (e) => { + // @ts-expect-error + if (e.target.innerHTML) { + // @ts-expect-error + props.dragEnd(e); + } + }); + } + }, [canvasRef, props]); + + return ( +
+
{ + props.handleSelect(e); + }} + /> + + {props.element.name} + +
+ ); +}; +export function SkillTree(props: ISkillTreeProps) { + return ( +
+ {}} + multiSelect + defaultExpandedIds={[1, 2]} + nodeAction="check" + nodeRenderer={({ element, getNodeProps, handleSelect, handleExpand }) => { + return ( + + ); + }} + /> +
+ ); +} diff --git a/ui/src/features/scene_manager/model/scene_view.ts b/ui/src/features/scene_manager/model/scene_view.ts index 2265a58..d282fc7 100644 --- a/ui/src/features/scene_manager/model/scene_view.ts +++ b/ui/src/features/scene_manager/model/scene_view.ts @@ -24,4 +24,5 @@ export enum SceneMode { MOVING = "Moving", EMPTY = "Empty", ADD_CAMERA = "Add camera", + MAGNETISM_MARKING = "magnetism_marking", } diff --git a/ui/src/features/scene_manager/presentation/components/static_asset_item_view.tsx b/ui/src/features/scene_manager/presentation/components/static_asset_item_view.tsx index 040faba..ecfadd3 100644 --- a/ui/src/features/scene_manager/presentation/components/static_asset_item_view.tsx +++ b/ui/src/features/scene_manager/presentation/components/static_asset_item_view.tsx @@ -15,7 +15,6 @@ export function StaticAssetModelView(props: IStaticAssetModelViewProps) { backgroundColor: "ActiveBorder", padding: "10px", color: "white", - width: "100px", textAlignLast: "center", }} > @@ -32,7 +31,6 @@ export function StaticAssetModelView(props: IStaticAssetModelViewProps) { backgroundColor: "brown", padding: "10px", color: "white", - width: "100px", textAlignLast: "center", }} > diff --git a/ui/src/features/scene_manager/presentation/scene_manager.tsx b/ui/src/features/scene_manager/presentation/scene_manager.tsx index bb774c3..47e2884 100644 --- a/ui/src/features/scene_manager/presentation/scene_manager.tsx +++ b/ui/src/features/scene_manager/presentation/scene_manager.tsx @@ -13,22 +13,24 @@ export const SceneManagerPath = "/scene/manager/"; export const SceneManger = observer(() => { const canvasRef = React.useRef(null); - const [sceneMangerStore] = React.useState(() => new SceneMangerStore()); + const [store] = React.useState(() => new SceneMangerStore()); const id = useParams().id as string; React.useEffect(() => { - sceneMangerStore.init(); - sceneMangerStore.loadScene(id, canvasRef.current!); + store.init(); + store.loadScene(id, canvasRef.current!); document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = "scroll"; - sceneMangerStore.dispose(); + store.dispose(); }; - }, [id, sceneMangerStore]); + }, [id, store]); - const sceneIcons: SceneManagerView[] = [SceneMode.ROTATE, SceneMode.MOVING, SceneMode.ADD_CAMERA].map((el) => { - return { name: el, clickHandel: () => sceneMangerStore.setSceneMode(el) }; - }); + const sceneIcons: SceneManagerView[] = Object.values(SceneMode) + .filter((el) => el !== SceneMode.EMPTY) + .map((el) => { + return { name: el, clickHandel: () => store.setSceneMode(el as SceneMode) }; + }); return (
@@ -48,8 +50,7 @@ export const SceneManger = observer(() => { return (
{ el.clickHandel(); @@ -70,25 +71,25 @@ export const SceneManger = observer(() => { }} >
Scene manager
- {sceneMangerStore.isVisibleSaveButton ? ( + {store.isVisibleSaveButton ? ( <> - + ) : ( <> )} - {sceneMangerStore.isLoading ? <>Loading... : <>} - {sceneMangerStore.sceneMode === SceneMode.ADD_CAMERA ? ( + {store.isLoading ? <>Loading... : <>} + {store.sceneMode === SceneMode.ADD_CAMERA ? (
{ - sceneMangerStore.addNewCamera(model); + store.addNewCamera(model); actions.setSubmitting(false); actions.resetForm(); }} validate={(model) => { - return model.validate(sceneMangerStore.getCameraLinkNames()); + return model.validate(store.getCameraLinkNames()); }} render={() => (
@@ -115,19 +116,19 @@ export const SceneManger = observer(() => { ) : ( <> )} - {sceneMangerStore.sceneMode === SceneMode.MOVING || SceneMode.ROTATE ? ( + {store.sceneMode === SceneMode.MOVING || SceneMode.ROTATE ? ( <> - {sceneMangerStore.robossemblerAssets?.assets.map((el) => { + {store.robossemblerAssets?.assets.map((el) => { return (
{el.name} - {sceneMangerStore.isRenderedAsset(el.name) ? ( + {store.isRenderedAsset(el.name) ? ( <> ) : (
- {sceneMangerStore.sceneModels.map((el) => { - return sceneMangerStore.deleteSceneItem(el)} model={el} />; + {store.sceneModels.map((el) => { + return store.deleteSceneItem(el)} model={el} />; })}
- {/* {sceneMangerStore.sceneMenuIsShow ? ( + {store.sceneMode === SceneMode.MAGNETISM_MARKING ? ( <> - +
+
completion of objects
+
+ {store.objectMagnetism ? ( + <>{store.objectMagnetism} + ) : ( + + )} + {store.objectForMagnetism ? <>{store.objectForMagnetism} : } +
+
) : ( <> - )} */} + )}
); diff --git a/ui/src/features/scene_manager/presentation/scene_manager_store.ts b/ui/src/features/scene_manager/presentation/scene_manager_store.ts index 78df48f..2093419 100644 --- a/ui/src/features/scene_manager/presentation/scene_manager_store.ts +++ b/ui/src/features/scene_manager/presentation/scene_manager_store.ts @@ -1,6 +1,6 @@ import makeAutoObservable from "mobx-store-inheritance"; -import { CoreThreeRepository } from "../../../core/repository/core_three_repository"; -import { Object3D, Vector2 } from "three"; +import { CoreThreeRepository, UserData } from "../../../core/repository/core_three_repository"; +import { Object3D, Vector2, Vector3 } from "three"; import { HttpError } from "../../../core/repository/http_repository"; import { UiErrorState } from "../../../core/store/base_store"; import { UiBaseError } from "../../../core/model/ui_base_error"; @@ -19,6 +19,8 @@ export class SceneMangerStore extends UiErrorState { sceneModels: BaseSceneItemModel[] = []; isSceneMenuShow = false; robossemblerAssets?: RobossemblerAssets; + objectForMagnetism: string; + objectMagnetism: string; constructor() { super(); @@ -129,14 +131,14 @@ export class SceneMangerStore extends UiErrorState { } clickLister(event: MouseEvent) { + const vector = new Vector2(); + vector.x = (event.clientX / window.innerWidth) * 2 - 1; + vector.y = -(event.clientY / window.innerHeight) * 2 + 1; if (this.sceneMode === SceneMode.EMPTY) { return; } - if (this.sceneMode === SceneMode.MOVING || this.sceneMode === SceneMode.ROTATE) { - const vector = new Vector2(); - vector.x = (event.clientX / window.innerWidth) * 2 - 1; - vector.y = -(event.clientY / window.innerHeight) * 2 + 1; + if (this.sceneMode === SceneMode.MOVING || this.sceneMode === SceneMode.ROTATE) { this.transformContollsCall(vector); } } diff --git a/ui/src/features/stick_objects_marking/stick_objects_marking_screen.tsx b/ui/src/features/stick_objects_marking/stick_objects_marking_screen.tsx new file mode 100644 index 0000000..74d0056 --- /dev/null +++ b/ui/src/features/stick_objects_marking/stick_objects_marking_screen.tsx @@ -0,0 +1,133 @@ +import * as React from "react"; +import { observer } from "mobx-react-lite"; +import { useParams } from "react-router-dom"; +import { Button } from "antd"; +import { StickObjectsMarkingStore, StickObjectsMarkingStoreMode } from "./stick_objects_marking_store"; +import { Vector3 } from "three"; + +export const StickObjectsMarking = "/stick/objects/marking"; +interface StickButtonsProps { + isVisible: boolean; + name: string; + groupMode: StickObjectsMarkingStoreMode; + storeMode: StickObjectsMarkingStoreMode; + storeModePoints: StickObjectsMarkingStoreMode; + setMode: Function; + setPointMode: Function; + points: Vector3[]; + body: string; +} + +export const StickButtons: React.FunctionComponent = observer((props) => { + return ( + <> + {props.isVisible ? ( + <> + {props.name} + + {props.points.map((el) => { + return ( + <> + {el.x} {el.y} {el.z} + + ); + })} + + ) : ( + + )} + + ); +}); + +export const StickObjectsMarkingScreen = observer(() => { + const canvasRef = React.useRef(null); + const [store] = React.useState(() => new StickObjectsMarkingStore()); + const id = useParams().id as string; + + React.useEffect(() => { + store.init(); + store.loadScene(canvasRef.current!); + document.body.style.overflow = "hidden"; + return () => { + document.body.style.overflow = "scroll"; + store.dispose(); + }; + }, [id, store]); + + return ( +
+ +
+
+ {Object.keys(store.points).map((el) => { + // @ts-expect-error + const v = store.points[el]; + return ( + <> +
+ {el as string}:{v} +
+ + ); + })} +
Marking objects for sticking
+
+ store.setMode(StickObjectsMarkingStoreMode.objectThatSticks)} + groupMode={StickObjectsMarkingStoreMode.objectThatSticks} + name={store.objectThatSticksName} + storeModePoints={StickObjectsMarkingStoreMode.addPointsObjectThatSticks} + isVisible={store.objectThatSticksName !== undefined} + setPointMode={() => store.setMode(StickObjectsMarkingStoreMode.addPointsObjectThatSticks)} + points={store.objectThatSticksNamePoints} + body="objectThatSticksName" + /> + store.setMode(StickObjectsMarkingStoreMode.objectsToWhichItSticks)} + groupMode={StickObjectsMarkingStoreMode.objectsToWhichItSticks} + name={store.objectsToWhichItSticksName} + storeModePoints={StickObjectsMarkingStoreMode.addPointsObjectsToWhichItSticks} + isVisible={store.objectsToWhichItSticksName !== undefined} + setPointMode={() => store.setMode(StickObjectsMarkingStoreMode.addPointsObjectsToWhichItSticks)} + /> + + +
+
+
+
+ ); +}); diff --git a/ui/src/features/stick_objects_marking/stick_objects_marking_store.tsx b/ui/src/features/stick_objects_marking/stick_objects_marking_store.tsx new file mode 100644 index 0000000..644f9f1 --- /dev/null +++ b/ui/src/features/stick_objects_marking/stick_objects_marking_store.tsx @@ -0,0 +1,164 @@ +import makeAutoObservable from "mobx-store-inheritance"; +import { Box3, Vector2, Vector3 } from "three"; +import { UiErrorState } from "../../core/store/base_store"; +import { HttpError } from "../../core/repository/http_repository"; +import { UiBaseError } from "../../core/model/ui_base_error"; +import { RobossemblerFiles } from "../scene_manager/model/scene_assets"; +import { StickObjectsMarkingThreeRepository } from "./stick_objects_marking_three_repository"; +import { UserData } from "../../core/repository/core_three_repository"; +import { ObjectExtensionsIsKeyExists } from "../../core/extensions/object"; + +export enum StickObjectsMarkingStoreMode { + objectsToWhichItSticks = "objectsToWhichItSticks", + objectThatSticks = "objectThatSticks", + addPointsObjectsToWhichItSticks = "addPointsObjectsToWhichItSticks", + addPointsObjectThatSticks = "addPointsObjectThatSticks", + move = "move", +} + +export class StickyHelper { + objectThatSticksName: string; + objectThatSticksNamePoints: Vector3[] = []; + objectsToWhichItSticksName: string; + objectsToWhichItSticksPoints: Vector3[] = []; + + constructor( + objectThatSticksName: string, + objectThatSticksNamePoints: Vector3[], + objectsToWhichItSticksName: string, + objectsToWhichItSticksPoints: Vector3[] + ) { + this.objectThatSticksName = objectThatSticksName; + this.objectThatSticksNamePoints = objectThatSticksNamePoints; + this.objectsToWhichItSticksName = objectsToWhichItSticksName; + this.objectsToWhichItSticksPoints = objectsToWhichItSticksPoints; + } +} + +export class StickObjectsMarkingStore extends UiErrorState { + mode: StickObjectsMarkingStoreMode; + stickObjectsMarkingThreeRepository: null | StickObjectsMarkingThreeRepository = null; + objectThatSticksName: string; + objectThatSticksNamePoints: Vector3[] = []; + objectsToWhichItSticksName: string; + objectsToWhichItSticksPoints: Vector3[] = []; + points = {}; + constructor() { + super(); + makeAutoObservable(this); + this.points = {}; + this.mode = StickObjectsMarkingStoreMode.move; + this.loadStickyJSON( + '{"objectThatSticksName":"cube2","objectThatSticksNamePoints":[{"x":5,"y":-0.5419443937360455,"z":0.041458499858311626}],"objectsToWhichItSticksName":"cube1","objectsToWhichItSticksPoints":[{"x":-5,"y":-2.2134708060033113,"z":1.1937718220731925}]}' + ); + } + updatePoint = (key: string, value: any) => { + // @ts-expect-error + this.points[key] = value; + }; + onSaveSticky(): void { + window.prompt( + "Copy to clipboard: Ctrl+C, Enter", + JSON.stringify( + new StickyHelper( + this.objectThatSticksName, + this.objectThatSticksNamePoints, + this.objectsToWhichItSticksName, + this.objectsToWhichItSticksPoints + ) + ) + ); + } + setMode(stickObjectsMarkingStoreMode: StickObjectsMarkingStoreMode): void { + this.mode = stickObjectsMarkingStoreMode; + } + + loaderWatcher() {} + + async init(): Promise {} + + errorHandingStrategy = (error: HttpError) => { + if (error.status === 404) { + this.errors.push(new UiBaseError(`${RobossemblerFiles.robossemblerAssets} not found to project`)); + } + }; + + async loadScene(canvasRef: HTMLCanvasElement) { + this.loadWebGl(canvasRef); + } + watcherSceneEditorObject() {} + loadWebGl(canvasRef: HTMLCanvasElement): void { + this.stickObjectsMarkingThreeRepository = new StickObjectsMarkingThreeRepository( + canvasRef as HTMLCanvasElement, + this.watcherSceneEditorObject, + this.updatePoint + ); + this.stickObjectsMarkingThreeRepository.stickyHelperLoader([ + new StickyHelper( + this.objectThatSticksName, + this.objectThatSticksNamePoints, + this.objectsToWhichItSticksName, + this.objectsToWhichItSticksPoints + ), + ]); + this.stickObjectsMarkingThreeRepository.render(); + window.addEventListener("click", (event) => this.clickLister(event)); + } + + clickLister(event: MouseEvent) { + const vector = new Vector2(); + vector.x = (event.clientX / window.innerWidth) * 2 - 1; + vector.y = -(event.clientY / window.innerHeight) * 2 + 1; + if (this.mode) { + if (this.mode === StickObjectsMarkingStoreMode.move) { + this.stickObjectsMarkingThreeRepository?.setRayCastAndGetFirstObject(vector).fold( + (success) => this.stickObjectsMarkingThreeRepository?.setTransformControlsAttach(success), + (_error) => this.stickObjectsMarkingThreeRepository?.disposeTransformControlsMode() + ); + } + this.stickObjectsMarkingThreeRepository?.setRayCast(vector).map((touch) => { + const objectMagnetism = touch.filter((el) => el.object.userData[UserData.objectForMagnetism] === true); + const BoundBoxVector = new Box3().setFromObject(objectMagnetism[0].object).getCenter(new Vector3()); + const centerRelativeVector = new Vector3().subVectors(BoundBoxVector, objectMagnetism[0].point); + if (objectMagnetism.isNotEmpty()) { + if (this.mode === StickObjectsMarkingStoreMode.objectsToWhichItSticks) { + this.objectsToWhichItSticksName = objectMagnetism[0].object.name; + } + if (this.mode === StickObjectsMarkingStoreMode.objectThatSticks) { + this.objectThatSticksName = objectMagnetism[0].object.name; + } + if (this.mode === StickObjectsMarkingStoreMode.addPointsObjectThatSticks) { + this.objectThatSticksNamePoints.push(centerRelativeVector); + } + if (this.mode === StickObjectsMarkingStoreMode.addPointsObjectsToWhichItSticks) { + this.objectsToWhichItSticksPoints.push(centerRelativeVector); + } + } + }); + } + } + + dispose() { + window.removeEventListener("click", this.clickLister); + } + loadStickyJSON(value: string): void { + const object = JSON.parse(value) as Object; + if ( + ObjectExtensionsIsKeyExists(object, [ + "objectThatSticksName", + "objectThatSticksNamePoints", + "objectsToWhichItSticksName", + "objectsToWhichItSticksPoints", + ]) + ) { + // @ts-expect-error + this.objectThatSticksName = object["objectThatSticksName"]; + // @ts-expect-error + this.objectThatSticksNamePoints = object["objectThatSticksNamePoints"]; + // @ts-expect-error + this.objectsToWhichItSticksName = object["objectsToWhichItSticksName"]; + // @ts-expect-error + this.objectsToWhichItSticksPoints = object["objectsToWhichItSticksPoints"]; + } + } +} diff --git a/ui/src/features/stick_objects_marking/stick_objects_marking_three_repository.ts b/ui/src/features/stick_objects_marking/stick_objects_marking_three_repository.ts new file mode 100644 index 0000000..b9f136d --- /dev/null +++ b/ui/src/features/stick_objects_marking/stick_objects_marking_three_repository.ts @@ -0,0 +1,101 @@ +import { Box3, BoxGeometry, Mesh, MeshBasicMaterial, Object3D, Vector3 } from "three"; +import { CoreThreeRepository } from "../../core/repository/core_three_repository"; +import { StickyHelper } from "./stick_objects_marking_store"; +import { CoreVector3 } from "../../core/model/core_vector3"; + +export class StickObjectsMarkingThreeRepository extends CoreThreeRepository { + stickyObjects: StickyHelper[]; + drawUiPoint: Function; + constructor(htmlCanvasRef: HTMLCanvasElement, watcherSceneEditorObject: Function, updatePoint: Function) { + super(htmlCanvasRef, watcherSceneEditorObject); + this.drawUiPoint = updatePoint; + this.sceneWatcher(); + } + + getStickyObject(name: string, pointNameHelper: string, index: number) { + const objectThatSticksNameMesh = this.scene.getObjectByName(name); + const pointName = objectThatSticksNameMesh!.name + ":point:" + index + pointNameHelper; + return this.scene.getObjectByName(pointName); + } + + mapperStickyObject(point: Vector3, index: number, name: string, pointNameHelper: string) { + const objectThatSticksNameMesh = this.scene.getObjectByName(name); + const pointName = objectThatSticksNameMesh!.name + ":point:" + index + pointNameHelper; + const sceneObject = this.scene.getObjectByName(pointName); + let pointMesh: Object3D; + if (sceneObject) { + pointMesh = sceneObject; + } else { + pointMesh = new Mesh(new BoxGeometry(1.1, 1.1, 1.1), new MeshBasicMaterial({ color: "#8BC34A" })); + } + pointMesh.position.copy(new Box3().setFromObject(objectThatSticksNameMesh!).getCenter(new Vector3()).add(point)); + pointMesh.name = pointName; + if (sceneObject === undefined) this.scene.add(pointMesh); + } + + stickyHelperLoader(stickyObjects: StickyHelper[]) { + this.stickyObjects = stickyObjects; + + stickyObjects.forEach((el) => { + el.objectThatSticksNamePoints.forEach((point, index) => + this.mapperStickyObject(point, index, el.objectThatSticksName, "objectThatSticksNamePoints") + ); + el.objectsToWhichItSticksPoints.forEach((point, index) => + this.mapperStickyObject(point, index, el.objectsToWhichItSticksName, "objectsToWhichItSticksPoints") + ); + }); + } + getCenter(obj: Object3D) { + return new Box3().setFromObject(obj).getCenter(new Vector3()); + } + sceneWatcher() { + this.transformControls.addEventListener("objectChange", (event) => { + //@ts-expect-error + const sceneActiveObject = event.target.object as Mesh; + + this.stickyObjects.forEach((stickyHelper) => { + if (sceneActiveObject.name === stickyHelper.objectThatSticksName) { + //локальные векторы точек + const objectThatSticksNameLocalVector = stickyHelper.objectThatSticksNamePoints[0]; + const objectsToWhichItSticksPointLocalVector = stickyHelper.objectsToWhichItSticksPoints[0]; + + //глобальные векторы обьектов + const globalVectorObjStickyName = this.scene.getObjectByName(stickyHelper.objectThatSticksName); + + const globalVectorObjToWhichSticks = this.scene.getObjectByName(stickyHelper.objectsToWhichItSticksName); + + const objectsToWhichItSticksNamePosition = new CoreVector3(globalVectorObjToWhichSticks!.position).add( + objectsToWhichItSticksPointLocalVector + ).vector; + + this.scene + .getObjectByName("cube2:point:0objectThatSticksNamePoints") + ?.position.copy(objectsToWhichItSticksNamePosition); + + // this.scene.getObjectByName("cube2:point:0objectThatSticksNamePoints")?.position; + + globalVectorObjStickyName?.position.copy( + this.scene + .getObjectByName("cube2:point:0objectThatSticksNamePoints")! + .position.add(objectsToWhichItSticksPointLocalVector) + ); + + // console.log(this.scene.children.map((e) => console.log(e.name))); + // cube2:point:0objectThatSticksNamePoints + // cube1:point:0objectsToWhichItSticksPoints + + // this.makePoint(objectThatSticksNamePosition, "red", 1.1); + // this.makePoint(objectsToWhichItSticksNamePosition, "red", 1.1); + + // const movePosition = objectThatSticksNamePosition.sub(objectsToWhichItSticksNamePosition); + // const movePosition = new Vector3().subVectors( + // objectThatSticksNamePosition, + // objectsToWhichItSticksNamePosition + // ); + + // this.scene.getObjectByName(stickyHelper.objectsToWhichItSticksName)?.position.copy(movePosition); + } + }); + }); + } +} diff --git a/ui/src/index.tsx b/ui/src/index.tsx index 574c7f8..e93ad76 100644 --- a/ui/src/index.tsx +++ b/ui/src/index.tsx @@ -9,6 +9,8 @@ import { SocketLister } from "./features/socket_lister/socket_lister"; import { RouterProvider } from "react-router-dom"; import { router } from "./core/routers/routers"; import { SceneManger } from "./features/scene_manager/presentation/scene_manager"; +import { BehaviorTreeBuilderScreen } from "./features/behavior_tree_builder/presentation/behavior_tree_builder_screen"; +import { StickObjectsMarkingScreen } from "./features/stick_objects_marking/stick_objects_marking_screen"; extensions(); const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement); @@ -20,7 +22,10 @@ root.render( {/* */} <> - + {/* */} + {/* */} + + );