graphCanvas.tsx 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022
  1. import * as React from "react";
  2. import { GlobalState } from '../globalState';
  3. import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
  4. import { NodeMaterialBlockConnectionPointTypes } from 'babylonjs/Materials/Node/Enums/nodeMaterialBlockConnectionPointTypes';
  5. import { GraphNode } from './graphNode';
  6. import * as dagre from 'dagre';
  7. import { Nullable } from 'babylonjs/types';
  8. import { NodeLink } from './nodeLink';
  9. import { NodePort } from './nodePort';
  10. import { NodeMaterialConnectionPoint, NodeMaterialConnectionPointDirection, NodeMaterialConnectionPointCompatibilityStates } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
  11. import { Vector2 } from 'babylonjs/Maths/math.vector';
  12. import { FragmentOutputBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/fragmentOutputBlock';
  13. import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
  14. import { DataStorage } from 'babylonjs/Misc/dataStorage';
  15. import { GraphFrame } from './graphFrame';
  16. import { IEditorData, IFrameData } from '../nodeLocationInfo';
  17. import { FrameNodePort } from './frameNodePort';
  18. import { Button } from 'babylonjs-gui/2D/controls/button';
  19. import { Engine } from 'babylonjs/Engines/engine';
  20. import { Scene } from 'babylonjs/scene';
  21. import { Container, Rectangle } from 'babylonjs-gui';
  22. import { GuiNode } from './guiNode';
  23. require("./graphCanvas.scss");
  24. export interface IGraphCanvasComponentProps {
  25. globalState: GlobalState
  26. }
  27. export type FramePortData = {
  28. frame: GraphFrame,
  29. port: FrameNodePort
  30. }
  31. export const isFramePortData = (variableToCheck: any): variableToCheck is FramePortData => {
  32. if (variableToCheck) {
  33. return (variableToCheck as FramePortData).port !== undefined;
  34. }
  35. else return false;
  36. }
  37. export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentProps> {
  38. private readonly MinZoom = 0.1;
  39. private readonly MaxZoom = 4;
  40. private _hostCanvas: HTMLDivElement;
  41. private _graphCanvas: HTMLDivElement;
  42. private _selectionContainer: HTMLDivElement;
  43. private _frameContainer: HTMLDivElement;
  44. private _svgCanvas: HTMLElement;
  45. private _rootContainer: HTMLDivElement;
  46. private _nodes: GraphNode[] = [];
  47. private _links: NodeLink[] = [];
  48. private _mouseStartPointX: Nullable<number> = null;
  49. private _mouseStartPointY: Nullable<number> = null
  50. private _dropPointX = 0;
  51. private _dropPointY = 0;
  52. private _selectionStartX = 0;
  53. private _selectionStartY = 0;
  54. private _candidateLinkedHasMoved = false;
  55. private _x = 0;
  56. private _y = 0;
  57. private _zoom = 1;
  58. private _selectedNodes: GraphNode[] = [];
  59. private _selectedGuiNodes: GuiNode[] = [];
  60. private _selectedLink: Nullable<NodeLink> = null;
  61. private _selectedPort: Nullable<NodePort> = null;
  62. private _candidateLink: Nullable<NodeLink> = null;
  63. private _candidatePort: Nullable<NodePort | FrameNodePort> = null;
  64. private _gridSize = 20;
  65. private _selectionBox: Nullable<HTMLDivElement> = null;
  66. private _selectedFrame: Nullable<GraphFrame> = null;
  67. private _frameCandidate: Nullable<HTMLDivElement> = null;
  68. private _frames: GraphFrame[] = [];
  69. private _altKeyIsPressed = false;
  70. private _ctrlKeyIsPressed = false;
  71. private _oldY = -1;
  72. public _frameIsMoving = false;
  73. public _isLoading = false;
  74. public get gridSize() {
  75. return this._gridSize;
  76. }
  77. public set gridSize(value: number) {
  78. this._gridSize = value;
  79. this.updateTransform();
  80. }
  81. public get globalState(){
  82. return this.props.globalState;
  83. }
  84. public get nodes() {
  85. return this._nodes;
  86. }
  87. public get links() {
  88. return this._links;
  89. }
  90. public get frames() {
  91. return this._frames;
  92. }
  93. public get zoom() {
  94. return this._zoom;
  95. }
  96. public set zoom(value: number) {
  97. if (this._zoom === value) {
  98. return;
  99. }
  100. this._zoom = value;
  101. this.updateTransform();
  102. }
  103. public get x() {
  104. return this._x;
  105. }
  106. public set x(value: number) {
  107. this._x = value;
  108. this.updateTransform();
  109. }
  110. public get y() {
  111. return this._y;
  112. }
  113. public set y(value: number) {
  114. this._y = value;
  115. this.updateTransform();
  116. }
  117. public get selectedNodes() {
  118. return this._selectedNodes;
  119. }
  120. public get selectedGuiNodes() {
  121. return this._selectedGuiNodes;
  122. }
  123. public get selectedLink() {
  124. return this._selectedLink;
  125. }
  126. public get selectedFrame() {
  127. return this._selectedFrame;
  128. }
  129. public get selectedPort() {
  130. return this._selectedPort;
  131. }
  132. public get canvasContainer() {
  133. return this._graphCanvas;
  134. }
  135. public get hostCanvas() {
  136. return this._hostCanvas;
  137. }
  138. public get svgCanvas() {
  139. return this._svgCanvas;
  140. }
  141. public get selectionContainer() {
  142. return this._selectionContainer;
  143. }
  144. public get frameContainer() {
  145. return this._frameContainer;
  146. }
  147. constructor(props: IGraphCanvasComponentProps) {
  148. super(props);
  149. props.globalState.onSelectionChangedObservable.add(selection => {
  150. if (!selection) {
  151. this._selectedNodes = [];
  152. this._selectedLink = null;
  153. this._selectedFrame = null;
  154. this._selectedPort = null;
  155. } else {
  156. if (selection instanceof NodeLink) {
  157. this._selectedNodes = [];
  158. this._selectedFrame = null;
  159. this._selectedLink = selection;
  160. this._selectedPort = null;
  161. } else if (selection instanceof GraphFrame) {
  162. this._selectedNodes = [];
  163. this._selectedFrame = selection;
  164. this._selectedLink = null;
  165. this._selectedPort = null;
  166. } else if (selection instanceof GraphNode){
  167. if (this._ctrlKeyIsPressed) {
  168. if (this._selectedNodes.indexOf(selection) === -1) {
  169. this._selectedNodes.push(selection);
  170. }
  171. } else {
  172. this._selectedNodes = [selection];
  173. }
  174. } else if(selection instanceof NodePort){
  175. this._selectedNodes = [];
  176. this._selectedFrame = null;
  177. this._selectedLink = null;
  178. this._selectedPort = selection;
  179. } else {
  180. this._selectedNodes = [];
  181. this._selectedFrame = null;
  182. this._selectedLink = null;
  183. this._selectedPort = selection.port;
  184. }
  185. }
  186. });
  187. props.globalState.onCandidatePortSelectedObservable.add(port => {
  188. this._candidatePort = port;
  189. });
  190. props.globalState.onGridSizeChanged.add(() => {
  191. this.gridSize = DataStorage.ReadNumber("GridSize", 20);
  192. });
  193. this.props.globalState.hostDocument!.addEventListener("keyup", () => this.onKeyUp(), false);
  194. this.props.globalState.hostDocument!.addEventListener("keydown", evt => {
  195. this._altKeyIsPressed = evt.altKey;
  196. this._ctrlKeyIsPressed = evt.ctrlKey;
  197. }, false);
  198. this.props.globalState.hostDocument!.defaultView!.addEventListener("blur", () => {
  199. this._altKeyIsPressed = false;
  200. this._ctrlKeyIsPressed = false;
  201. }, false);
  202. // Store additional data to serialization object
  203. this.props.globalState.storeEditorData = (editorData, graphFrame) => {
  204. editorData.frames = [];
  205. if (graphFrame) {
  206. editorData.frames.push(graphFrame!.serialize());
  207. } else {
  208. editorData.x = this.x;
  209. editorData.y = this.y;
  210. editorData.zoom = this.zoom;
  211. for (var frame of this._frames) {
  212. editorData.frames.push(frame.serialize());
  213. }
  214. }
  215. }
  216. }
  217. public getGridPosition(position: number, useCeil = false) {
  218. let gridSize = this.gridSize;
  219. if (gridSize === 0) {
  220. return position;
  221. }
  222. if (useCeil) {
  223. return gridSize * Math.ceil(position / gridSize);
  224. }
  225. return gridSize * Math.floor(position / gridSize);
  226. }
  227. public getGridPositionCeil(position: number) {
  228. let gridSize = this.gridSize;
  229. if (gridSize === 0) {
  230. return position;
  231. }
  232. return gridSize * Math.ceil(position / gridSize);
  233. }
  234. updateTransform() {
  235. this._rootContainer.style.transform = `translate(${this._x}px, ${this._y}px) scale(${this._zoom})`;
  236. if (DataStorage.ReadBoolean("ShowGrid", true)) {
  237. this._hostCanvas.style.backgroundSize = `${this._gridSize * this._zoom}px ${this._gridSize * this._zoom}px`;
  238. this._hostCanvas.style.backgroundPosition = `${this._x}px ${this._y}px`;
  239. } else {
  240. this._hostCanvas.style.backgroundSize = `0`;
  241. }
  242. }
  243. onKeyUp() {
  244. this._altKeyIsPressed = false;
  245. this._ctrlKeyIsPressed = false;
  246. this._oldY = -1;
  247. }
  248. findNodeFromBlock(block: NodeMaterialBlock) {
  249. return this.nodes.filter(n => n.block === block)[0];
  250. }
  251. reset() {
  252. for (var node of this._nodes) {
  253. node.dispose();
  254. }
  255. const frames = this._frames.splice(0);
  256. for (var frame of frames) {
  257. frame.dispose();
  258. }
  259. this._nodes = [];
  260. this._frames = [];
  261. this._links = [];
  262. this._graphCanvas.innerHTML = "";
  263. this._svgCanvas.innerHTML = "";
  264. }
  265. connectPorts(pointA: NodeMaterialConnectionPoint, pointB: NodeMaterialConnectionPoint) {
  266. var blockA = pointA.ownerBlock;
  267. var blockB = pointB.ownerBlock;
  268. var nodeA = this.findNodeFromBlock(blockA);
  269. var nodeB = this.findNodeFromBlock(blockB);
  270. if (!nodeA || !nodeB) {
  271. return;
  272. }
  273. var portA = nodeA.getPortForConnectionPoint(pointA);
  274. var portB = nodeB.getPortForConnectionPoint(pointB);
  275. if (!portA || !portB) {
  276. return;
  277. }
  278. for (var currentLink of this._links) {
  279. if (currentLink.portA === portA && currentLink.portB === portB) {
  280. return;
  281. }
  282. if (currentLink.portA === portB && currentLink.portB === portA) {
  283. return;
  284. }
  285. }
  286. const link = new NodeLink(this, portA, nodeA, portB, nodeB);
  287. this._links.push(link);
  288. nodeA.links.push(link);
  289. nodeB.links.push(link);
  290. }
  291. removeLink(link: NodeLink) {
  292. let index = this._links.indexOf(link);
  293. if (index > -1) {
  294. this._links.splice(index, 1);
  295. }
  296. link.dispose();
  297. }
  298. appendBlock(block: NodeMaterialBlock) {
  299. let newNode = new GraphNode(block, this.props.globalState);
  300. newNode.appendVisual(this._graphCanvas, this);
  301. this._nodes.push(newNode);
  302. return newNode;
  303. }
  304. distributeGraph() {
  305. this.x = 0;
  306. this.y = 0;
  307. this.zoom = 1;
  308. let graph = new dagre.graphlib.Graph();
  309. graph.setGraph({});
  310. graph.setDefaultEdgeLabel(() => ({}));
  311. graph.graph().rankdir = "LR";
  312. // Build dagre graph
  313. this._nodes.forEach(node => {
  314. if (this._frames.some(f => f.nodes.indexOf(node) !== -1)) {
  315. return;
  316. }
  317. graph.setNode(node.id.toString(), {
  318. id: node.id,
  319. type: "node",
  320. width: node.width,
  321. height: node.height
  322. });
  323. });
  324. this._frames.forEach(frame => {
  325. graph.setNode(frame.id.toString(), {
  326. id: frame.id,
  327. type: "frame",
  328. width: frame.element.clientWidth,
  329. height: frame.element.clientHeight
  330. });
  331. })
  332. this._nodes.forEach(node => {
  333. node.block.outputs.forEach(output => {
  334. if (!output.hasEndpoints) {
  335. return;
  336. }
  337. output.endpoints.forEach(endpoint => {
  338. let sourceFrames = this._frames.filter(f => f.nodes.indexOf(node) !== -1);
  339. let targetFrames = this._frames.filter(f => f.nodes.some(n => n.block === endpoint.ownerBlock));
  340. let sourceId = sourceFrames.length > 0 ? sourceFrames[0].id : node.id;
  341. let targetId = targetFrames.length > 0 ? targetFrames[0].id : endpoint.ownerBlock.uniqueId;
  342. graph.setEdge(sourceId.toString(), targetId.toString());
  343. });
  344. });
  345. });
  346. // Distribute
  347. dagre.layout(graph);
  348. // Update graph
  349. let dagreNodes = graph.nodes().map(node => graph.node(node));
  350. dagreNodes.forEach((dagreNode: any) => {
  351. if (!dagreNode) {
  352. return;
  353. }
  354. if (dagreNode.type === "node") {
  355. for (var node of this._nodes) {
  356. if (node.id === dagreNode.id) {
  357. node.x = dagreNode.x - dagreNode.width / 2;
  358. node.y = dagreNode.y - dagreNode.height / 2;
  359. node.cleanAccumulation();
  360. return;
  361. }
  362. }
  363. return;
  364. }
  365. for (var frame of this._frames) {
  366. if (frame.id === dagreNode.id) {
  367. this._frameIsMoving = true;
  368. frame.move(dagreNode.x - dagreNode.width / 2, dagreNode.y - dagreNode.height / 2, false);
  369. frame.cleanAccumulation();
  370. this._frameIsMoving = false;
  371. return;
  372. }
  373. }
  374. });
  375. }
  376. componentDidMount() {
  377. this._hostCanvas = this.props.globalState.hostDocument.getElementById("graph-canvas") as HTMLDivElement;
  378. this._rootContainer = this.props.globalState.hostDocument.getElementById("graph-container") as HTMLDivElement;
  379. this._graphCanvas = this.props.globalState.hostDocument.getElementById("graph-canvas-container") as HTMLDivElement;
  380. this._svgCanvas = this.props.globalState.hostDocument.getElementById("graph-svg-container") as HTMLElement;
  381. this._selectionContainer = this.props.globalState.hostDocument.getElementById("selection-container") as HTMLDivElement;
  382. this._frameContainer = this.props.globalState.hostDocument.getElementById("frame-container") as HTMLDivElement;
  383. this.gridSize = DataStorage.ReadNumber("GridSize", 20);
  384. this.updateTransform();
  385. }
  386. onMove(evt: React.PointerEvent) {
  387. // Selection box
  388. if (this._selectionBox) {
  389. const rootRect = this.canvasContainer.getBoundingClientRect();
  390. const localX = evt.pageX - rootRect.left;
  391. const localY = evt.pageY - rootRect.top;
  392. if (localX > this._selectionStartX) {
  393. this._selectionBox.style.left = `${this._selectionStartX / this.zoom}px`;
  394. this._selectionBox.style.width = `${(localX - this._selectionStartX) / this.zoom}px`;
  395. } else {
  396. this._selectionBox.style.left = `${localX / this.zoom}px`;
  397. this._selectionBox.style.width = `${(this._selectionStartX - localX) / this.zoom}px`;
  398. }
  399. if (localY > this._selectionStartY) {
  400. this._selectionBox.style.top = `${this._selectionStartY / this.zoom}px`;
  401. this._selectionBox.style.height = `${(localY - this._selectionStartY) / this.zoom}px`;
  402. } else {
  403. this._selectionBox.style.top = `${localY / this.zoom}px`;
  404. this._selectionBox.style.height = `${(this._selectionStartY - localY) / this.zoom}px`;
  405. }
  406. this.props.globalState.onSelectionBoxMoved.notifyObservers(this._selectionBox.getBoundingClientRect());
  407. return;
  408. }
  409. // Candidate frame box
  410. if (this._frameCandidate) {
  411. const rootRect = this.canvasContainer.getBoundingClientRect();
  412. const localX = evt.pageX - rootRect.left;
  413. const localY = evt.pageY - rootRect.top;
  414. if (localX > this._selectionStartX) {
  415. this._frameCandidate.style.left = `${this._selectionStartX / this.zoom}px`;
  416. this._frameCandidate.style.width = `${(localX - this._selectionStartX) / this.zoom}px`;
  417. } else {
  418. this._frameCandidate.style.left = `${localX / this.zoom}px`;
  419. this._frameCandidate.style.width = `${(this._selectionStartX - localX) / this.zoom}px`;
  420. }
  421. if (localY > this._selectionStartY) {
  422. this._frameCandidate.style.top = `${this._selectionStartY / this.zoom}px`;
  423. this._frameCandidate.style.height = `${(localY - this._selectionStartY) / this.zoom}px`;
  424. } else {
  425. this._frameCandidate.style.top = `${localY / this.zoom}px`;
  426. this._frameCandidate.style.height = `${(this._selectionStartY - localY) / this.zoom}px`;
  427. }
  428. return;
  429. }
  430. // Candidate link
  431. if (this._candidateLink) {
  432. const rootRect = this.canvasContainer.getBoundingClientRect();
  433. this._candidatePort = null;
  434. this.props.globalState.onCandidateLinkMoved.notifyObservers(new Vector2(evt.pageX, evt.pageY));
  435. this._dropPointX = (evt.pageX - rootRect.left) / this.zoom;
  436. this._dropPointY = (evt.pageY - rootRect.top) / this.zoom;
  437. this._candidateLink.update(this._dropPointX, this._dropPointY, true);
  438. this._candidateLinkedHasMoved = true;
  439. return;
  440. }
  441. // Zoom with mouse + alt
  442. if (this._altKeyIsPressed && evt.buttons === 1) {
  443. if (this._oldY < 0) {
  444. this._oldY = evt.pageY;
  445. }
  446. let zoomDelta = (evt.pageY - this._oldY) / 10;
  447. if (Math.abs(zoomDelta) > 5) {
  448. const oldZoom = this.zoom;
  449. this.zoom = Math.max(Math.min(this.MaxZoom, this.zoom + zoomDelta / 100), this.MinZoom);
  450. const boundingRect = evt.currentTarget.getBoundingClientRect();
  451. const clientWidth = boundingRect.width;
  452. const widthDiff = clientWidth * this.zoom - clientWidth * oldZoom;
  453. const clientX = evt.clientX - boundingRect.left;
  454. const xFactor = (clientX - this.x) / oldZoom / clientWidth;
  455. this.x = this.x - widthDiff * xFactor;
  456. this._oldY = evt.pageY;
  457. }
  458. return;
  459. }
  460. // Move canvas
  461. this._rootContainer.style.cursor = "move";
  462. if (this._mouseStartPointX === null || this._mouseStartPointY === null) {
  463. return;
  464. }
  465. this.x += evt.clientX - this._mouseStartPointX;
  466. this.y += evt.clientY - this._mouseStartPointY;
  467. this._mouseStartPointX = evt.clientX;
  468. this._mouseStartPointY = evt.clientY;
  469. }
  470. onDown(evt: React.PointerEvent<HTMLElement>) {
  471. this._rootContainer.setPointerCapture(evt.pointerId);
  472. // Selection?
  473. if (evt.currentTarget === this._hostCanvas && evt.ctrlKey) {
  474. this._selectionBox = this.props.globalState.hostDocument.createElement("div");
  475. this._selectionBox.classList.add("selection-box");
  476. this._selectionContainer.appendChild(this._selectionBox);
  477. const rootRect = this.canvasContainer.getBoundingClientRect();
  478. this._selectionStartX = (evt.pageX - rootRect.left);
  479. this._selectionStartY = (evt.pageY - rootRect.top);
  480. this._selectionBox.style.left = `${this._selectionStartX / this.zoom}px`;
  481. this._selectionBox.style.top = `${this._selectionStartY / this.zoom}px`;
  482. this._selectionBox.style.width = "0px";
  483. this._selectionBox.style.height = "0px";
  484. return;
  485. }
  486. // Frame?
  487. if (evt.currentTarget === this._hostCanvas && evt.shiftKey) {
  488. this._frameCandidate = this.props.globalState.hostDocument.createElement("div");
  489. this._frameCandidate.classList.add("frame-box");
  490. this._frameContainer.appendChild(this._frameCandidate);
  491. const rootRect = this.canvasContainer.getBoundingClientRect();
  492. this._selectionStartX = (evt.pageX - rootRect.left);
  493. this._selectionStartY = (evt.pageY - rootRect.top);
  494. this._frameCandidate.style.left = `${this._selectionStartX / this.zoom}px`;
  495. this._frameCandidate.style.top = `${this._selectionStartY / this.zoom}px`;
  496. this._frameCandidate.style.width = "0px";
  497. this._frameCandidate.style.height = "0px";
  498. return;
  499. }
  500. // Port dragging
  501. if (evt.nativeEvent.srcElement && (evt.nativeEvent.srcElement as HTMLElement).nodeName === "IMG") {
  502. if (!this._candidateLink) {
  503. let portElement = ((evt.nativeEvent.srcElement as HTMLElement).parentElement as any).port as NodePort;
  504. this._candidateLink = new NodeLink(this, portElement, portElement.node);
  505. this._candidateLinkedHasMoved = false;
  506. }
  507. return;
  508. }
  509. this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
  510. this._mouseStartPointX = evt.clientX;
  511. this._mouseStartPointY = evt.clientY;
  512. }
  513. onUp(evt: React.PointerEvent) {
  514. this._mouseStartPointX = null;
  515. this._mouseStartPointY = null;
  516. this._rootContainer.releasePointerCapture(evt.pointerId);
  517. this._oldY = -1;
  518. if (this._candidateLink) {
  519. if (this._candidateLinkedHasMoved) {
  520. this.processCandidatePort();
  521. this.props.globalState.onCandidateLinkMoved.notifyObservers(null);
  522. } else { // is a click event on NodePort
  523. if(this._candidateLink.portA instanceof FrameNodePort) { //only on Frame Node Ports
  524. const port = this._candidateLink.portA;
  525. const frame = this.frames.find((frame: GraphFrame) => frame.id === port.parentFrameId);
  526. if (frame) {
  527. const data: FramePortData = {
  528. frame,
  529. port
  530. }
  531. this.props.globalState.onSelectionChangedObservable.notifyObservers(data);
  532. }
  533. } else if(this._candidateLink.portA instanceof NodePort){
  534. this.props.globalState.onSelectionChangedObservable.notifyObservers(this._candidateLink.portA );
  535. }
  536. }
  537. this._candidateLink.dispose();
  538. this._candidateLink = null;
  539. this._candidatePort = null;
  540. }
  541. if (this._selectionBox) {
  542. this._selectionBox.parentElement!.removeChild(this._selectionBox);
  543. this._selectionBox = null;
  544. }
  545. if (this._frameCandidate) {
  546. let newFrame = new GraphFrame(this._frameCandidate, this);
  547. this._frames.push(newFrame);
  548. this._frameCandidate.parentElement!.removeChild(this._frameCandidate);
  549. this._frameCandidate = null;
  550. this.props.globalState.onSelectionChangedObservable.notifyObservers(newFrame);
  551. }
  552. }
  553. onWheel(evt: React.WheelEvent) {
  554. let delta = evt.deltaY < 0 ? 0.1 : -0.1;
  555. let oldZoom = this.zoom;
  556. this.zoom = Math.min(Math.max(this.MinZoom, this.zoom + delta * this.zoom), this.MaxZoom);
  557. const boundingRect = evt.currentTarget.getBoundingClientRect();
  558. const clientWidth = boundingRect.width;
  559. const clientHeight = boundingRect.height;
  560. const widthDiff = clientWidth * this.zoom - clientWidth * oldZoom;
  561. const heightDiff = clientHeight * this.zoom - clientHeight * oldZoom;
  562. const clientX = evt.clientX - boundingRect.left;
  563. const clientY = evt.clientY - boundingRect.top;
  564. const xFactor = (clientX - this.x) / oldZoom / clientWidth;
  565. const yFactor = (clientY - this.y) / oldZoom / clientHeight;
  566. this.x = this.x - widthDiff * xFactor;
  567. this.y = this.y - heightDiff * yFactor;
  568. evt.stopPropagation();
  569. }
  570. zoomToFit() {
  571. // Get negative offset
  572. let minX = 0;
  573. let minY = 0;
  574. this._nodes.forEach(node => {
  575. if (this._frames.some(f => f.nodes.indexOf(node) !== -1)) {
  576. return;
  577. }
  578. if (node.x < minX) {
  579. minX = node.x;
  580. }
  581. if (node.y < minY) {
  582. minY = node.y;
  583. }
  584. });
  585. this._frames.forEach(frame => {
  586. if (frame.x < minX) {
  587. minX = frame.x;
  588. }
  589. if (frame.y < minY) {
  590. minY = frame.y;
  591. }
  592. });
  593. // Restore to 0
  594. this._frames.forEach(frame => {
  595. frame.x += -minX;
  596. frame.y += -minY;
  597. frame.cleanAccumulation();
  598. });
  599. this._nodes.forEach(node => {
  600. node.x += -minX;
  601. node.y += -minY;
  602. node.cleanAccumulation();
  603. });
  604. // Get correct zoom
  605. const xFactor = this._rootContainer.clientWidth / this._rootContainer.scrollWidth;
  606. const yFactor = this._rootContainer.clientHeight / this._rootContainer.scrollHeight;
  607. const zoomFactor = xFactor < yFactor ? xFactor : yFactor;
  608. this.zoom = zoomFactor;
  609. this.x = 0;
  610. this.y = 0;
  611. }
  612. processCandidatePort() {
  613. let pointB = this._candidateLink!.portA.connectionPoint;
  614. let nodeB = this._candidateLink!.portA.node;
  615. let pointA: NodeMaterialConnectionPoint;
  616. let nodeA: GraphNode;
  617. if (this._candidatePort) {
  618. pointA = this._candidatePort.connectionPoint;
  619. nodeA = this._candidatePort.node;
  620. } else {
  621. if (pointB.direction === NodeMaterialConnectionPointDirection.Output) {
  622. return;
  623. }
  624. // No destination so let's spin a new input block
  625. let pointName = "output", inputBlock;
  626. let customInputBlock = this._candidateLink!.portA.connectionPoint.createCustomInputBlock();
  627. if (!customInputBlock) {
  628. inputBlock = new InputBlock(NodeMaterialBlockConnectionPointTypes[this._candidateLink!.portA.connectionPoint.type], undefined, this._candidateLink!.portA.connectionPoint.type);
  629. } else {
  630. [inputBlock, pointName] = customInputBlock;
  631. }
  632. this.props.globalState.nodeMaterial.attachedBlocks.push(inputBlock);
  633. pointA = (inputBlock as any)[pointName];
  634. nodeA = this.appendBlock(inputBlock);
  635. nodeA.x = this._dropPointX - 200;
  636. nodeA.y = this._dropPointY - 50;
  637. }
  638. if (pointA.direction === NodeMaterialConnectionPointDirection.Input) {
  639. let temp = pointB;
  640. pointB = pointA;
  641. pointA = temp;
  642. let tempNode = nodeA;
  643. nodeA = nodeB;
  644. nodeB = tempNode;
  645. }
  646. if (pointB.connectedPoint === pointA) {
  647. return;
  648. }
  649. if (pointB === pointA) {
  650. return;
  651. }
  652. if (pointB.direction === pointA.direction) {
  653. return;
  654. }
  655. if (pointB.ownerBlock === pointA.ownerBlock) {
  656. return;
  657. }
  658. // Check compatibility
  659. let isFragmentOutput = pointB.ownerBlock.getClassName() === "FragmentOutputBlock";
  660. let compatibilityState = pointA.checkCompatibilityState(pointB);
  661. if ((pointA.needDualDirectionValidation || pointB.needDualDirectionValidation) && compatibilityState === NodeMaterialConnectionPointCompatibilityStates.Compatible && !(pointA instanceof InputBlock)) {
  662. compatibilityState = pointB.checkCompatibilityState(pointA);
  663. }
  664. if (compatibilityState === NodeMaterialConnectionPointCompatibilityStates.Compatible) {
  665. if (isFragmentOutput) {
  666. let fragmentBlock = pointB.ownerBlock as FragmentOutputBlock;
  667. if (pointB.name === "rgb" && fragmentBlock.rgba.isConnected) {
  668. nodeB.getLinksForConnectionPoint(fragmentBlock.rgba)[0].dispose();
  669. } else if (pointB.name === "rgba" && fragmentBlock.rgb.isConnected) {
  670. nodeB.getLinksForConnectionPoint(fragmentBlock.rgb)[0].dispose();
  671. }
  672. }
  673. } else {
  674. let message = "";
  675. switch (compatibilityState) {
  676. case NodeMaterialConnectionPointCompatibilityStates.TypeIncompatible:
  677. message = "Cannot connect two different connection types";
  678. break;
  679. case NodeMaterialConnectionPointCompatibilityStates.TargetIncompatible:
  680. message = "Source block can only work in fragment shader whereas destination block is currently aimed for the vertex shader";
  681. break;
  682. }
  683. this.props.globalState.onErrorMessageDialogRequiredObservable.notifyObservers(message);
  684. return;
  685. }
  686. let linksToNotifyForDispose: Nullable<NodeLink[]> = null;
  687. if (pointB.isConnected) {
  688. let links = nodeB.getLinksForConnectionPoint(pointB);
  689. linksToNotifyForDispose = links.slice();
  690. links.forEach(link => {
  691. link.dispose(false);
  692. });
  693. }
  694. if (pointB.ownerBlock.inputsAreExclusive) { // Disconnect all inputs if block has exclusive inputs
  695. pointB.ownerBlock.inputs.forEach(i => {
  696. let links = nodeB.getLinksForConnectionPoint(i);
  697. if (!linksToNotifyForDispose) {
  698. linksToNotifyForDispose = links.slice();
  699. } else {
  700. linksToNotifyForDispose.push(...links.slice());
  701. }
  702. links.forEach(link => {
  703. link.dispose(false);
  704. });
  705. })
  706. }
  707. pointA.connectTo(pointB);
  708. this.connectPorts(pointA, pointB);
  709. if (pointB.innerType === NodeMaterialBlockConnectionPointTypes.AutoDetect) {
  710. // need to potentially propagate the type of pointA to other ports of blocks connected to owner of pointB
  711. const refreshNode = (node: GraphNode) => {
  712. node.refresh();
  713. const links = node.links;
  714. // refresh first the nodes so that the right types are assigned to the auto-detect ports
  715. links.forEach((link) => {
  716. const nodeA = link.nodeA, nodeB = link.nodeB;
  717. if (!visitedNodes.has(nodeA)) {
  718. visitedNodes.add(nodeA);
  719. refreshNode(nodeA);
  720. }
  721. if (nodeB && !visitedNodes.has(nodeB)) {
  722. visitedNodes.add(nodeB);
  723. refreshNode(nodeB);
  724. }
  725. });
  726. // then refresh the links to display the right color between ports
  727. links.forEach((link) => {
  728. if (!visitedLinks.has(link)) {
  729. visitedLinks.add(link);
  730. link.update();
  731. }
  732. });
  733. };
  734. const visitedNodes = new Set<GraphNode>([nodeA]);
  735. const visitedLinks = new Set<NodeLink>([nodeB.links[nodeB.links.length - 1]]);
  736. refreshNode(nodeB);
  737. } else {
  738. nodeB.refresh();
  739. }
  740. linksToNotifyForDispose?.forEach((link) => {
  741. link.onDisposedObservable.notifyObservers(link);
  742. link.onDisposedObservable.clear();
  743. });
  744. this.props.globalState.onRebuildRequiredObservable.notifyObservers();
  745. }
  746. processEditorData(editorData: IEditorData) {
  747. const frames = this._frames.splice(0);
  748. for (var frame of frames) {
  749. frame.dispose();
  750. }
  751. this._frames = [];
  752. this.x = editorData.x || 0;
  753. this.y = editorData.y || 0;
  754. this.zoom = editorData.zoom || 1;
  755. // Frames
  756. if (editorData.frames) {
  757. for (var frameData of editorData.frames) {
  758. var frame = GraphFrame.Parse(frameData, this, editorData.map);
  759. this._frames.push(frame);
  760. }
  761. }
  762. }
  763. addFrame(frameData: IFrameData) {
  764. const frame = GraphFrame.Parse(frameData, this, this.props.globalState.nodeMaterial.editorData.map);
  765. this._frames.push(frame);
  766. this.globalState.onSelectionChangedObservable.notifyObservers(frame);
  767. }
  768. createGUICanvas()
  769. {
  770. // Get the canvas element from the DOM.
  771. const canvas = document.getElementById("graph-canvas") as HTMLCanvasElement;
  772. // Associate a Babylon Engine to it.
  773. const engine = new BABYLON.Engine(canvas);
  774. // Create our first scene.
  775. var scene = new BABYLON.Scene(engine);
  776. // This creates and positions a free camera (non-mesh)
  777. var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
  778. // This targets the camera to scene origin
  779. camera.setTarget(BABYLON.Vector3.Zero());
  780. // This attaches the camera to the canvas
  781. camera.attachControl(true);
  782. // GUI
  783. this._advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");
  784. engine.runRenderLoop(() => {this.updateGUIs(); scene.render()});
  785. }
  786. public addNewButton()
  787. {
  788. if(!this._advancedTexture)
  789. {
  790. this.createGUICanvas();
  791. }
  792. var button1 = BABYLON.GUI.Button.CreateSimpleButton("but1", "Click Me");
  793. button1.width = "150px"
  794. button1.height = "40px";
  795. button1.color = "white";
  796. button1.cornerRadius = 20;
  797. button1.background = "green";
  798. button1.onPointerUpObservable.add(function() {
  799. });
  800. this._guis.push(button1);
  801. this._advancedTexture.addControl(button1);
  802. }
  803. private _guis: BABYLON.GUI.Container[] = [];
  804. private _advancedTexture: BABYLON.GUI.AdvancedDynamicTexture;
  805. updateGUIs()
  806. {
  807. this._guis.forEach(element => {
  808. element.paddingLeft == 1;
  809. //should create a new component type that has the contrainer ias a property.
  810. //with then have the update information.
  811. });
  812. }
  813. render() {
  814. //var canv = new HTMLCanvasElement;
  815. var canv = <canvas id="graph-canvas"
  816. onWheel={evt => this.onWheel(evt)}
  817. onPointerMove={evt => this.onMove(evt)}
  818. onPointerDown={evt => this.onDown(evt)}
  819. onPointerUp={evt => this.onUp(evt)}
  820. >
  821. <div id="graph-container">
  822. <div id="graph-canvas-container">
  823. </div>
  824. <div id="frame-container">
  825. </div>
  826. <svg id="graph-svg-container">
  827. </svg>
  828. <div id="selection-container">
  829. </div>
  830. </div>
  831. </canvas>
  832. return (
  833. canv
  834. );
  835. }
  836. }