Home Reference Source

cables_dev/cables_ui/src/ui/elements/canvasoverlays/transformgizmo.js

  1. import { vec3, mat4, vec2 } from "gl-matrix";
  2. import { Port } from "cables";
  3. import { CglContext } from "cables-corelibs/cgl/cgl_state.js";
  4. import { gui } from "../../gui.js";
  5. import undo from "../../utils/undo.js";
  6. /**
  7. * @typedef GizmoParams
  8. * @property {Port} posX
  9. * @property {Port} posY
  10. * @property {Port} posZ
  11. */
  12. export default class Gizmo
  13. {
  14. #params = null;
  15. #eleCenter = null;
  16. #eleX = null;
  17. #eleY = null;
  18. #eleZ = null;
  19. #eleXZ = null;
  20. #eleXY = null;
  21. #eleYZ = null;
  22. lineX = null;
  23. lineY = null;
  24. lineZ = null;
  25. #origValue = 0;
  26. #dragSum = 0;
  27. #dragSumY = 0;
  28. #dir = 1;
  29. hidden = true;
  30. static EVENT_GUI_GIZMO_MOVE = "gizmoMove";
  31. /**
  32. * @param {CglContext} cgl
  33. */
  34. constructor(cgl)
  35. {
  36. this._cgl = cgl;
  37. }
  38. dispose()
  39. {
  40. this.hidden = true;
  41. if (this.#eleCenter) this.#eleCenter.remove();
  42. if (this.#eleX) this.#eleX.remove();
  43. if (this.#eleY) this.#eleY.remove();
  44. if (this.#eleZ) this.#eleZ.remove();
  45. if (this.#eleXZ) this.#eleXZ.remove();
  46. if (this.#eleXY) this.#eleXY.remove();
  47. if (this.#eleYZ) this.#eleYZ.remove();
  48. if (this.lineX) this.lineX.remove();
  49. if (this.lineY) this.lineY.remove();
  50. if (this.lineZ) this.lineZ.remove();
  51. }
  52. /**
  53. * @param {number} x2
  54. * @param {number} y2
  55. */
  56. getDir(x2, y2)
  57. {
  58. const xd = this.#params.x - x2;
  59. const yd = this.#params.y - y2;
  60. const dist = (xd + yd) / 2;
  61. if (dist < 0) return 1;
  62. return -1;
  63. }
  64. /**
  65. * @param {GizmoParams} params
  66. */
  67. set(params)
  68. {
  69. if (!params) return this.setParams(params);
  70. const cgl = this._cgl;
  71. if (!cgl) return;
  72. cgl.pushModelMatrix();
  73. function toScreen(trans)
  74. {
  75. const vp = cgl.getViewPort();
  76. let x = vp[2] - (vp[2] * 0.5 - (trans[0] * vp[2] * 0.5) / trans[2]);
  77. let y = vp[3] - (vp[3] * 0.5 + (trans[1] * vp[3] * 0.5) / trans[2]);
  78. if (cgl.canvas.styleMarginLeft) x += cgl.canvas.styleMarginLeft;
  79. if (cgl.canvas.styleMarginTop) y += cgl.canvas.styleMarginTop;
  80. x /= cgl.pixelDensity;
  81. y /= cgl.pixelDensity;
  82. return { "x": x, "y": y };
  83. }
  84. function distance(x1, y1, x2, y2)
  85. {
  86. const xd = x2 - x1;
  87. const yd = y2 - y1;
  88. return Math.sqrt(xd * xd + yd * yd);
  89. }
  90. const m = mat4.create();
  91. const pos = vec3.create();
  92. const trans = vec3.create();
  93. const transX = vec3.create();
  94. const transY = vec3.create();
  95. const transZ = vec3.create();
  96. const identVec = vec3.create();
  97. mat4.translate(cgl.mvMatrix, cgl.mvMatrix, [params.posX.get(), params.posY.get(), params.posZ.get()]);
  98. mat4.multiply(m, cgl.vMatrix, cgl.mvMatrix);
  99. vec3.transformMat4(pos, identVec, m);
  100. let tempParams = {};
  101. if (pos[2] > 0)
  102. {
  103. tempParams = null;
  104. }
  105. else
  106. {
  107. vec3.transformMat4(trans, pos, cgl.pMatrix);
  108. const zero = toScreen(trans);
  109. // normalize distance to gizmo handles
  110. vec3.transformMat4(pos, [1, 0, 0], m);
  111. vec3.transformMat4(transX, pos, cgl.pMatrix);
  112. let screenDist = toScreen(transX);
  113. const d1 = distance(zero.x, zero.y, screenDist.x, screenDist.y);
  114. vec3.transformMat4(pos, [0, 1, 0], m);
  115. vec3.transformMat4(transX, pos, cgl.pMatrix);
  116. screenDist = toScreen(transX);
  117. const d2 = distance(zero.x, zero.y, screenDist.x, screenDist.y);
  118. vec3.transformMat4(pos, [0, 0, 1], m);
  119. vec3.transformMat4(transX, pos, cgl.pMatrix);
  120. screenDist = toScreen(transX);
  121. const d3 = distance(zero.x, zero.y, screenDist.x, screenDist.y);
  122. const d = Math.max(d3, Math.max(d1, d2));
  123. const w = (1 / (d + 0.00000001)) * 50;
  124. this._multi = w;
  125. vec3.transformMat4(pos, [w, 0, 0], m);
  126. vec3.transformMat4(transX, pos, cgl.pMatrix);
  127. vec3.transformMat4(pos, [0, w, 0], m);
  128. vec3.transformMat4(transY, pos, cgl.pMatrix);
  129. vec3.transformMat4(pos, [0, 0, w], m);
  130. vec3.transformMat4(transZ, pos, cgl.pMatrix);
  131. const screenX = toScreen(transX);
  132. const screenY = toScreen(transY);
  133. const screenZ = toScreen(transZ);
  134. // console.log(screenZ);
  135. tempParams.x = zero.x;
  136. tempParams.y = zero.y;
  137. tempParams.xx = screenX.x;
  138. tempParams.xy = screenX.y;
  139. tempParams.yx = screenY.x;
  140. tempParams.yy = screenY.y;
  141. tempParams.zx = screenZ.x;
  142. tempParams.zy = screenZ.y;
  143. tempParams.coord = trans;
  144. tempParams.coordX = transX;
  145. tempParams.coordY = transY;
  146. tempParams.coordZ = transZ;
  147. tempParams.posX = params.posX;
  148. tempParams.posY = params.posY;
  149. tempParams.posZ = params.posZ;
  150. tempParams.dist = w;
  151. }
  152. cgl.popModelMatrix();
  153. this.setParams(tempParams);
  154. }
  155. setParams(params)
  156. {
  157. this.#params = params;
  158. if (!this._cgl) return;
  159. if (!this.#eleCenter)
  160. {
  161. const container = this._cgl.canvas.parentElement;
  162. if (!container) return;
  163. this.#eleCenter = document.createElement("div");
  164. this.#eleCenter.id = "gizmo";
  165. this.#eleCenter.style.background = "#fff";
  166. this.#eleCenter.style.display = "none";
  167. this.#eleCenter.style.opacity = "0.9";
  168. this.#eleCenter.style.pointerEvents = "none";
  169. // this._eleCenter.style['border-radius']="1130px";
  170. // this._eleCenter.style.transform='scale(2)';
  171. this.#eleCenter.classList.add("gizmo");
  172. container.appendChild(this.#eleCenter);
  173. this.#eleX = document.createElement("div");
  174. this.#eleX.id = "gizmoX";
  175. this.#eleX.style.background = "#f00";
  176. this.#eleX.style.display = "none";
  177. this.#eleX.classList.add("gizmo");
  178. container.appendChild(this.#eleX);
  179. this.#eleXZ = document.createElement("div");
  180. this.#eleXZ.id = "gizmoXZ";
  181. this.#eleXZ.style.background = "#f0f";
  182. this.#eleXZ.style.display = "none";
  183. this.#eleXZ.style.opacity = "0.5";
  184. this.#eleXZ.style.borderRadius = "0";
  185. this.#eleXZ.classList.add("gizmo");
  186. container.appendChild(this.#eleXZ);
  187. this.#eleXY = document.createElement("div");
  188. this.#eleXY.id = "gizmoXY";
  189. this.#eleXY.style.background = "#ff0";
  190. this.#eleXY.style.display = "none";
  191. this.#eleXY.style.opacity = "0.5";
  192. this.#eleXY.style.borderRadius = "0";
  193. this.#eleXY.classList.add("gizmo");
  194. container.appendChild(this.#eleXY);
  195. this.#eleYZ = document.createElement("div");
  196. this.#eleYZ.id = "gizmoYZ";
  197. this.#eleYZ.style.background = "#0ff";
  198. this.#eleYZ.style.display = "none";
  199. this.#eleYZ.style.opacity = "0.5";
  200. this.#eleYZ.style.borderRadius = "0";
  201. this.#eleYZ.classList.add("gizmo");
  202. container.appendChild(this.#eleYZ);
  203. this.#eleY = document.createElement("div");
  204. this.#eleY.id = "gizmoY";
  205. this.#eleY.style.background = "#0f0";
  206. this.#eleY.style.display = "none";
  207. this.#eleY.classList.add("gizmo");
  208. container.appendChild(this.#eleY);
  209. this.#eleZ = document.createElement("div");
  210. this.#eleZ.id = "gizmoZ";
  211. this.#eleZ.style.background = "#00f";
  212. this.#eleZ.style.display = "none";
  213. this.#eleZ.classList.add("gizmo");
  214. container.appendChild(this.#eleZ);
  215. this.lineX = new htmlLine(container, "#f00");
  216. this.lineY = new htmlLine(container, "#0f0");
  217. this.lineZ = new htmlLine(container, "#00f");
  218. this.#eleX.addEventListener(
  219. "pointerdown",
  220. () =>
  221. {
  222. if (!this.#params) return;
  223. this._draggingPort = this.#params.posX;
  224. this._draggingPortY = null;
  225. this.#origValue = this.#params.posX.get();
  226. this.#dragSum = 0;
  227. this.dragger(this.#eleCenter);
  228. this.#dir = this.getDir(this.#params.xx, this.#params.xy);
  229. },
  230. );
  231. this.#eleY.addEventListener(
  232. "pointerdown",
  233. () =>
  234. {
  235. if (!this.#params) return;
  236. this._draggingPort = this.#params.posY;
  237. this._draggingPortY = null;
  238. this.#origValue = this.#params.posY.get();
  239. this.#dragSum = 0;
  240. this.dragger(this.#eleCenter);
  241. this.#dir = this.getDir(this.#params.yx, this.#params.yy);
  242. },
  243. );
  244. this.#eleZ.addEventListener(
  245. "pointerdown",
  246. () =>
  247. {
  248. if (!this.#params) return;
  249. this._draggingPort = this.#params.posZ;
  250. this._draggingPortY = null;
  251. this.#origValue = this.#params.posZ.get();
  252. this.#dragSum = 0;
  253. this.dragger(this.#eleCenter);
  254. this.#dir = this.getDir(this.#params.zx, this.#params.zy);
  255. },
  256. );
  257. this.#eleXZ.addEventListener(
  258. "pointerdown",
  259. () =>
  260. {
  261. if (!this.#params) return;
  262. this._draggingPort = this.#params.posX;
  263. this._draggingPortY = this.#params.posZ;
  264. this.#origValue = this.#params.posX.get();
  265. this._origValueY = this.#params.posZ.get();
  266. this.#dragSum = 0;
  267. this.#dragSumY = 0;
  268. this.dragger(this.#eleCenter);
  269. },
  270. );
  271. this.#eleXY.addEventListener(
  272. "pointerdown",
  273. () =>
  274. {
  275. if (!this.#params) return;
  276. this._draggingPort = this.#params.posX;
  277. this._draggingPortY = this.#params.posY;
  278. this.#origValue = this.#params.posX.get();
  279. this._origValueY = this.#params.posY.get();
  280. this.#dragSum = 0;
  281. this.#dragSumY = 0;
  282. // this.flipX = true;
  283. this.flipY = true;
  284. this.dragger(this.#eleCenter);
  285. },
  286. );
  287. this.#eleYZ.addEventListener(
  288. "pointerdown",
  289. () =>
  290. {
  291. if (!this.#params) return;
  292. this._draggingPort = this.#params.posZ;
  293. this._draggingPortY = this.#params.posY;
  294. this.#origValue = this.#params.posZ.get();
  295. this._origValueY = this.#params.posY.get();
  296. this.#dragSum = 0;
  297. this.#dragSumY = 0;
  298. this.flipY = true;
  299. this.flipX = true;
  300. this.dragger(this.#eleCenter);
  301. },
  302. );
  303. }
  304. if (!params)
  305. {
  306. if (this.hidden) return;
  307. const self = this;
  308. setTimeout(() =>
  309. {
  310. this.hidden = true;
  311. this.#eleCenter.style.display = "none";
  312. this.#eleXZ.style.display = "none";
  313. this.#eleXY.style.display = "none";
  314. this.#eleYZ.style.display = "none";
  315. this.#eleX.style.display = "none";
  316. this.#eleZ.style.display = "none";
  317. this.#eleY.style.display = "none";
  318. this.lineX.hide();
  319. this.lineZ.hide();
  320. this.lineY.hide();
  321. }, 1);
  322. return;
  323. }
  324. this.hidden = false;
  325. this.lineX.show();
  326. this.lineZ.show();
  327. this.lineY.show();
  328. this.#eleCenter.style.display = "block";
  329. this.#eleCenter.style.left = params.x + "px";
  330. this.#eleCenter.style.top = params.y + "px";
  331. this.#eleXZ.style.display = "block";
  332. this.#eleXZ.style.left = (params.xx + params.zx) / 2 + "px";
  333. this.#eleXZ.style.top = (params.xy + params.zy) / 2 + "px";
  334. this.#eleXY.style.display = "block";
  335. this.#eleXY.style.left = (params.xx + params.yx) / 2 + "px";
  336. this.#eleXY.style.top = (params.xy + params.yy) / 2 + "px";
  337. this.#eleYZ.style.display = "block";
  338. this.#eleYZ.style.left = (params.zx + params.yx) / 2 + "px";
  339. this.#eleYZ.style.top = (params.zy + params.yy) / 2 + "px";
  340. this.#eleX.style.display = "block";
  341. this.#eleX.style.left = params.xx + "px";
  342. this.#eleX.style.top = params.xy + "px";
  343. this.#eleY.style.display = "block";
  344. this.#eleY.style.left = params.yx + "px";
  345. this.#eleY.style.top = params.yy + "px";
  346. this.#eleZ.style.display = "block";
  347. this.#eleZ.style.left = params.zx + "px";
  348. this.#eleZ.style.top = params.zy + "px";
  349. this.lineX.set(params.x, params.y, params.xx, params.xy);
  350. this.lineY.set(params.x, params.y, params.yx, params.yy);
  351. this.lineZ.set(params.x, params.y, params.zx, params.zy);
  352. }
  353. dragger(el)
  354. {
  355. let isDown = false;
  356. const self = this;
  357. const incMode = 0;
  358. function keydown(e) {}
  359. const down = (e) =>
  360. {
  361. if (CABLES.UI) gui.savedState.setUnSaved("transformDown", this.#params.posX.op.getSubPatch());
  362. isDown = true;
  363. document.addEventListener("pointerlockchange", lockChange, false);
  364. document.addEventListener("mozpointerlockchange", lockChange, false);
  365. document.addEventListener("webkitpointerlockchange", lockChange, false);
  366. document.addEventListener("keydown", keydown, false);
  367. el.requestPointerLock = el.requestPointerLock || el.mozRequestPointerLock || el.webkitRequestPointerLock;
  368. if (el.requestPointerLock) el.requestPointerLock();
  369. };
  370. const up = (e) =>
  371. {
  372. self.flipY = false;
  373. self.flipX = false;
  374. if (self._draggingPort)
  375. {
  376. const undofunc = (function (patch, p1Name, op1Id, oldValue, newValue)
  377. {
  378. const op = patch.getOpById(op1Id);
  379. const p = op.getPortByName(p1Name);
  380. undo.add({
  381. "title": "move gizmo " + p.name,
  382. undo()
  383. {
  384. p.set(oldValue);
  385. gui.emitEvent("portValueEdited", op, p, oldValue);
  386. },
  387. redo()
  388. {
  389. p.set(newValue);
  390. gui.emitEvent("portValueEdited", op, p, newValue);
  391. }
  392. });
  393. }(
  394. self._draggingPort.op.patch,
  395. self._draggingPort.getName(),
  396. self._draggingPort.op.id,
  397. self.#origValue,
  398. self._draggingPort.get()
  399. ));
  400. }
  401. if (CABLES.UI && this.#params) gui.savedState.setUnSaved("transformUp", this.#params.posX.op.getSubPatch());
  402. isDown = false;
  403. document.removeEventListener("pointerlockchange", lockChange, false);
  404. document.removeEventListener("mozpointerlockchange", lockChange, false);
  405. document.removeEventListener("webkitpointerlockchange", lockChange, false);
  406. document.removeEventListener("keydown", keydown, false);
  407. if (document.exitPointerLock) document.exitPointerLock();
  408. document.removeEventListener("mouseup", up);
  409. document.removeEventListener("pointerdown", down);
  410. document.removeEventListener("pointermove", move, false);
  411. if (CABLES.UI && self._draggingPort) gui.opParams.show(self._draggingPort.op);
  412. };
  413. const move = (e) =>
  414. {
  415. if (CABLES.UI && this.#params) gui.savedState.setUnSaved("transformMove", this.#params.posX.op.getSubPatch());
  416. if (self._draggingPortY)
  417. {
  418. let vX = e.movementX * ((self._multi || 1) / 100);
  419. let vY = e.movementY * ((self._multi || 1) / 100);
  420. let p = [vX, vY];
  421. vec2.rotate(p, p, [0, 0], -self.lineX.angle);
  422. vX = p[0];
  423. vY = p[1];
  424. if (self.flipY) vY *= -1;
  425. if (self.flipX) vX *= -1;
  426. if (e.shiftKey) vX *= 0.025;
  427. if (e.shiftKey) vY *= 0.025;
  428. self.#dragSum += vX;
  429. self.#dragSumY += vY;
  430. const newValue = self.#origValue + self.#dragSum;
  431. const newValueY = self._origValueY + self.#dragSumY;
  432. self._draggingPort.set(newValue);
  433. self._draggingPortY.set(newValueY);
  434. if (CABLES.UI) gui.emitEvent(Gizmo.EVENT_GUI_GIZMO_MOVE, self._draggingPort.op.id, self._draggingPort.getName(), newValue);
  435. if (CABLES.UI) gui.emitEvent(Gizmo.EVENT_GUI_GIZMO_MOVE, self._draggingPortY.op.id, self._draggingPortY.getName(), newValueY);
  436. }
  437. else
  438. {
  439. // one axis...
  440. let v = (e.movementY + e.movementX) * (self.#dir * ((self._multi || 1) / 100));
  441. if (e.shiftKey) v *= 0.025;
  442. self.#dragSum += v;
  443. const newValue = self.#origValue + self.#dragSum;
  444. self._draggingPort.set(newValue);
  445. if (CABLES.UI) gui.emitEvent(Gizmo.EVENT_GUI_GIZMO_MOVE, self._draggingPort.op.id, self._draggingPort.getName(), newValue);
  446. }
  447. };
  448. function lockChange(e)
  449. {
  450. if (document.pointerLockElement === el || document.mozPointerLockElement === el || document.webkitPointerLockElement === el)
  451. {
  452. document.addEventListener("pointermove", move, false);
  453. }
  454. else
  455. {
  456. // escape clicked...
  457. self._draggingPort.set(self.#origValue);
  458. up();
  459. }
  460. }
  461. document.addEventListener("mouseup", up);
  462. document.addEventListener("pointerdown", down);
  463. }
  464. }
  465. const htmlLine = function (parentElement, color)
  466. {
  467. let line = null;
  468. this.angle = 0;
  469. function createLineElement(x, y, length, angle)
  470. {
  471. line = document.createElement("div");
  472. const styles = "border: 1px solid " + color + "; " +
  473. "width: " + length + "px; " +
  474. "height: 0px; " +
  475. "transform: rotate(" + angle + "rad); " +
  476. "position: absolute; " +
  477. "top: " + y + "px; " +
  478. "left: " + x + "px; ";
  479. line.setAttribute("style", styles);
  480. line.classList.add("gizmoline");
  481. return line;
  482. }
  483. function setPos(x, y, length, angle)
  484. {
  485. line.style.width = length + "px";
  486. line.style.top = y + "px";
  487. line.style.left = x + "px";
  488. line.style.transform = "rotate(" + angle + "rad)";
  489. line.style["z-index"] = "9999";
  490. }
  491. this.set = function (x1, y1, x2, y2)
  492. {
  493. let a = x1 - x2,
  494. b = y1 - y2,
  495. c = Math.sqrt(a * a + b * b);
  496. let sx = (x1 + x2) / 2,
  497. sy = (y1 + y2) / 2;
  498. let x = sx - c / 2,
  499. y = sy;
  500. const alpha = Math.PI - Math.atan2(-b, a);
  501. this.angle = alpha;
  502. setPos(x, y, c, alpha);
  503. };
  504. this.remove = function ()
  505. {
  506. line.remove();
  507. };
  508. this.hide = function ()
  509. {
  510. if (line) line.style.display = "none";
  511. };
  512. this.show = function ()
  513. {
  514. if (line) line.style.display = "block";
  515. };
  516. parentElement.appendChild(createLineElement(100, 100, 200, 200));
  517. this.hide();
  518. };