graphCanvas.tsx 29 KB

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