Scene builder: прилипание добавляемого объекта к сетке и другим объектам
This commit is contained in:
parent
6732d5a98f
commit
40b9b116c1
31 changed files with 1193 additions and 46 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -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]
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { ArrayExtensions } from "./array";
|
||||
import { MapExtensions } from "./map";
|
||||
import { NumberExtensions } from "./number";
|
||||
import { StringExtensions } from "./string";
|
||||
|
||||
export type CallBackVoidFunction = <T>(value: T) => void;
|
||||
export type CallBackStringVoidFunction = (value: string) => void;
|
||||
export type CallBackEventTarget = (value: EventTarget) => void;
|
||||
|
||||
declare global {
|
||||
interface Array<T> {
|
||||
|
@ -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<K, V> {
|
||||
addValueOrMakeCallback(key: K, value: V, callBack: CallBackVoidFunction): void;
|
||||
}
|
||||
interface Vector3 {}
|
||||
}
|
||||
export const extensions = () => {
|
||||
ArrayExtensions();
|
||||
StringExtensions();
|
||||
NumberExtensions();
|
||||
MapExtensions();
|
||||
};
|
||||
|
|
8
ui/src/core/extensions/number.ts
Normal file
8
ui/src/core/extensions/number.ts
Normal file
|
@ -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));
|
||||
};
|
||||
}
|
||||
};
|
7
ui/src/core/extensions/object.ts
Normal file
7
ui/src/core/extensions/object.ts
Normal file
|
@ -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}]}
|
17
ui/src/core/model/core_vector3.ts
Normal file
17
ui/src/core/model/core_vector3.ts
Normal file
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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<BaseSceneItemModel> {
|
|||
|
||||
this.init();
|
||||
}
|
||||
drawPoint(point: Vector3): Mesh<BoxGeometry, MeshBasicMaterial, Object3DEventMap> {
|
||||
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<BaseSceneItemModel> {
|
|||
}
|
||||
|
||||
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<BaseSceneItemModel> {
|
|||
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() {
|
||||
|
|
|
@ -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";
|
||||
|
|
15
ui/src/core/ui/text/text.tsx
Normal file
15
ui/src/core/ui/text/text.tsx
Normal file
|
@ -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 <div style={{ color: "white", fontSize: "20px" }}>{props.text}</div>;
|
||||
return <div>{props.text}</div>;
|
||||
}
|
|
@ -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 (
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
width: "100vw",
|
||||
height: "86px",
|
||||
background: "#041226",
|
||||
}}
|
||||
>
|
||||
<CoreText text="Robossembler studio" type={CoreTextType.header} />
|
||||
</div>
|
||||
<div style={{ display: "flex" }}>
|
||||
<div
|
||||
style={{
|
||||
width: "30vw",
|
||||
height: "70px",
|
||||
background: "#1B2E42",
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
style={{
|
||||
width: "1782px",
|
||||
height: "70px",
|
||||
background: "#244366",
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<div style={{ display: "flex" }}>
|
||||
<div
|
||||
style={{
|
||||
width: "30vw",
|
||||
background: "#1B3041",
|
||||
boxShadow: "inset 0px 10px 4px #16283D",
|
||||
}}
|
||||
>
|
||||
<div style={{ overflow: "auto", height: "100%", width: "100%", padding: "10px" }}>
|
||||
<SkillTree dragEnd={store.dragEnd} skills={skills} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={ref}
|
||||
style={{
|
||||
width: "1782px",
|
||||
height: String(window.innerHeight - 86 - 70) + "px",
|
||||
background: "#244366",
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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<HttpError> {
|
||||
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<any> {}
|
||||
dragZoneSetOffset(offsetTop: number, offsetWidth: number, width: number, height: number) {
|
||||
this.area = {
|
||||
x: offsetTop,
|
||||
y: offsetWidth,
|
||||
w: width,
|
||||
h: height,
|
||||
};
|
||||
}
|
||||
dispose() {}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import { BaseSchemes } from "rete";
|
||||
import { AreaPlugin } from "rete-area-plugin";
|
||||
|
||||
export function addCustomBackground<S extends BaseSchemes, K>(area: AreaPlugin<S, K>) {
|
||||
const background = document.createElement("div");
|
||||
|
||||
background.classList.add("background");
|
||||
background.classList.add("fill-area");
|
||||
|
||||
area.area.content.add(background);
|
||||
}
|
|
@ -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 (
|
||||
<Svg data-testid="connection">
|
||||
<Path styles={props.styles} d={path} />
|
||||
</Svg>
|
||||
);
|
||||
}
|
|
@ -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<NodeExtraData & { selected: boolean; styles?: (props: any) => 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<T extends [string, undefined | { index?: number }][]>(entries: T) {
|
||||
entries.sort((a, b) => {
|
||||
const ai = a[1]?.index || 0;
|
||||
const bi = b[1]?.index || 0;
|
||||
|
||||
return ai - bi;
|
||||
});
|
||||
}
|
||||
|
||||
type Props<S extends ClassicScheme> = {
|
||||
data: S["Node"] & NodeExtraData;
|
||||
styles?: () => any;
|
||||
emit: RenderEmit<S>;
|
||||
};
|
||||
export type NodeComponent<Scheme extends ClassicScheme> = (props: Props<Scheme>) => JSX.Element;
|
||||
|
||||
export function CustomNode<Scheme extends ClassicScheme>(props: Props<Scheme>) {
|
||||
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 (
|
||||
<NodeStyles selected={selected} width={width} height={height} styles={props.styles} data-testid="node">
|
||||
<div
|
||||
onPointerDown={(e) => {
|
||||
e.stopPropagation();
|
||||
console.log(">>>");
|
||||
}}
|
||||
className="title"
|
||||
data-testid="title"
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
|
||||
{outputs.map(
|
||||
([key, output]) =>
|
||||
output && (
|
||||
<div className="output" key={key} data-testid={`output-${key}`}>
|
||||
<div style={{ color: "white" }}>BODY</div>
|
||||
<div className="output-title" data-testid="output-title">
|
||||
{output?.label}
|
||||
</div>
|
||||
<RefSocket
|
||||
name="output-socket"
|
||||
side="output"
|
||||
emit={props.emit}
|
||||
socketKey={key}
|
||||
nodeId={id}
|
||||
payload={output.socket}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{controls.map(([key, control]) => {
|
||||
return control ? <RefControl key={key} name="control" emit={props.emit} payload={control} /> : null;
|
||||
})}
|
||||
{inputs.map(
|
||||
([key, input]) =>
|
||||
input && (
|
||||
<div className="input" key={key} data-testid={`input-${key}`}>
|
||||
<RefSocket
|
||||
name="input-socket"
|
||||
emit={props.emit}
|
||||
side="input"
|
||||
socketKey={key}
|
||||
nodeId={id}
|
||||
payload={input.socket}
|
||||
/>
|
||||
{input && (!input.control || !input.showControl) && (
|
||||
<div className="input-title" data-testid="input-title">
|
||||
{input?.label}
|
||||
</div>
|
||||
)}
|
||||
{input?.control && input?.showControl && (
|
||||
<span className="input-control">
|
||||
<RefControl key={key} name="input-control" emit={props.emit} payload={input.control} />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</NodeStyles>
|
||||
);
|
||||
}
|
|
@ -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<T extends ClassicPreset.Socket>(props: { data: T }) {
|
||||
return <Styles title={props.data.name} />;
|
||||
}
|
|
@ -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<ClassicPreset.Node, ClassicPreset.Connection<ClassicPreset.Node, ClassicPreset.Node>>;
|
||||
type AreaExtra = ReactArea2D<Schemes>;
|
||||
|
||||
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<Schemes>();
|
||||
const areaContainer = new AreaPlugin<Schemes, AreaExtra>(container);
|
||||
const connection = new ConnectionPlugin<Schemes, AreaExtra>();
|
||||
const render = new ReactPlugin<Schemes, AreaExtra>({ 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(),
|
||||
};
|
||||
}
|
|
@ -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<BtDrawView> {}
|
|
@ -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 <Presets.classic.Node styles={() => styles} {...props} />;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export const $nodewidth = 200;
|
||||
export const $socketmargin = 6;
|
||||
export const $socketsize = 16;
|
|
@ -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<IFlatMetadata>;
|
||||
getNodeProps: (args?: { onClick?: EventCallback }) => IBranchProps | LeafProps;
|
||||
handleExpand: EventCallback;
|
||||
handleSelect: EventCallback;
|
||||
dragEnd: CallBackEventTarget;
|
||||
}
|
||||
|
||||
export const RefListener = (props: IRefListerProps) => {
|
||||
const canvasRef = React.useRef<HTMLDataElement>(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 (
|
||||
<div {...props.getNodeProps({ onClick: props.handleExpand })}>
|
||||
<div
|
||||
onClick={(e) => {
|
||||
props.handleSelect(e);
|
||||
}}
|
||||
/>
|
||||
<span ref={canvasRef} style={{ color: "white" }} draggable="true">
|
||||
{props.element.name}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export function SkillTree(props: ISkillTreeProps) {
|
||||
return (
|
||||
<div>
|
||||
<TreeView
|
||||
data={flattenTree(props.skills)}
|
||||
aria-label="onSelect"
|
||||
onSelect={() => {}}
|
||||
multiSelect
|
||||
defaultExpandedIds={[1, 2]}
|
||||
nodeAction="check"
|
||||
nodeRenderer={({ element, getNodeProps, handleSelect, handleExpand }) => {
|
||||
return (
|
||||
<RefListener
|
||||
dragEnd={props.dragEnd}
|
||||
getNodeProps={getNodeProps}
|
||||
element={element}
|
||||
handleExpand={handleExpand}
|
||||
handleSelect={handleSelect}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -24,4 +24,5 @@ export enum SceneMode {
|
|||
MOVING = "Moving",
|
||||
EMPTY = "Empty",
|
||||
ADD_CAMERA = "Add camera",
|
||||
MAGNETISM_MARKING = "magnetism_marking",
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -13,22 +13,24 @@ export const SceneManagerPath = "/scene/manager/";
|
|||
|
||||
export const SceneManger = observer(() => {
|
||||
const canvasRef = React.useRef<HTMLCanvasElement>(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 (
|
||||
<div>
|
||||
|
@ -48,8 +50,7 @@ export const SceneManger = observer(() => {
|
|||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: sceneMangerStore.sceneMode === el.name ? "aqua" : "ActiveBorder",
|
||||
width: "100px",
|
||||
backgroundColor: store.sceneMode === el.name ? "aqua" : "ActiveBorder",
|
||||
}}
|
||||
onClick={() => {
|
||||
el.clickHandel();
|
||||
|
@ -70,25 +71,25 @@ export const SceneManger = observer(() => {
|
|||
}}
|
||||
>
|
||||
<div style={{ color: "white" }}>Scene manager</div>
|
||||
{sceneMangerStore.isVisibleSaveButton ? (
|
||||
{store.isVisibleSaveButton ? (
|
||||
<>
|
||||
<Button onClick={() => sceneMangerStore.onTapSave()}>Save</Button>
|
||||
<Button onClick={() => store.onTapSave()}>Save</Button>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{sceneMangerStore.isLoading ? <>Loading...</> : <></>}
|
||||
{sceneMangerStore.sceneMode === SceneMode.ADD_CAMERA ? (
|
||||
{store.isLoading ? <>Loading...</> : <></>}
|
||||
{store.sceneMode === SceneMode.ADD_CAMERA ? (
|
||||
<div>
|
||||
<Formik
|
||||
initialValues={CameraViewModel.empty()}
|
||||
onSubmit={async (model, actions) => {
|
||||
sceneMangerStore.addNewCamera(model);
|
||||
store.addNewCamera(model);
|
||||
actions.setSubmitting(false);
|
||||
actions.resetForm();
|
||||
}}
|
||||
validate={(model) => {
|
||||
return model.validate(sceneMangerStore.getCameraLinkNames());
|
||||
return model.validate(store.getCameraLinkNames());
|
||||
}}
|
||||
render={() => (
|
||||
<Form>
|
||||
|
@ -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 (
|
||||
<div>
|
||||
<div style={{ color: "white", marginLeft: "10px", marginRight: "10px", display: "contents" }}>
|
||||
{el.name}
|
||||
{sceneMangerStore.isRenderedAsset(el.name) ? (
|
||||
{store.isRenderedAsset(el.name) ? (
|
||||
<></>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() => {
|
||||
sceneMangerStore.loadSceneRobossemblerAsset(el.name);
|
||||
store.loadSceneRobossemblerAsset(el.name);
|
||||
}}
|
||||
>
|
||||
add scene
|
||||
|
@ -144,18 +145,28 @@ export const SceneManger = observer(() => {
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{sceneMangerStore.sceneModels.map((el) => {
|
||||
return <StaticAssetModelView onTap={() => sceneMangerStore.deleteSceneItem(el)} model={el} />;
|
||||
{store.sceneModels.map((el) => {
|
||||
return <StaticAssetModelView onTap={() => store.deleteSceneItem(el)} model={el} />;
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* {sceneMangerStore.sceneMenuIsShow ? (
|
||||
{store.sceneMode === SceneMode.MAGNETISM_MARKING ? (
|
||||
<>
|
||||
<SceneMenu x={sceneMangerStore.sceneMenu.x} y={sceneMangerStore.sceneMenu.y} />
|
||||
<div style={{ backgroundColor: "white" }}>
|
||||
<div>completion of objects</div>
|
||||
<div>
|
||||
{store.objectMagnetism ? (
|
||||
<>{store.objectMagnetism}</>
|
||||
) : (
|
||||
<Button>Selects an object for magnetism</Button>
|
||||
)}
|
||||
{store.objectForMagnetism ? <>{store.objectForMagnetism}</> : <Button>Selects an object magnet</Button>}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)} */}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -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<HttpError> {
|
|||
sceneModels: BaseSceneItemModel[] = [];
|
||||
isSceneMenuShow = false;
|
||||
robossemblerAssets?: RobossemblerAssets;
|
||||
objectForMagnetism: string;
|
||||
objectMagnetism: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -129,14 +131,14 @@ export class SceneMangerStore extends UiErrorState<HttpError> {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<StickButtonsProps> = observer((props) => {
|
||||
return (
|
||||
<>
|
||||
{props.isVisible ? (
|
||||
<>
|
||||
{props.name}
|
||||
<Button
|
||||
style={{
|
||||
backgroundColor: props.storeMode === props.storeModePoints ? "#ff7e1e" : "",
|
||||
}}
|
||||
onClick={() => props.setPointMode()}
|
||||
>
|
||||
add points
|
||||
</Button>
|
||||
{props.points.map((el) => {
|
||||
return (
|
||||
<>
|
||||
{el.x} {el.y} {el.z}
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
style={{
|
||||
backgroundColor: props.groupMode === props.storeMode ? "#ff7e1e" : "",
|
||||
}}
|
||||
onClick={() => props.setMode(props.storeMode)}
|
||||
>
|
||||
{props.body}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export const StickObjectsMarkingScreen = observer(() => {
|
||||
const canvasRef = React.useRef<HTMLCanvasElement>(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 (
|
||||
<div>
|
||||
<canvas ref={canvasRef} style={{ position: "absolute", overflow: "hidden" }} />
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignContent: "center",
|
||||
justifyContent: "space-between",
|
||||
position: "absolute",
|
||||
width: "100vw",
|
||||
}}
|
||||
>
|
||||
<div style={{ backgroundColor: "white", padding: "20px" }}>
|
||||
{Object.keys(store.points).map((el) => {
|
||||
// @ts-expect-error
|
||||
const v = store.points[el];
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
{el as string}:{v}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
<div>Marking objects for sticking</div>
|
||||
<div>
|
||||
<StickButtons
|
||||
storeMode={store.mode}
|
||||
setMode={() => 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"
|
||||
/>
|
||||
<StickButtons
|
||||
body="objectsToWhichItSticksName"
|
||||
points={store.objectsToWhichItSticksPoints}
|
||||
storeMode={store.mode}
|
||||
setMode={() => store.setMode(StickObjectsMarkingStoreMode.objectsToWhichItSticks)}
|
||||
groupMode={StickObjectsMarkingStoreMode.objectsToWhichItSticks}
|
||||
name={store.objectsToWhichItSticksName}
|
||||
storeModePoints={StickObjectsMarkingStoreMode.addPointsObjectsToWhichItSticks}
|
||||
isVisible={store.objectsToWhichItSticksName !== undefined}
|
||||
setPointMode={() => store.setMode(StickObjectsMarkingStoreMode.addPointsObjectsToWhichItSticks)}
|
||||
/>
|
||||
<Button onClick={() => store.onSaveSticky()}>save</Button>
|
||||
<Button
|
||||
onClick={() => store.setMode(StickObjectsMarkingStoreMode.move)}
|
||||
style={{ backgroundColor: store.mode === StickObjectsMarkingStoreMode.move ? "#ff7e1e" : "" }}
|
||||
>
|
||||
run
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
|
@ -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<HttpError> {
|
||||
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<any> {}
|
||||
|
||||
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"];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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(
|
|||
|
||||
{/* </SocketLister> */}
|
||||
<>
|
||||
<SceneManger></SceneManger>
|
||||
{/* <SceneManger></SceneManger> */}
|
||||
{/* <StickObjectsMarkingScreen /> */}
|
||||
|
||||
<BehaviorTreeBuilderScreen />
|
||||
</>
|
||||
</>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue