graphCanvas.tsx 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  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 } from '../nodeLocationInfo';
  17. require("./graphCanvas.scss");
  18. export interface IGraphCanvasComponentProps {
  19. globalState: GlobalState
  20. }
  21. export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentProps> {
  22. private readonly MinZoom = 0.1;
  23. private readonly MaxZoom = 4;
  24. private _hostCanvas: HTMLDivElement;
  25. private _graphCanvas: HTMLDivElement;
  26. private _selectionContainer: HTMLDivElement;
  27. private _frameContainer: HTMLDivElement;
  28. private _svgCanvas: HTMLElement;
  29. private _rootContainer: HTMLDivElement;
  30. private _nodes: GraphNode[] = [];
  31. private _links: NodeLink[] = [];
  32. private _mouseStartPointX: Nullable<number> = null;
  33. private _mouseStartPointY: Nullable<number> = null
  34. private _dropPointX = 0;
  35. private _dropPointY = 0;
  36. private _selectionStartX = 0;
  37. private _selectionStartY = 0;
  38. private _x = 0;
  39. private _y = 0;
  40. private _zoom = 1;
  41. private _selectedNodes: GraphNode[] = [];
  42. private _selectedLink: Nullable<NodeLink> = null;
  43. private _candidateLink: Nullable<NodeLink> = null;
  44. private _candidatePort: Nullable<NodePort> = null;
  45. private _gridSize = 20;
  46. private _selectionBox: Nullable<HTMLDivElement> = null;
  47. private _selectedFrame: Nullable<GraphFrame> = null;
  48. private _frameCandidate: Nullable<HTMLDivElement> = null;
  49. private _frames: GraphFrame[] = [];
  50. private _altKeyIsPressed = false;
  51. private _ctrlKeyIsPressed = false;
  52. private _oldY = -1;
  53. public _frameIsMoving = false;
  54. public _isLoading = false;
  55. public get gridSize() {
  56. return this._gridSize;
  57. }
  58. public set gridSize(value: number) {
  59. this._gridSize = value;
  60. this.updateTransform();
  61. }
  62. public get globalState(){
  63. return this.props.globalState;
  64. }
  65. public get nodes() {
  66. return this._nodes;
  67. }
  68. public get links() {
  69. return this._links;
  70. }
  71. public get frames() {
  72. return this._frames;
  73. }
  74. public get zoom() {
  75. return this._zoom;
  76. }
  77. public set zoom(value: number) {
  78. if (this._zoom === value) {
  79. return;
  80. }
  81. this._zoom = value;
  82. this.updateTransform();
  83. }
  84. public get x() {
  85. return this._x;
  86. }
  87. public set x(value: number) {
  88. this._x = value;
  89. this.updateTransform();
  90. }
  91. public get y() {
  92. return this._y;
  93. }
  94. public set y(value: number) {
  95. this._y = value;
  96. this.updateTransform();
  97. }
  98. public get selectedNodes() {
  99. return this._selectedNodes;
  100. }
  101. public get selectedLink() {
  102. return this._selectedLink;
  103. }
  104. public get selectedFrame() {
  105. return this._selectedFrame;
  106. }
  107. public get canvasContainer() {
  108. return this._graphCanvas;
  109. }
  110. public get hostCanvas() {
  111. return this._hostCanvas;
  112. }
  113. public get svgCanvas() {
  114. return this._svgCanvas;
  115. }
  116. public get selectionContainer() {
  117. return this._selectionContainer;
  118. }
  119. public get frameContainer() {
  120. return this._frameContainer;
  121. }
  122. constructor(props: IGraphCanvasComponentProps) {
  123. super(props);
  124. props.globalState.onSelectionChangedObservable.add(selection => {
  125. if (!selection) {
  126. this._selectedNodes = [];
  127. this._selectedLink = null;
  128. this._selectedFrame = null;
  129. } else {
  130. if (selection instanceof NodeLink) {
  131. this._selectedNodes = [];
  132. this._selectedFrame = null;
  133. this._selectedLink = selection;
  134. } else if (selection instanceof GraphFrame) {
  135. this._selectedNodes = [];
  136. this._selectedFrame = selection;
  137. this._selectedLink = null;
  138. } else{
  139. if (this._ctrlKeyIsPressed) {
  140. if (this._selectedNodes.indexOf(selection) === -1) {
  141. this._selectedNodes.push(selection);
  142. }
  143. } else {
  144. this._selectedNodes = [selection];
  145. }
  146. }
  147. }
  148. });
  149. props.globalState.onCandidatePortSelected.add(port => {
  150. this._candidatePort = port;
  151. });
  152. props.globalState.onGridSizeChanged.add(() => {
  153. this.gridSize = DataStorage.ReadNumber("GridSize", 20);
  154. });
  155. this.props.globalState.hostDocument!.addEventListener("keyup", () => this.onKeyUp(), false);
  156. this.props.globalState.hostDocument!.addEventListener("keydown", evt => {
  157. this._altKeyIsPressed = evt.altKey;
  158. this._ctrlKeyIsPressed = evt.ctrlKey;
  159. }, false);
  160. this.props.globalState.hostDocument!.defaultView!.addEventListener("blur", () => {
  161. this._altKeyIsPressed = false;
  162. this._ctrlKeyIsPressed = false;
  163. }, false);
  164. // Store additional data to serialization object
  165. this.props.globalState.storeEditorData = (editorData) => {
  166. editorData.zoom = this.zoom;
  167. editorData.x = this.x;
  168. editorData.y = this.y;
  169. editorData.frames = [];
  170. for (var frame of this._frames) {
  171. editorData.frames.push(frame.serialize());
  172. }
  173. }
  174. }
  175. public getGridPosition(position: number, useCeil = false) {
  176. let gridSize = this.gridSize;
  177. if (gridSize === 0) {
  178. return position;
  179. }
  180. if (useCeil) {
  181. return gridSize * Math.ceil(position / gridSize);
  182. }
  183. return gridSize * Math.floor(position / gridSize);
  184. }
  185. public getGridPositionCeil(position: number) {
  186. let gridSize = this.gridSize;
  187. if (gridSize === 0) {
  188. return position;
  189. }
  190. return gridSize * Math.ceil(position / gridSize);
  191. }
  192. updateTransform() {
  193. this._rootContainer.style.transform = `translate(${this._x}px, ${this._y}px) scale(${this._zoom})`;
  194. if (DataStorage.ReadBoolean("ShowGrid", true)) {
  195. this._hostCanvas.style.backgroundSize = `${this._gridSize * this._zoom}px ${this._gridSize * this._zoom}px`;
  196. this._hostCanvas.style.backgroundPosition = `${this._x}px ${this._y}px`;
  197. } else {
  198. this._hostCanvas.style.backgroundSize = `0`;
  199. }
  200. }
  201. onKeyUp() {
  202. this._altKeyIsPressed = false;
  203. this._ctrlKeyIsPressed = false;
  204. this._oldY = -1;
  205. }
  206. findNodeFromBlock(block: NodeMaterialBlock) {
  207. return this.nodes.filter(n => n.block === block)[0];
  208. }
  209. reset() {
  210. for (var node of this._nodes) {
  211. node.dispose();
  212. }
  213. const frames = this._frames.splice(0);
  214. for (var frame of frames) {
  215. frame.dispose();
  216. }
  217. this._nodes = [];
  218. this._frames = [];
  219. this._links = [];
  220. this._graphCanvas.innerHTML = "";
  221. this._svgCanvas.innerHTML = "";
  222. }
  223. connectPorts(pointA: NodeMaterialConnectionPoint, pointB: NodeMaterialConnectionPoint) {
  224. var blockA = pointA.ownerBlock;
  225. var blockB = pointB.ownerBlock;
  226. var nodeA = this.findNodeFromBlock(blockA);
  227. var nodeB = this.findNodeFromBlock(blockB);
  228. if (!nodeA || !nodeB) {
  229. return;
  230. }
  231. var portA = nodeA.getPortForConnectionPoint(pointA);
  232. var portB = nodeB.getPortForConnectionPoint(pointB);
  233. if (!portA || !portB) {
  234. return;
  235. }
  236. for (var currentLink of this._links) {
  237. if (currentLink.portA === portA && currentLink.portB === portB) {
  238. return;
  239. }
  240. if (currentLink.portA === portB && currentLink.portB === portA) {
  241. return;
  242. }
  243. }
  244. const link = new NodeLink(this, portA, nodeA, portB, nodeB);
  245. this._links.push(link);
  246. nodeA.links.push(link);
  247. nodeB.links.push(link);
  248. }
  249. removeLink(link: NodeLink) {
  250. let index = this._links.indexOf(link);
  251. if (index > -1) {
  252. this._links.splice(index, 1);
  253. }
  254. link.dispose();
  255. }
  256. appendBlock(block: NodeMaterialBlock) {
  257. let newNode = new GraphNode(block, this.props.globalState);
  258. newNode.appendVisual(this._graphCanvas, this);
  259. this._nodes.push(newNode);
  260. return newNode;
  261. }
  262. distributeGraph() {
  263. this.x = 0;
  264. this.y = 0;
  265. this.zoom = 1;
  266. let graph = new dagre.graphlib.Graph();
  267. graph.setGraph({});
  268. graph.setDefaultEdgeLabel(() => ({}));
  269. graph.graph().rankdir = "LR";
  270. // Build dagre graph
  271. this._nodes.forEach(node => {
  272. if (this._frames.some(f => f.nodes.indexOf(node) !== -1)) {
  273. return;
  274. }
  275. graph.setNode(node.id.toString(), {
  276. id: node.id,
  277. type: "node",
  278. width: node.width,
  279. height: node.height
  280. });
  281. });
  282. this._frames.forEach(frame => {
  283. graph.setNode(frame.id.toString(), {
  284. id: frame.id,
  285. type: "frame",
  286. width: frame.element.clientWidth,
  287. height: frame.element.clientHeight
  288. });
  289. })
  290. this._nodes.forEach(node => {
  291. node.block.outputs.forEach(output => {
  292. if (!output.hasEndpoints) {
  293. return;
  294. }
  295. output.endpoints.forEach(endpoint => {
  296. let sourceFrames = this._frames.filter(f => f.nodes.indexOf(node) !== -1);
  297. let targetFrames = this._frames.filter(f => f.nodes.some(n => n.block === endpoint.ownerBlock));
  298. let sourceId = sourceFrames.length > 0 ? sourceFrames[0].id : node.id;
  299. let targetId = targetFrames.length > 0 ? targetFrames[0].id : endpoint.ownerBlock.uniqueId;
  300. graph.setEdge(sourceId.toString(), targetId.toString());
  301. });
  302. });
  303. });
  304. // Distribute
  305. dagre.layout(graph);
  306. // Update graph
  307. let dagreNodes = graph.nodes().map(node => graph.node(node));
  308. dagreNodes.forEach(dagreNode => {
  309. if (dagreNode.type === "node") {
  310. for (var node of this._nodes) {
  311. if (node.id === dagreNode.id) {
  312. node.x = dagreNode.x - dagreNode.width / 2;
  313. node.y = dagreNode.y - dagreNode.height / 2;
  314. node.cleanAccumulation();
  315. return;
  316. }
  317. }
  318. return;
  319. }
  320. for (var frame of this._frames) {
  321. if (frame.id === dagreNode.id) {
  322. this._frameIsMoving = true;
  323. frame.move(dagreNode.x - dagreNode.width / 2, dagreNode.y - dagreNode.height / 2, false);
  324. frame.cleanAccumulation();
  325. this._frameIsMoving = false;
  326. return;
  327. }
  328. }
  329. });
  330. }
  331. componentDidMount() {
  332. this._hostCanvas = this.props.globalState.hostDocument.getElementById("graph-canvas") as HTMLDivElement;
  333. this._rootContainer = this.props.globalState.hostDocument.getElementById("graph-container") as HTMLDivElement;
  334. this._graphCanvas = this.props.globalState.hostDocument.getElementById("graph-canvas-container") as HTMLDivElement;
  335. this._svgCanvas = this.props.globalState.hostDocument.getElementById("graph-svg-container") as HTMLElement;
  336. this._selectionContainer = this.props.globalState.hostDocument.getElementById("selection-container") as HTMLDivElement;
  337. this._frameContainer = this.props.globalState.hostDocument.getElementById("frame-container") as HTMLDivElement;
  338. this.gridSize = DataStorage.ReadNumber("GridSize", 20);
  339. this.updateTransform();
  340. }
  341. onMove(evt: React.PointerEvent) {
  342. // Selection box
  343. if (this._selectionBox) {
  344. const rootRect = this.canvasContainer.getBoundingClientRect();
  345. const localX = evt.pageX - rootRect.left;
  346. const localY = evt.pageY - rootRect.top;
  347. if (localX > this._selectionStartX) {
  348. this._selectionBox.style.left = `${this._selectionStartX / this.zoom}px`;
  349. this._selectionBox.style.width = `${(localX - this._selectionStartX) / this.zoom}px`;
  350. } else {
  351. this._selectionBox.style.left = `${localX / this.zoom}px`;
  352. this._selectionBox.style.width = `${(this._selectionStartX - localX) / this.zoom}px`;
  353. }
  354. if (localY > this._selectionStartY) {
  355. this._selectionBox.style.top = `${this._selectionStartY / this.zoom}px`;
  356. this._selectionBox.style.height = `${(localY - this._selectionStartY) / this.zoom}px`;
  357. } else {
  358. this._selectionBox.style.top = `${localY / this.zoom}px`;
  359. this._selectionBox.style.height = `${(this._selectionStartY - localY) / this.zoom}px`;
  360. }
  361. this.props.globalState.onSelectionBoxMoved.notifyObservers(this._selectionBox.getBoundingClientRect());
  362. return;
  363. }
  364. // Candidate frame box
  365. if (this._frameCandidate) {
  366. const rootRect = this.canvasContainer.getBoundingClientRect();
  367. const localX = evt.pageX - rootRect.left;
  368. const localY = evt.pageY - rootRect.top;
  369. if (localX > this._selectionStartX) {
  370. this._frameCandidate.style.left = `${this._selectionStartX / this.zoom}px`;
  371. this._frameCandidate.style.width = `${(localX - this._selectionStartX) / this.zoom}px`;
  372. } else {
  373. this._frameCandidate.style.left = `${localX / this.zoom}px`;
  374. this._frameCandidate.style.width = `${(this._selectionStartX - localX) / this.zoom}px`;
  375. }
  376. if (localY > this._selectionStartY) {
  377. this._frameCandidate.style.top = `${this._selectionStartY / this.zoom}px`;
  378. this._frameCandidate.style.height = `${(localY - this._selectionStartY) / this.zoom}px`;
  379. } else {
  380. this._frameCandidate.style.top = `${localY / this.zoom}px`;
  381. this._frameCandidate.style.height = `${(this._selectionStartY - localY) / this.zoom}px`;
  382. }
  383. return;
  384. }
  385. // Candidate link
  386. if (this._candidateLink) {
  387. const rootRect = this.canvasContainer.getBoundingClientRect();
  388. this._candidatePort = null;
  389. this.props.globalState.onCandidateLinkMoved.notifyObservers(new Vector2(evt.pageX, evt.pageY));
  390. this._dropPointX = (evt.pageX - rootRect.left) / this.zoom;
  391. this._dropPointY = (evt.pageY - rootRect.top) / this.zoom;
  392. this._candidateLink.update(this._dropPointX, this._dropPointY, true);
  393. return;
  394. }
  395. // Zoom with mouse + alt
  396. if (this._altKeyIsPressed && evt.buttons === 1) {
  397. if (this._oldY < 0) {
  398. this._oldY = evt.pageY;
  399. }
  400. let zoomDelta = (evt.pageY - this._oldY) / 10;
  401. if (Math.abs(zoomDelta) > 5) {
  402. const oldZoom = this.zoom;
  403. this.zoom = Math.max(Math.min(this.MaxZoom, this.zoom + zoomDelta / 100), this.MinZoom);
  404. const boundingRect = evt.currentTarget.getBoundingClientRect();
  405. const clientWidth = boundingRect.width;
  406. const widthDiff = clientWidth * this.zoom - clientWidth * oldZoom;
  407. const clientX = evt.clientX - boundingRect.left;
  408. const xFactor = (clientX - this.x) / oldZoom / clientWidth;
  409. this.x = this.x - widthDiff * xFactor;
  410. this._oldY = evt.pageY;
  411. }
  412. return;
  413. }
  414. // Move canvas
  415. this._rootContainer.style.cursor = "move";
  416. if (this._mouseStartPointX === null || this._mouseStartPointY === null) {
  417. return;
  418. }
  419. this.x += evt.clientX - this._mouseStartPointX;
  420. this.y += evt.clientY - this._mouseStartPointY;
  421. this._mouseStartPointX = evt.clientX;
  422. this._mouseStartPointY = evt.clientY;
  423. }
  424. onDown(evt: React.PointerEvent<HTMLElement>) {
  425. this._rootContainer.setPointerCapture(evt.pointerId);
  426. // Selection?
  427. if (evt.currentTarget === this._hostCanvas && evt.ctrlKey) {
  428. this._selectionBox = this.props.globalState.hostDocument.createElement("div");
  429. this._selectionBox.classList.add("selection-box");
  430. this._selectionContainer.appendChild(this._selectionBox);
  431. const rootRect = this.canvasContainer.getBoundingClientRect();
  432. this._selectionStartX = (evt.pageX - rootRect.left);
  433. this._selectionStartY = (evt.pageY - rootRect.top);
  434. this._selectionBox.style.left = `${this._selectionStartX / this.zoom}px`;
  435. this._selectionBox.style.top = `${this._selectionStartY / this.zoom}px`;
  436. this._selectionBox.style.width = "0px";
  437. this._selectionBox.style.height = "0px";
  438. return;
  439. }
  440. // Frame?
  441. if (evt.currentTarget === this._hostCanvas && evt.shiftKey) {
  442. this._frameCandidate = this.props.globalState.hostDocument.createElement("div");
  443. this._frameCandidate.classList.add("frame-box");
  444. this._frameContainer.appendChild(this._frameCandidate);
  445. const rootRect = this.canvasContainer.getBoundingClientRect();
  446. this._selectionStartX = (evt.pageX - rootRect.left);
  447. this._selectionStartY = (evt.pageY - rootRect.top);
  448. this._frameCandidate.style.left = `${this._selectionStartX / this.zoom}px`;
  449. this._frameCandidate.style.top = `${this._selectionStartY / this.zoom}px`;
  450. this._frameCandidate.style.width = "0px";
  451. this._frameCandidate.style.height = "0px";
  452. return;
  453. }
  454. // Port dragging
  455. if (evt.nativeEvent.srcElement && (evt.nativeEvent.srcElement as HTMLElement).nodeName === "IMG") {
  456. if (!this._candidateLink) {
  457. let portElement = ((evt.nativeEvent.srcElement as HTMLElement).parentElement as any).port as NodePort;
  458. this._candidateLink = new NodeLink(this, portElement, portElement.node);
  459. }
  460. return;
  461. }
  462. this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
  463. this._mouseStartPointX = evt.clientX;
  464. this._mouseStartPointY = evt.clientY;
  465. }
  466. onUp(evt: React.PointerEvent) {
  467. this._mouseStartPointX = null;
  468. this._mouseStartPointY = null;
  469. this._rootContainer.releasePointerCapture(evt.pointerId);
  470. this._oldY = -1;
  471. if (this._candidateLink) {
  472. this.processCandidatePort();
  473. this.props.globalState.onCandidateLinkMoved.notifyObservers(null);
  474. this._candidateLink.dispose();
  475. this._candidateLink = null;
  476. this._candidatePort = null;
  477. }
  478. if (this._selectionBox) {
  479. this._selectionBox.parentElement!.removeChild(this._selectionBox);
  480. this._selectionBox = null;
  481. }
  482. if (this._frameCandidate) {
  483. let newFrame = new GraphFrame(this._frameCandidate, this);
  484. this._frames.push(newFrame);
  485. this._frameCandidate.parentElement!.removeChild(this._frameCandidate);
  486. this._frameCandidate = null;
  487. this.props.globalState.onSelectionChangedObservable.notifyObservers(newFrame);
  488. }
  489. }
  490. onWheel(evt: React.WheelEvent) {
  491. let delta = evt.deltaY < 0 ? 0.1 : -0.1;
  492. let oldZoom = this.zoom;
  493. this.zoom = Math.min(Math.max(this.MinZoom, this.zoom + delta * this.zoom), this.MaxZoom);
  494. const boundingRect = evt.currentTarget.getBoundingClientRect();
  495. const clientWidth = boundingRect.width;
  496. const clientHeight = boundingRect.height;
  497. const widthDiff = clientWidth * this.zoom - clientWidth * oldZoom;
  498. const heightDiff = clientHeight * this.zoom - clientHeight * oldZoom;
  499. const clientX = evt.clientX - boundingRect.left;
  500. const clientY = evt.clientY - boundingRect.top;
  501. const xFactor = (clientX - this.x) / oldZoom / clientWidth;
  502. const yFactor = (clientY - this.y) / oldZoom / clientHeight;
  503. this.x = this.x - widthDiff * xFactor;
  504. this.y = this.y - heightDiff * yFactor;
  505. evt.stopPropagation();
  506. }
  507. zoomToFit() {
  508. // Get negative offset
  509. let minX = 0;
  510. let minY = 0;
  511. this._nodes.forEach(node => {
  512. if (this._frames.some(f => f.nodes.indexOf(node) !== -1)) {
  513. return;
  514. }
  515. if (node.x < minX) {
  516. minX = node.x;
  517. }
  518. if (node.y < minY) {
  519. minY = node.y;
  520. }
  521. });
  522. this._frames.forEach(frame => {
  523. if (frame.x < minX) {
  524. minX = frame.x;
  525. }
  526. if (frame.y < minY) {
  527. minY = frame.y;
  528. }
  529. });
  530. // Restore to 0
  531. this._frames.forEach(frame => {
  532. frame.x += -minX;
  533. frame.y += -minY;
  534. frame.cleanAccumulation();
  535. });
  536. this._nodes.forEach(node => {
  537. node.x += -minX;
  538. node.y += -minY;
  539. node.cleanAccumulation();
  540. });
  541. // Get correct zoom
  542. const xFactor = this._rootContainer.clientWidth / this._rootContainer.scrollWidth;
  543. const yFactor = this._rootContainer.clientHeight / this._rootContainer.scrollHeight;
  544. const zoomFactor = xFactor < yFactor ? xFactor : yFactor;
  545. this.zoom = zoomFactor;
  546. this.x = 0;
  547. this.y = 0;
  548. }
  549. processCandidatePort() {
  550. let pointB = this._candidateLink!.portA.connectionPoint;
  551. let nodeB = this._candidateLink!.portA.node;
  552. let pointA: NodeMaterialConnectionPoint;
  553. let nodeA: GraphNode;
  554. if (this._candidatePort) {
  555. pointA = this._candidatePort.connectionPoint;
  556. nodeA = this._candidatePort.node;
  557. } else {
  558. if (pointB.direction === NodeMaterialConnectionPointDirection.Output) {
  559. return;
  560. }
  561. // No destination so let's spin a new input block
  562. let inputBlock = new InputBlock(NodeMaterialBlockConnectionPointTypes[this._candidateLink!.portA.connectionPoint.type], undefined, this._candidateLink!.portA.connectionPoint.type);
  563. this.props.globalState.nodeMaterial.attachedBlocks.push(inputBlock);
  564. pointA = inputBlock.output;
  565. nodeA = this.appendBlock(inputBlock);
  566. nodeA.x = this._dropPointX - 200;
  567. nodeA.y = this._dropPointY - 50;
  568. }
  569. if (pointA.direction === NodeMaterialConnectionPointDirection.Input) {
  570. let temp = pointB;
  571. pointB = pointA;
  572. pointA = temp;
  573. let tempNode = nodeA;
  574. nodeA = nodeB;
  575. nodeB = tempNode;
  576. }
  577. if (pointB.connectedPoint === pointA) {
  578. return;
  579. }
  580. if (pointB === pointA) {
  581. return;
  582. }
  583. if (pointB.direction === pointA.direction) {
  584. return;
  585. }
  586. if (pointB.ownerBlock === pointA.ownerBlock) {
  587. return;
  588. }
  589. // Check compatibility
  590. let isFragmentOutput = pointB.ownerBlock.getClassName() === "FragmentOutputBlock";
  591. let compatibilityState = pointA.checkCompatibilityState(pointB);
  592. if (compatibilityState === NodeMaterialConnectionPointCompatibilityStates.Compatible) {
  593. if (isFragmentOutput) {
  594. let fragmentBlock = pointB.ownerBlock as FragmentOutputBlock;
  595. if (pointB.name === "rgb" && fragmentBlock.rgba.isConnected) {
  596. nodeB.getLinksForConnectionPoint(fragmentBlock.rgba)[0].dispose();
  597. } else if (pointB.name === "rgba" && fragmentBlock.rgb.isConnected) {
  598. nodeB.getLinksForConnectionPoint(fragmentBlock.rgb)[0].dispose();
  599. }
  600. }
  601. } else {
  602. let message = "";
  603. switch (compatibilityState) {
  604. case NodeMaterialConnectionPointCompatibilityStates.TypeIncompatible:
  605. message = "Cannot connect two different connection types";
  606. break;
  607. case NodeMaterialConnectionPointCompatibilityStates.TargetIncompatible:
  608. message = "Source block can only work in fragment shader whereas destination block is currently aimed for the vertex shader";
  609. break;
  610. }
  611. this.props.globalState.onErrorMessageDialogRequiredObservable.notifyObservers(message);
  612. return;
  613. }
  614. if (pointB.isConnected) {
  615. let links = nodeB.getLinksForConnectionPoint(pointB);
  616. links.forEach(link => {
  617. link.dispose();
  618. });
  619. }
  620. if (pointB.ownerBlock.inputsAreExclusive) { // Disconnect all inputs if block has exclusive inputs
  621. pointB.ownerBlock.inputs.forEach(i => {
  622. let links = nodeB.getLinksForConnectionPoint(i);
  623. links.forEach(link => {
  624. link.dispose();
  625. });
  626. })
  627. }
  628. pointA.connectTo(pointB);
  629. this.connectPorts(pointA, pointB);
  630. nodeB.refresh();
  631. this.props.globalState.onRebuildRequiredObservable.notifyObservers();
  632. }
  633. processEditorData(editorData: IEditorData) {
  634. const frames = this._frames.splice(0);
  635. for (var frame of frames) {
  636. frame.dispose();
  637. }
  638. this._frames = [];
  639. this.x = editorData.x || 0;
  640. this.y = editorData.y || 0;
  641. this.zoom = editorData.zoom || 1;
  642. // Frames
  643. if (editorData.frames) {
  644. for (var frameData of editorData.frames) {
  645. var frame = GraphFrame.Parse(frameData, this, editorData.map);
  646. this._frames.push(frame);
  647. }
  648. }
  649. }
  650. render() {
  651. return (
  652. <div id="graph-canvas"
  653. onWheel={evt => this.onWheel(evt)}
  654. onPointerMove={evt => this.onMove(evt)}
  655. onPointerDown={evt => this.onDown(evt)}
  656. onPointerUp={evt => this.onUp(evt)}
  657. >
  658. <div id="graph-container">
  659. <div id="graph-canvas-container">
  660. </div>
  661. <div id="frame-container">
  662. </div>
  663. <svg id="graph-svg-container">
  664. </svg>
  665. <div id="selection-container">
  666. </div>
  667. </div>
  668. </div>
  669. );
  670. }
  671. }