@judah-silva/rnacanvas 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -16,8 +16,9 @@ declare class Residue extends Group<MeshObject> {
16
16
  /**
17
17
  * Cretes a material with the given color and sets it to the mesh
18
18
  * @param color String in the format of #RRGGBB
19
+ * @param highlight Whether to apply a highlight effect to the residue
19
20
  */
20
- colorResidue(color: string): void;
21
+ colorResidue(color: string, highlight?: boolean): void;
21
22
  }
22
23
 
23
24
  /**
@@ -34,10 +35,15 @@ declare class MeshObject {
34
35
  applyHighlight(): void;
35
36
  resetHighlight(): void;
36
37
  /**
37
- * Cretes a material with the given color and sets it to the mesh
38
+ * Creates a material with the given color and sets it to the mesh
38
39
  * @param color String in the format of #RRGGBB
39
40
  */
40
41
  createAndSetMaterial(color: string): void;
42
+ /**
43
+ * Sets the material of the mesh object to the given color
44
+ * @param color String in the format of #RRGGBB
45
+ */
46
+ colorMaterial(color: string): void;
41
47
  setNewMesh(mesh: Mesh): MeshObject;
42
48
  get mesh(): Mesh;
43
49
  get uuid(): string;
@@ -300,12 +306,21 @@ declare class RenderScene {
300
306
  private _eventManager;
301
307
  private _isDisposed;
302
308
  isRunning: boolean;
309
+ /**
310
+ *
311
+ * @param canvas HTML Canvas Element
312
+ * @param hexColor Color in the format of #RRGGBB
313
+ * @param cameraPositionZ Z position of the camera
314
+ * @param renderWidth Width of the render window
315
+ * @param renderHeight Height of the render window
316
+ */
303
317
  constructor(canvas: HTMLCanvasElement, hexColor: string, cameraPositionZ: number, renderWidth: number, renderHeight: number);
304
318
  start(): void;
305
319
  stop(): void;
306
320
  dispose(): void;
307
321
  add(motif: Motif): void;
308
322
  remove(motif: Motif): void;
323
+ removeAll(): void;
309
324
  setBackgroundColor(hexColor: string): void;
310
325
  private _reattachToScene;
311
326
  private _handleResize;
@@ -469,8 +484,8 @@ declare class CanvasDataManager {
469
484
  private static _listeners;
470
485
  static get selectedMotifIds(): Set<string>;
471
486
  static setSelectedMotifIds(selectedMotifIds: Set<string>): void;
472
- static get lockedMotifIds(): string[];
473
- static setLockedMotifIds(lockedMotifIds: string[]): void;
487
+ static get lockedMotifIds(): Set<string>;
488
+ static setLockedMotifIds(lockedMotifIds: Set<string>): void;
474
489
  static get hardLockedMotifIds(): string[];
475
490
  static setHardLockedMotifIds(hardLockedMotifIds: string[]): void;
476
491
  static get scoreRMSD(): ScoreInfo[][];
package/dist/index.d.ts CHANGED
@@ -16,8 +16,9 @@ declare class Residue extends Group<MeshObject> {
16
16
  /**
17
17
  * Cretes a material with the given color and sets it to the mesh
18
18
  * @param color String in the format of #RRGGBB
19
+ * @param highlight Whether to apply a highlight effect to the residue
19
20
  */
20
- colorResidue(color: string): void;
21
+ colorResidue(color: string, highlight?: boolean): void;
21
22
  }
22
23
 
23
24
  /**
@@ -34,10 +35,15 @@ declare class MeshObject {
34
35
  applyHighlight(): void;
35
36
  resetHighlight(): void;
36
37
  /**
37
- * Cretes a material with the given color and sets it to the mesh
38
+ * Creates a material with the given color and sets it to the mesh
38
39
  * @param color String in the format of #RRGGBB
39
40
  */
40
41
  createAndSetMaterial(color: string): void;
42
+ /**
43
+ * Sets the material of the mesh object to the given color
44
+ * @param color String in the format of #RRGGBB
45
+ */
46
+ colorMaterial(color: string): void;
41
47
  setNewMesh(mesh: Mesh): MeshObject;
42
48
  get mesh(): Mesh;
43
49
  get uuid(): string;
@@ -300,12 +306,21 @@ declare class RenderScene {
300
306
  private _eventManager;
301
307
  private _isDisposed;
302
308
  isRunning: boolean;
309
+ /**
310
+ *
311
+ * @param canvas HTML Canvas Element
312
+ * @param hexColor Color in the format of #RRGGBB
313
+ * @param cameraPositionZ Z position of the camera
314
+ * @param renderWidth Width of the render window
315
+ * @param renderHeight Height of the render window
316
+ */
303
317
  constructor(canvas: HTMLCanvasElement, hexColor: string, cameraPositionZ: number, renderWidth: number, renderHeight: number);
304
318
  start(): void;
305
319
  stop(): void;
306
320
  dispose(): void;
307
321
  add(motif: Motif): void;
308
322
  remove(motif: Motif): void;
323
+ removeAll(): void;
309
324
  setBackgroundColor(hexColor: string): void;
310
325
  private _reattachToScene;
311
326
  private _handleResize;
@@ -469,8 +484,8 @@ declare class CanvasDataManager {
469
484
  private static _listeners;
470
485
  static get selectedMotifIds(): Set<string>;
471
486
  static setSelectedMotifIds(selectedMotifIds: Set<string>): void;
472
- static get lockedMotifIds(): string[];
473
- static setLockedMotifIds(lockedMotifIds: string[]): void;
487
+ static get lockedMotifIds(): Set<string>;
488
+ static setLockedMotifIds(lockedMotifIds: Set<string>): void;
474
489
  static get hardLockedMotifIds(): string[];
475
490
  static setHardLockedMotifIds(hardLockedMotifIds: string[]): void;
476
491
  static get scoreRMSD(): ScoreInfo[][];
package/dist/index.js CHANGED
@@ -328,10 +328,14 @@ var Residue = class extends Group {
328
328
  /**
329
329
  * Cretes a material with the given color and sets it to the mesh
330
330
  * @param color String in the format of #RRGGBB
331
+ * @param highlight Whether to apply a highlight effect to the residue
331
332
  */
332
- colorResidue(color) {
333
+ colorResidue(color, highlight = false) {
333
334
  this._children.forEach((child) => {
334
- child.createAndSetMaterial(color);
335
+ child.colorMaterial(color);
336
+ if (highlight) {
337
+ child.applyHighlight();
338
+ }
335
339
  });
336
340
  }
337
341
  };
@@ -363,23 +367,34 @@ var MeshObject = class {
363
367
  }
364
368
  applyHighlight() {
365
369
  const mat = this._mesh.material;
366
- mat.emissiveColor = mat.diffuseColor.scale(0.75);
370
+ mat.emissiveColor = mat.diffuseColor.scale(0.6);
367
371
  }
368
372
  resetHighlight() {
369
373
  const mat = this._mesh.material;
370
374
  mat.emissiveColor = import_core7.Color3.Black();
371
375
  }
372
376
  /**
373
- * Cretes a material with the given color and sets it to the mesh
377
+ * Creates a material with the given color and sets it to the mesh
374
378
  * @param color String in the format of #RRGGBB
375
379
  */
376
380
  createAndSetMaterial(color) {
377
381
  const mat = new import_core7.StandardMaterial("mat");
378
- const color3 = import_core7.Color3.FromHexString(`#${color.replace(/^0x/, "")}`);
382
+ const color3 = import_core7.Color3.FromHexString(`${color.replace(/^0x/i, "")}`);
379
383
  mat.diffuseColor = color3;
380
384
  mat.specularColor = color3;
385
+ mat.backFaceCulling = false;
381
386
  this._mesh.material = mat;
382
387
  }
388
+ /**
389
+ * Sets the material of the mesh object to the given color
390
+ * @param color String in the format of #RRGGBB
391
+ */
392
+ colorMaterial(color) {
393
+ const mat = this._mesh.material;
394
+ const color3 = import_core7.Color3.FromHexString(`${color.replace(/^0x/i, "")}`);
395
+ mat.diffuseColor = color3;
396
+ mat.specularColor = color3;
397
+ }
383
398
  setNewMesh(mesh) {
384
399
  this._mesh = mesh;
385
400
  return this;
@@ -603,9 +618,19 @@ var EventManager = class {
603
618
  canceled: false,
604
619
  timestamp: performance.now()
605
620
  };
606
- if (keyboardInfo.type === import_core8.KeyboardEventTypes.KEYDOWN) {
621
+ if (keyboardInfo.type === import_core8.KeyboardEventTypes.KEYDOWN && /^[wasdqeWASD]$/.test(event.key)) {
622
+ if (event.key.charCodeAt(0) >= 97) {
623
+ this._activeKeys.delete(event.key.toUpperCase());
624
+ } else {
625
+ this._activeKeys.delete(event.key.toLowerCase());
626
+ }
607
627
  this._activeKeys.add(event.key);
608
628
  } else if (keyboardInfo.type === import_core8.KeyboardEventTypes.KEYUP) {
629
+ if (event.key.charCodeAt(0) >= 97) {
630
+ this._activeKeys.delete(event.key.toUpperCase());
631
+ } else {
632
+ this._activeKeys.delete(event.key.toLowerCase());
633
+ }
609
634
  this._activeKeys.delete(event.key);
610
635
  }
611
636
  if (this._activeKeys.has("w")) keyboardEvent.rotationAxis.add(new Vec3(-1, 0, 0));
@@ -667,12 +692,20 @@ var RenderScene = class {
667
692
  // State variables
668
693
  _isDisposed = false;
669
694
  isRunning = false;
695
+ /**
696
+ *
697
+ * @param canvas HTML Canvas Element
698
+ * @param hexColor Color in the format of #RRGGBB
699
+ * @param cameraPositionZ Z position of the camera
700
+ * @param renderWidth Width of the render window
701
+ * @param renderHeight Height of the render window
702
+ */
670
703
  constructor(canvas, hexColor, cameraPositionZ, renderWidth, renderHeight) {
671
704
  this._canvas = canvas;
672
705
  this._engine = new import_core9.Engine(this._canvas, true);
673
706
  this._engine.setSize(renderWidth, renderHeight);
674
707
  this._scene = new import_core9.Scene(this._engine);
675
- this._scene.clearColor = import_core9.Color4.FromHexString(`${hexColor.replace(/^0x/, "")}`);
708
+ this._scene.clearColor = import_core9.Color4.FromHexString(`${hexColor.replace(/^0x/i, "")}`);
676
709
  this._camera = new import_core9.UniversalCamera("camera", new import_core9.Vector3(0, 0, cameraPositionZ));
677
710
  this._camera.setTarget(new import_core9.Vector3(0, 0, 0));
678
711
  this._camera.mode = import_core9.Camera.ORTHOGRAPHIC_CAMERA;
@@ -684,7 +717,6 @@ var RenderScene = class {
684
717
  const light = new import_core9.HemisphericLight("light", import_core9.Vector3.Up(), this._scene);
685
718
  light.intensity = 1.25;
686
719
  this._eventManager = new EventManager();
687
- window.addEventListener("resize", this._handleResize.bind(this));
688
720
  }
689
721
  // Start the render loop
690
722
  start() {
@@ -725,7 +757,6 @@ var RenderScene = class {
725
757
  // Stop and dispose of all resources
726
758
  dispose() {
727
759
  this.stop();
728
- window.removeEventListener("resize", this._handleResize);
729
760
  this._eventManager.dispose();
730
761
  this._scene.dispose();
731
762
  this._engine.dispose();
@@ -745,10 +776,18 @@ var RenderScene = class {
745
776
  return;
746
777
  }
747
778
  this._children.delete(motif.uuid);
748
- this._scene.removeTransformNode(motif.node);
779
+ motif.node.dispose(false, true);
780
+ }
781
+ removeAll() {
782
+ this._scene.meshes.slice().forEach((mesh) => {
783
+ if (mesh && this._scene.getMeshById(mesh.id)) {
784
+ this._scene.removeMesh(mesh);
785
+ }
786
+ });
787
+ this._children.clear();
749
788
  }
750
789
  setBackgroundColor(hexColor) {
751
- this._scene.clearColor = import_core9.Color4.FromHexString(`${hexColor.replace(/^0x/, "")}`);
790
+ this._scene.clearColor = import_core9.Color4.FromHexString(`${hexColor.replace(/^0x/i, "")}`);
752
791
  }
753
792
  _reattachToScene(node, currObj) {
754
793
  node._scene = this._scene;
@@ -762,7 +801,7 @@ var RenderScene = class {
762
801
  geo?.applyToMesh(mesh);
763
802
  mesh.material = mat;
764
803
  currObj.setNewMesh(mesh);
765
- node.dispose();
804
+ node.dispose(false, true);
766
805
  }
767
806
  if (!(currObj instanceof MeshObject)) {
768
807
  currObj.children.forEach((childObj) => {
@@ -861,7 +900,7 @@ function getPoints(nucleotideData, includeAtoms) {
861
900
  }
862
901
 
863
902
  // src/3D/utils/GetMotif.ts
864
- async function getMotif(motifName, motifMesh, motifColorHex = "0xcc2900", includeAtoms = true) {
903
+ async function getMotif(motifName, motifMesh, motifColorHex = "#cc2900", includeAtoms = true) {
865
904
  const motif = new Motif(`${motifName}_motif`);
866
905
  for (const [key] of Object.entries(motifMesh)) {
867
906
  const { vertices, indices } = getPoints(motifMesh[key], includeAtoms);
@@ -895,7 +934,7 @@ var CanvasAttributeTypes = /* @__PURE__ */ ((CanvasAttributeTypes2) => {
895
934
  })(CanvasAttributeTypes || {});
896
935
  var CanvasDataManager = class {
897
936
  static _selectedMotifIds = /* @__PURE__ */ new Set();
898
- static _lockedMotifIds = [];
937
+ static _lockedMotifIds = /* @__PURE__ */ new Set();
899
938
  static _hardLockedMotifIds = [];
900
939
  static _scoreRMSD = [];
901
940
  static _kabschRMSD = [];
@@ -1152,28 +1191,27 @@ function Canvas({
1152
1191
  }) {
1153
1192
  const canvasRef = (0, import_react.useRef)(null);
1154
1193
  const scene = (0, import_react.useRef)(null);
1155
- const motifs = [];
1194
+ const motifs = (0, import_react.useRef)([]);
1156
1195
  let hardLockedMotifIds = [];
1157
1196
  motifProps.forEach((motifProp) => {
1158
- motifs.push(motifProp.motif);
1159
1197
  if (motifProp.locked) hardLockedMotifIds.push(motifProp.motif.uuid);
1160
1198
  });
1161
1199
  CanvasDataManager.setHardLockedMotifIds(hardLockedMotifIds);
1162
1200
  const selectedMotifMeshState = (0, import_react.useRef)(/* @__PURE__ */ new Set());
1163
- const lockedMotifIdState = (0, import_react.useRef)([]);
1201
+ const lockedMotifIdState = (0, import_react.useRef)(/* @__PURE__ */ new Set());
1164
1202
  const [cursorStyle, setCursorStyle] = (0, import_react.useState)("auto");
1165
1203
  const [selectedMotifIds, setSelectedmotifIds] = (0, import_react.useState)(/* @__PURE__ */ new Set());
1166
1204
  const [scoreRMSD, setScoreRMSD] = (0, import_react.useState)([]);
1167
1205
  const [kabschRMSD, setKabschRMSD] = (0, import_react.useState)([]);
1168
- const [lockedMotifIds, setLockedMotifIds] = (0, import_react.useState)([]);
1206
+ const [lockedMotifIds, setLockedMotifIds] = (0, import_react.useState)(/* @__PURE__ */ new Set());
1169
1207
  const addMotif = (motif) => {
1170
1208
  if (selectedMotifMeshState.current.has(motif)) {
1171
1209
  return;
1172
1210
  }
1173
1211
  const newSet = /* @__PURE__ */ new Set();
1174
- for (let i = 0; i < motifs.length; i += 1) {
1175
- if (selectedMotifMeshState.current.has(motifs[i]) || motifs[i].uuid === motif.uuid) {
1176
- newSet.add(motifs[i].uuid);
1212
+ for (let i = 0; i < motifs.current.length; i += 1) {
1213
+ if (selectedMotifMeshState.current.has(motifs.current[i]) || motifs.current[i].uuid === motif.uuid) {
1214
+ newSet.add(motifs.current[i].uuid);
1177
1215
  }
1178
1216
  }
1179
1217
  setSelectedmotifIds(newSet);
@@ -1191,7 +1229,7 @@ function Canvas({
1191
1229
  });
1192
1230
  };
1193
1231
  function updateGlow() {
1194
- motifs.forEach((motif) => {
1232
+ motifs.current.forEach((motif) => {
1195
1233
  motif.children.forEach((residue) => {
1196
1234
  residue.children.forEach((childMesh) => {
1197
1235
  if (selectedMotifIds.has(motif.uuid)) {
@@ -1203,12 +1241,27 @@ function Canvas({
1203
1241
  });
1204
1242
  });
1205
1243
  }
1244
+ function motifOutOfBounds(motif, deltaX, deltaY) {
1245
+ if (!canvasRef.current) {
1246
+ return false;
1247
+ }
1248
+ const canvasWidth = canvasRef.current.width;
1249
+ const canvasHeight = canvasRef.current.height;
1250
+ const left = -canvasWidth / 2;
1251
+ const right = canvasWidth / 2;
1252
+ const top = canvasHeight / 2;
1253
+ const bottom = -canvasHeight / 2;
1254
+ const newX = motif.position.x + deltaX;
1255
+ const newY = motif.position.y + deltaY;
1256
+ if (newX <= left || newX >= right || newY <= bottom || newY >= top) return true;
1257
+ return false;
1258
+ }
1206
1259
  function onSelectMotif(event) {
1207
1260
  if (event.type !== Events.EventType.OBJECT_SELECTED) {
1208
1261
  return;
1209
1262
  }
1210
1263
  const { motif } = event;
1211
- if (!motif || selectedMotifMeshState.current.has(motif) || lockedMotifIdState.current.includes(motif.uuid) || hardLockedMotifIds.includes(motif.uuid)) {
1264
+ if (!motif || selectedMotifMeshState.current.has(motif)) {
1212
1265
  return;
1213
1266
  }
1214
1267
  if (event.multiSelect && motif) {
@@ -1240,7 +1293,7 @@ function Canvas({
1240
1293
  const deltaX = rawDeltaX / renderWidth * canvasRef.current.width;
1241
1294
  const deltaY = rawDeltaY / renderHeight * canvasRef.current.height;
1242
1295
  selectedMotifMeshState.current.forEach((element) => {
1243
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1296
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid) && !motifOutOfBounds(element, -deltaX, -deltaY)) {
1244
1297
  element.translate(-deltaX, -deltaY, 0);
1245
1298
  }
1246
1299
  });
@@ -1253,11 +1306,11 @@ function Canvas({
1253
1306
  if (scene.current) {
1254
1307
  const angle = directionVec.length() / scene.current.renderWidth * (3 * Math.PI);
1255
1308
  selectedMotifMeshState.current.forEach((element) => {
1256
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1309
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1257
1310
  element.rotate(axisVec, angle);
1258
1311
  }
1259
1312
  });
1260
- if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1313
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs.current));
1261
1314
  }
1262
1315
  }
1263
1316
  }
@@ -1270,7 +1323,7 @@ function Canvas({
1270
1323
  const zoomSpeed = 0.1;
1271
1324
  const zoomDirection = event.originalEvent.deltaY > 0 ? -1 : 1;
1272
1325
  selectedMotifMeshState.current.forEach((element) => {
1273
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid) && !(element.scale <= 1 && zoomDirection === -1) && !(element.scale >= 30 && zoomDirection === 1)) {
1326
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid) && !(element.scale <= 1 && zoomDirection === -1) && !(element.scale >= 30 && zoomDirection === 1)) {
1274
1327
  const scaleFactor = 1 + zoomDirection * zoomSpeed;
1275
1328
  element.multiplyScalar(scaleFactor);
1276
1329
  }
@@ -1298,19 +1351,19 @@ function Canvas({
1298
1351
  }
1299
1352
  const angle = event.rotationAxis.length() / 500 * (6 * Math.PI);
1300
1353
  selectedMotifMeshState.current.forEach((element) => {
1301
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1354
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1302
1355
  element.rotate(event.rotationAxis, angle);
1303
1356
  }
1304
1357
  });
1305
- if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1358
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs.current));
1306
1359
  }
1307
1360
  function onKeyboardTranslate(event) {
1308
1361
  if (event.translationDirection.equals(Vec3.Zero)) {
1309
1362
  return;
1310
1363
  }
1311
- event.translationDirection.multiplyScalar(0.5);
1364
+ event.translationDirection.multiplyScalar(4.5);
1312
1365
  selectedMotifMeshState.current.forEach((element) => {
1313
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1366
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid) && !motifOutOfBounds(element, event.translationDirection.x, event.translationDirection.y)) {
1314
1367
  element.translate(
1315
1368
  event.translationDirection.x,
1316
1369
  event.translationDirection.y,
@@ -1323,10 +1376,10 @@ function Canvas({
1323
1376
  if (!event.rotationAxis.equals(Vec3.Zero) || !event.translationDirection.equals(Vec3.Zero)) {
1324
1377
  return;
1325
1378
  }
1326
- if (!/^[1-9]$/.test(event.key) || Number(event.key) > motifs.length) {
1379
+ if (!/^[1-9]$/.test(event.key) || Number(event.key) > motifs.current.length) {
1327
1380
  return;
1328
1381
  }
1329
- const motif = motifs[Number(event.key) - 1];
1382
+ const motif = motifs.current[Number(event.key) - 1];
1330
1383
  if (selectedMotifMeshState.current.has(motif)) {
1331
1384
  removeMotif(motif);
1332
1385
  } else {
@@ -1349,8 +1402,8 @@ function Canvas({
1349
1402
  return positions;
1350
1403
  };
1351
1404
  const updateMotifs = () => {
1352
- const positions = calculatePositions(motifs.length);
1353
- motifs.forEach((motifMesh, index) => {
1405
+ const positions = calculatePositions(motifs.current.length);
1406
+ motifs.current.forEach((motifMesh, index) => {
1354
1407
  if (!scene.current?.children.has(motifMesh.uuid)) return;
1355
1408
  if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
1356
1409
  motifMesh.setPosition(positions[index].x, positions[index].y, positions[index].z);
@@ -1360,9 +1413,6 @@ function Canvas({
1360
1413
  motifMesh.setScale(scale);
1361
1414
  });
1362
1415
  };
1363
- (0, import_react.useEffect)(() => {
1364
- updateMotifs();
1365
- }, [motifProps]);
1366
1416
  (0, import_react.useEffect)(() => {
1367
1417
  const unsubscribe = CanvasDataManager.subscribe("selectedMotifs" /* SELECTED_MOTIFS */, () => {
1368
1418
  setSelectedmotifIds(CanvasDataManager.selectedMotifIds);
@@ -1374,13 +1424,13 @@ function Canvas({
1374
1424
  CanvasDataManager.setSelectedMotifIds(selectedMotifIds);
1375
1425
  }
1376
1426
  selectedMotifMeshState.current.clear();
1377
- motifs.forEach((motif) => {
1427
+ motifs.current.forEach((motif) => {
1378
1428
  if (selectedMotifIds.has(motif.uuid)) {
1379
1429
  selectedMotifMeshState.current.add(motif);
1380
1430
  }
1381
1431
  });
1382
1432
  updateGlow();
1383
- if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1433
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs.current));
1384
1434
  }, [selectedMotifIds]);
1385
1435
  (0, import_react.useEffect)(() => {
1386
1436
  const unsubscribe = CanvasDataManager.subscribe("lockedMotifIds" /* LOCKED_MOTIF_IDS */, () => {
@@ -1421,8 +1471,8 @@ function Canvas({
1421
1471
  if (!showRMSD) return;
1422
1472
  if (CanvasDataManager.kabschRMSD !== kabschRMSD) {
1423
1473
  CanvasDataManager.setKabschRMSD(kabschRMSD);
1424
- } else if (CanvasDataManager.kabschRMSD.length !== motifs.length) {
1425
- CanvasDataManager.setKabschRMSD(calculateAllKabschRMSD(motifs));
1474
+ } else if (CanvasDataManager.kabschRMSD.length !== motifs.current.length) {
1475
+ CanvasDataManager.setKabschRMSD(calculateAllKabschRMSD(motifs.current));
1426
1476
  }
1427
1477
  }, [kabschRMSD]);
1428
1478
  (0, import_react.useEffect)(() => {
@@ -1441,84 +1491,91 @@ function Canvas({
1441
1491
  rendererSizeIsWindow ? window.innerWidth : rendererWidth,
1442
1492
  rendererSizeIsWindow ? window.innerHeight : rendererHeight
1443
1493
  );
1444
- }
1445
- if (motifs.length > 0) {
1446
- if (showRMSD) setKabschRMSD(calculateAllKabschRMSD(motifs));
1447
- if (scene.current.children.size !== motifs.length) {
1448
- const positions = calculatePositions(motifs.length);
1449
- motifs.forEach((motifMesh, index) => {
1450
- scene.current?.add(motifMesh);
1451
- if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
1452
- motifMesh.setPosition(positions[index].x, positions[index].y, positions[index].z);
1453
- if (motifProps[index].rotation) motifMesh.setQuaternion(motifProps[index].rotation);
1454
- let scale = canvasRef.current.width / 250;
1455
- if (motifProps[index].scale) scale = motifProps[index].scale;
1456
- motifMesh.multiplyScalar(scale);
1494
+ const eventManager = scene.current?.eventManager;
1495
+ eventManager.on(Events.EventType.OBJECT_SELECTED, onSelectMotif);
1496
+ eventManager.on(Events.EventType.OBJECT_DESELECTED, onDeselectMotif);
1497
+ eventManager.on(Events.EventType.POINTER_MOVE, onMouseMove);
1498
+ eventManager.on(Events.EventType.POINTER_WHEEL, onMouseScroll);
1499
+ eventManager.on(Events.EventType.POINTER_DOWN, onMouseDown);
1500
+ eventManager.on(Events.EventType.POINTER_UP, onMouseUp);
1501
+ eventManager.on(Events.EventType.KEY_DOWN, onKeyboardRotate);
1502
+ eventManager.on(Events.EventType.KEY_DOWN, onKeyboardTranslate);
1503
+ eventManager.on(Events.EventType.KEY_DOWN, onKeyboardSelect);
1504
+ if (customEventProps) {
1505
+ customEventProps.forEach((customEventProp) => {
1506
+ switch (customEventProp.eventType) {
1507
+ // Handle Pointer Events
1508
+ case Events.EventType.POINTER_DOWN:
1509
+ case Events.EventType.POINTER_UP:
1510
+ case Events.EventType.POINTER_MOVE:
1511
+ case Events.EventType.POINTER_WHEEL:
1512
+ case Events.EventType.TOUCH_END:
1513
+ case Events.EventType.TOUCH_MOVE:
1514
+ case Events.EventType.TOUCH_START:
1515
+ eventManager.on(
1516
+ customEventProp.eventType,
1517
+ customEventProp.callback
1518
+ );
1519
+ break;
1520
+ // Handle Keyboard Events
1521
+ case Events.EventType.KEY_DOWN:
1522
+ case Events.EventType.KEY_UP:
1523
+ eventManager.on(
1524
+ customEventProp.eventType,
1525
+ customEventProp.callback
1526
+ );
1527
+ break;
1528
+ // Handle Pinch Events
1529
+ case Events.EventType.PINCH:
1530
+ case Events.EventType.PINCH_END:
1531
+ case Events.EventType.PINCH_START:
1532
+ eventManager.on(
1533
+ customEventProp.eventType,
1534
+ customEventProp.callback
1535
+ );
1536
+ break;
1537
+ // Handle Selection Events
1538
+ case Events.EventType.OBJECT_SELECTED:
1539
+ case Events.EventType.OBJECT_DESELECTED:
1540
+ eventManager.on(
1541
+ customEventProp.eventType,
1542
+ customEventProp.callback
1543
+ );
1544
+ break;
1545
+ // Handle Events
1546
+ default:
1547
+ eventManager.on(
1548
+ customEventProp.eventType,
1549
+ customEventProp.callback
1550
+ );
1551
+ break;
1552
+ }
1457
1553
  });
1458
- const eventManager = scene.current?.eventManager;
1459
- eventManager.on(Events.EventType.OBJECT_SELECTED, onSelectMotif);
1460
- eventManager.on(Events.EventType.OBJECT_DESELECTED, onDeselectMotif);
1461
- eventManager.on(Events.EventType.POINTER_MOVE, onMouseMove);
1462
- eventManager.on(Events.EventType.POINTER_WHEEL, onMouseScroll);
1463
- eventManager.on(Events.EventType.POINTER_DOWN, onMouseDown);
1464
- eventManager.on(Events.EventType.POINTER_UP, onMouseUp);
1465
- eventManager.on(Events.EventType.KEY_DOWN, onKeyboardRotate);
1466
- eventManager.on(Events.EventType.KEY_DOWN, onKeyboardTranslate);
1467
- eventManager.on(Events.EventType.KEY_DOWN, onKeyboardSelect);
1468
- if (customEventProps) {
1469
- customEventProps.forEach((customEventProp) => {
1470
- switch (customEventProp.eventType) {
1471
- // Handle Pointer Events
1472
- case Events.EventType.POINTER_DOWN:
1473
- case Events.EventType.POINTER_UP:
1474
- case Events.EventType.POINTER_MOVE:
1475
- case Events.EventType.POINTER_WHEEL:
1476
- case Events.EventType.TOUCH_END:
1477
- case Events.EventType.TOUCH_MOVE:
1478
- case Events.EventType.TOUCH_START:
1479
- eventManager.on(
1480
- customEventProp.eventType,
1481
- customEventProp.callback
1482
- );
1483
- break;
1484
- // Handle Keyboard Events
1485
- case Events.EventType.KEY_DOWN:
1486
- case Events.EventType.KEY_UP:
1487
- eventManager.on(
1488
- customEventProp.eventType,
1489
- customEventProp.callback
1490
- );
1491
- break;
1492
- // Handle Pinch Events
1493
- case Events.EventType.PINCH:
1494
- case Events.EventType.PINCH_END:
1495
- case Events.EventType.PINCH_START:
1496
- eventManager.on(
1497
- customEventProp.eventType,
1498
- customEventProp.callback
1499
- );
1500
- break;
1501
- // Handle Selection Events
1502
- case Events.EventType.OBJECT_SELECTED:
1503
- case Events.EventType.OBJECT_DESELECTED:
1504
- eventManager.on(
1505
- customEventProp.eventType,
1506
- customEventProp.callback
1507
- );
1508
- break;
1509
- // Handle Events
1510
- default:
1511
- eventManager.on(
1512
- customEventProp.eventType,
1513
- customEventProp.callback
1514
- );
1515
- break;
1516
- }
1517
- });
1518
- }
1519
1554
  }
1555
+ scene.current.start();
1556
+ }
1557
+ }, []);
1558
+ (0, import_react.useEffect)(() => {
1559
+ if (!scene.current || !canvasRef.current) return;
1560
+ motifs.current.length = 0;
1561
+ motifProps.forEach((motifProp) => {
1562
+ motifs.current.push(motifProp.motif);
1563
+ });
1564
+ if (motifs.current.length > 0) {
1565
+ if (showRMSD) setKabschRMSD(calculateAllKabschRMSD(motifs.current));
1566
+ scene.current.removeAll();
1567
+ const positions = calculatePositions(motifs.current.length);
1568
+ motifs.current.forEach((motifMesh, index) => {
1569
+ scene.current?.add(motifMesh);
1570
+ if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
1571
+ motifMesh.setPosition(positions[index].x, positions[index].y, positions[index].z);
1572
+ if (motifProps[index].rotation) motifMesh.setQuaternion(motifProps[index].rotation);
1573
+ let scale = canvasRef.current.width / 250;
1574
+ if (motifProps[index].scale) scale = motifProps[index].scale;
1575
+ motifMesh.setScale(scale);
1576
+ if (motifProps[index].locked) lockedMotifIdState.current.add(motifMesh.uuid);
1577
+ });
1520
1578
  }
1521
- scene.current?.start();
1522
1579
  }, [rendererWidth, rendererHeight, rendererSizeIsWindow, motifProps]);
1523
1580
  return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("canvas", { ref: canvasRef }));
1524
1581
  }
package/dist/index.mjs CHANGED
@@ -271,10 +271,14 @@ var Residue = class extends Group {
271
271
  /**
272
272
  * Cretes a material with the given color and sets it to the mesh
273
273
  * @param color String in the format of #RRGGBB
274
+ * @param highlight Whether to apply a highlight effect to the residue
274
275
  */
275
- colorResidue(color) {
276
+ colorResidue(color, highlight = false) {
276
277
  this._children.forEach((child) => {
277
- child.createAndSetMaterial(color);
278
+ child.colorMaterial(color);
279
+ if (highlight) {
280
+ child.applyHighlight();
281
+ }
278
282
  });
279
283
  }
280
284
  };
@@ -306,23 +310,34 @@ var MeshObject = class {
306
310
  }
307
311
  applyHighlight() {
308
312
  const mat = this._mesh.material;
309
- mat.emissiveColor = mat.diffuseColor.scale(0.75);
313
+ mat.emissiveColor = mat.diffuseColor.scale(0.6);
310
314
  }
311
315
  resetHighlight() {
312
316
  const mat = this._mesh.material;
313
317
  mat.emissiveColor = Color3.Black();
314
318
  }
315
319
  /**
316
- * Cretes a material with the given color and sets it to the mesh
320
+ * Creates a material with the given color and sets it to the mesh
317
321
  * @param color String in the format of #RRGGBB
318
322
  */
319
323
  createAndSetMaterial(color) {
320
324
  const mat = new StandardMaterial("mat");
321
- const color3 = Color3.FromHexString(`#${color.replace(/^0x/, "")}`);
325
+ const color3 = Color3.FromHexString(`${color.replace(/^0x/i, "")}`);
322
326
  mat.diffuseColor = color3;
323
327
  mat.specularColor = color3;
328
+ mat.backFaceCulling = false;
324
329
  this._mesh.material = mat;
325
330
  }
331
+ /**
332
+ * Sets the material of the mesh object to the given color
333
+ * @param color String in the format of #RRGGBB
334
+ */
335
+ colorMaterial(color) {
336
+ const mat = this._mesh.material;
337
+ const color3 = Color3.FromHexString(`${color.replace(/^0x/i, "")}`);
338
+ mat.diffuseColor = color3;
339
+ mat.specularColor = color3;
340
+ }
326
341
  setNewMesh(mesh) {
327
342
  this._mesh = mesh;
328
343
  return this;
@@ -546,9 +561,19 @@ var EventManager = class {
546
561
  canceled: false,
547
562
  timestamp: performance.now()
548
563
  };
549
- if (keyboardInfo.type === KeyboardEventTypes.KEYDOWN) {
564
+ if (keyboardInfo.type === KeyboardEventTypes.KEYDOWN && /^[wasdqeWASD]$/.test(event.key)) {
565
+ if (event.key.charCodeAt(0) >= 97) {
566
+ this._activeKeys.delete(event.key.toUpperCase());
567
+ } else {
568
+ this._activeKeys.delete(event.key.toLowerCase());
569
+ }
550
570
  this._activeKeys.add(event.key);
551
571
  } else if (keyboardInfo.type === KeyboardEventTypes.KEYUP) {
572
+ if (event.key.charCodeAt(0) >= 97) {
573
+ this._activeKeys.delete(event.key.toUpperCase());
574
+ } else {
575
+ this._activeKeys.delete(event.key.toLowerCase());
576
+ }
552
577
  this._activeKeys.delete(event.key);
553
578
  }
554
579
  if (this._activeKeys.has("w")) keyboardEvent.rotationAxis.add(new Vec3(-1, 0, 0));
@@ -610,12 +635,20 @@ var RenderScene = class {
610
635
  // State variables
611
636
  _isDisposed = false;
612
637
  isRunning = false;
638
+ /**
639
+ *
640
+ * @param canvas HTML Canvas Element
641
+ * @param hexColor Color in the format of #RRGGBB
642
+ * @param cameraPositionZ Z position of the camera
643
+ * @param renderWidth Width of the render window
644
+ * @param renderHeight Height of the render window
645
+ */
613
646
  constructor(canvas, hexColor, cameraPositionZ, renderWidth, renderHeight) {
614
647
  this._canvas = canvas;
615
648
  this._engine = new Engine(this._canvas, true);
616
649
  this._engine.setSize(renderWidth, renderHeight);
617
650
  this._scene = new Scene5(this._engine);
618
- this._scene.clearColor = Color4.FromHexString(`${hexColor.replace(/^0x/, "")}`);
651
+ this._scene.clearColor = Color4.FromHexString(`${hexColor.replace(/^0x/i, "")}`);
619
652
  this._camera = new UniversalCamera("camera", new Vector33(0, 0, cameraPositionZ));
620
653
  this._camera.setTarget(new Vector33(0, 0, 0));
621
654
  this._camera.mode = Camera.ORTHOGRAPHIC_CAMERA;
@@ -627,7 +660,6 @@ var RenderScene = class {
627
660
  const light = new HemisphericLight("light", Vector33.Up(), this._scene);
628
661
  light.intensity = 1.25;
629
662
  this._eventManager = new EventManager();
630
- window.addEventListener("resize", this._handleResize.bind(this));
631
663
  }
632
664
  // Start the render loop
633
665
  start() {
@@ -668,7 +700,6 @@ var RenderScene = class {
668
700
  // Stop and dispose of all resources
669
701
  dispose() {
670
702
  this.stop();
671
- window.removeEventListener("resize", this._handleResize);
672
703
  this._eventManager.dispose();
673
704
  this._scene.dispose();
674
705
  this._engine.dispose();
@@ -688,10 +719,18 @@ var RenderScene = class {
688
719
  return;
689
720
  }
690
721
  this._children.delete(motif.uuid);
691
- this._scene.removeTransformNode(motif.node);
722
+ motif.node.dispose(false, true);
723
+ }
724
+ removeAll() {
725
+ this._scene.meshes.slice().forEach((mesh) => {
726
+ if (mesh && this._scene.getMeshById(mesh.id)) {
727
+ this._scene.removeMesh(mesh);
728
+ }
729
+ });
730
+ this._children.clear();
692
731
  }
693
732
  setBackgroundColor(hexColor) {
694
- this._scene.clearColor = Color4.FromHexString(`${hexColor.replace(/^0x/, "")}`);
733
+ this._scene.clearColor = Color4.FromHexString(`${hexColor.replace(/^0x/i, "")}`);
695
734
  }
696
735
  _reattachToScene(node, currObj) {
697
736
  node._scene = this._scene;
@@ -705,7 +744,7 @@ var RenderScene = class {
705
744
  geo?.applyToMesh(mesh);
706
745
  mesh.material = mat;
707
746
  currObj.setNewMesh(mesh);
708
- node.dispose();
747
+ node.dispose(false, true);
709
748
  }
710
749
  if (!(currObj instanceof MeshObject)) {
711
750
  currObj.children.forEach((childObj) => {
@@ -804,7 +843,7 @@ function getPoints(nucleotideData, includeAtoms) {
804
843
  }
805
844
 
806
845
  // src/3D/utils/GetMotif.ts
807
- async function getMotif(motifName, motifMesh, motifColorHex = "0xcc2900", includeAtoms = true) {
846
+ async function getMotif(motifName, motifMesh, motifColorHex = "#cc2900", includeAtoms = true) {
808
847
  const motif = new Motif(`${motifName}_motif`);
809
848
  for (const [key] of Object.entries(motifMesh)) {
810
849
  const { vertices, indices } = getPoints(motifMesh[key], includeAtoms);
@@ -838,7 +877,7 @@ var CanvasAttributeTypes = /* @__PURE__ */ ((CanvasAttributeTypes2) => {
838
877
  })(CanvasAttributeTypes || {});
839
878
  var CanvasDataManager = class {
840
879
  static _selectedMotifIds = /* @__PURE__ */ new Set();
841
- static _lockedMotifIds = [];
880
+ static _lockedMotifIds = /* @__PURE__ */ new Set();
842
881
  static _hardLockedMotifIds = [];
843
882
  static _scoreRMSD = [];
844
883
  static _kabschRMSD = [];
@@ -1095,28 +1134,27 @@ function Canvas({
1095
1134
  }) {
1096
1135
  const canvasRef = useRef(null);
1097
1136
  const scene = useRef(null);
1098
- const motifs = [];
1137
+ const motifs = useRef([]);
1099
1138
  let hardLockedMotifIds = [];
1100
1139
  motifProps.forEach((motifProp) => {
1101
- motifs.push(motifProp.motif);
1102
1140
  if (motifProp.locked) hardLockedMotifIds.push(motifProp.motif.uuid);
1103
1141
  });
1104
1142
  CanvasDataManager.setHardLockedMotifIds(hardLockedMotifIds);
1105
1143
  const selectedMotifMeshState = useRef(/* @__PURE__ */ new Set());
1106
- const lockedMotifIdState = useRef([]);
1144
+ const lockedMotifIdState = useRef(/* @__PURE__ */ new Set());
1107
1145
  const [cursorStyle, setCursorStyle] = useState("auto");
1108
1146
  const [selectedMotifIds, setSelectedmotifIds] = useState(/* @__PURE__ */ new Set());
1109
1147
  const [scoreRMSD, setScoreRMSD] = useState([]);
1110
1148
  const [kabschRMSD, setKabschRMSD] = useState([]);
1111
- const [lockedMotifIds, setLockedMotifIds] = useState([]);
1149
+ const [lockedMotifIds, setLockedMotifIds] = useState(/* @__PURE__ */ new Set());
1112
1150
  const addMotif = (motif) => {
1113
1151
  if (selectedMotifMeshState.current.has(motif)) {
1114
1152
  return;
1115
1153
  }
1116
1154
  const newSet = /* @__PURE__ */ new Set();
1117
- for (let i = 0; i < motifs.length; i += 1) {
1118
- if (selectedMotifMeshState.current.has(motifs[i]) || motifs[i].uuid === motif.uuid) {
1119
- newSet.add(motifs[i].uuid);
1155
+ for (let i = 0; i < motifs.current.length; i += 1) {
1156
+ if (selectedMotifMeshState.current.has(motifs.current[i]) || motifs.current[i].uuid === motif.uuid) {
1157
+ newSet.add(motifs.current[i].uuid);
1120
1158
  }
1121
1159
  }
1122
1160
  setSelectedmotifIds(newSet);
@@ -1134,7 +1172,7 @@ function Canvas({
1134
1172
  });
1135
1173
  };
1136
1174
  function updateGlow() {
1137
- motifs.forEach((motif) => {
1175
+ motifs.current.forEach((motif) => {
1138
1176
  motif.children.forEach((residue) => {
1139
1177
  residue.children.forEach((childMesh) => {
1140
1178
  if (selectedMotifIds.has(motif.uuid)) {
@@ -1146,12 +1184,27 @@ function Canvas({
1146
1184
  });
1147
1185
  });
1148
1186
  }
1187
+ function motifOutOfBounds(motif, deltaX, deltaY) {
1188
+ if (!canvasRef.current) {
1189
+ return false;
1190
+ }
1191
+ const canvasWidth = canvasRef.current.width;
1192
+ const canvasHeight = canvasRef.current.height;
1193
+ const left = -canvasWidth / 2;
1194
+ const right = canvasWidth / 2;
1195
+ const top = canvasHeight / 2;
1196
+ const bottom = -canvasHeight / 2;
1197
+ const newX = motif.position.x + deltaX;
1198
+ const newY = motif.position.y + deltaY;
1199
+ if (newX <= left || newX >= right || newY <= bottom || newY >= top) return true;
1200
+ return false;
1201
+ }
1149
1202
  function onSelectMotif(event) {
1150
1203
  if (event.type !== Events.EventType.OBJECT_SELECTED) {
1151
1204
  return;
1152
1205
  }
1153
1206
  const { motif } = event;
1154
- if (!motif || selectedMotifMeshState.current.has(motif) || lockedMotifIdState.current.includes(motif.uuid) || hardLockedMotifIds.includes(motif.uuid)) {
1207
+ if (!motif || selectedMotifMeshState.current.has(motif)) {
1155
1208
  return;
1156
1209
  }
1157
1210
  if (event.multiSelect && motif) {
@@ -1183,7 +1236,7 @@ function Canvas({
1183
1236
  const deltaX = rawDeltaX / renderWidth * canvasRef.current.width;
1184
1237
  const deltaY = rawDeltaY / renderHeight * canvasRef.current.height;
1185
1238
  selectedMotifMeshState.current.forEach((element) => {
1186
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1239
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid) && !motifOutOfBounds(element, -deltaX, -deltaY)) {
1187
1240
  element.translate(-deltaX, -deltaY, 0);
1188
1241
  }
1189
1242
  });
@@ -1196,11 +1249,11 @@ function Canvas({
1196
1249
  if (scene.current) {
1197
1250
  const angle = directionVec.length() / scene.current.renderWidth * (3 * Math.PI);
1198
1251
  selectedMotifMeshState.current.forEach((element) => {
1199
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1252
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1200
1253
  element.rotate(axisVec, angle);
1201
1254
  }
1202
1255
  });
1203
- if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1256
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs.current));
1204
1257
  }
1205
1258
  }
1206
1259
  }
@@ -1213,7 +1266,7 @@ function Canvas({
1213
1266
  const zoomSpeed = 0.1;
1214
1267
  const zoomDirection = event.originalEvent.deltaY > 0 ? -1 : 1;
1215
1268
  selectedMotifMeshState.current.forEach((element) => {
1216
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid) && !(element.scale <= 1 && zoomDirection === -1) && !(element.scale >= 30 && zoomDirection === 1)) {
1269
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid) && !(element.scale <= 1 && zoomDirection === -1) && !(element.scale >= 30 && zoomDirection === 1)) {
1217
1270
  const scaleFactor = 1 + zoomDirection * zoomSpeed;
1218
1271
  element.multiplyScalar(scaleFactor);
1219
1272
  }
@@ -1241,19 +1294,19 @@ function Canvas({
1241
1294
  }
1242
1295
  const angle = event.rotationAxis.length() / 500 * (6 * Math.PI);
1243
1296
  selectedMotifMeshState.current.forEach((element) => {
1244
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1297
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1245
1298
  element.rotate(event.rotationAxis, angle);
1246
1299
  }
1247
1300
  });
1248
- if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1301
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs.current));
1249
1302
  }
1250
1303
  function onKeyboardTranslate(event) {
1251
1304
  if (event.translationDirection.equals(Vec3.Zero)) {
1252
1305
  return;
1253
1306
  }
1254
- event.translationDirection.multiplyScalar(0.5);
1307
+ event.translationDirection.multiplyScalar(4.5);
1255
1308
  selectedMotifMeshState.current.forEach((element) => {
1256
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1309
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid) && !motifOutOfBounds(element, event.translationDirection.x, event.translationDirection.y)) {
1257
1310
  element.translate(
1258
1311
  event.translationDirection.x,
1259
1312
  event.translationDirection.y,
@@ -1266,10 +1319,10 @@ function Canvas({
1266
1319
  if (!event.rotationAxis.equals(Vec3.Zero) || !event.translationDirection.equals(Vec3.Zero)) {
1267
1320
  return;
1268
1321
  }
1269
- if (!/^[1-9]$/.test(event.key) || Number(event.key) > motifs.length) {
1322
+ if (!/^[1-9]$/.test(event.key) || Number(event.key) > motifs.current.length) {
1270
1323
  return;
1271
1324
  }
1272
- const motif = motifs[Number(event.key) - 1];
1325
+ const motif = motifs.current[Number(event.key) - 1];
1273
1326
  if (selectedMotifMeshState.current.has(motif)) {
1274
1327
  removeMotif(motif);
1275
1328
  } else {
@@ -1292,8 +1345,8 @@ function Canvas({
1292
1345
  return positions;
1293
1346
  };
1294
1347
  const updateMotifs = () => {
1295
- const positions = calculatePositions(motifs.length);
1296
- motifs.forEach((motifMesh, index) => {
1348
+ const positions = calculatePositions(motifs.current.length);
1349
+ motifs.current.forEach((motifMesh, index) => {
1297
1350
  if (!scene.current?.children.has(motifMesh.uuid)) return;
1298
1351
  if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
1299
1352
  motifMesh.setPosition(positions[index].x, positions[index].y, positions[index].z);
@@ -1303,9 +1356,6 @@ function Canvas({
1303
1356
  motifMesh.setScale(scale);
1304
1357
  });
1305
1358
  };
1306
- useEffect(() => {
1307
- updateMotifs();
1308
- }, [motifProps]);
1309
1359
  useEffect(() => {
1310
1360
  const unsubscribe = CanvasDataManager.subscribe("selectedMotifs" /* SELECTED_MOTIFS */, () => {
1311
1361
  setSelectedmotifIds(CanvasDataManager.selectedMotifIds);
@@ -1317,13 +1367,13 @@ function Canvas({
1317
1367
  CanvasDataManager.setSelectedMotifIds(selectedMotifIds);
1318
1368
  }
1319
1369
  selectedMotifMeshState.current.clear();
1320
- motifs.forEach((motif) => {
1370
+ motifs.current.forEach((motif) => {
1321
1371
  if (selectedMotifIds.has(motif.uuid)) {
1322
1372
  selectedMotifMeshState.current.add(motif);
1323
1373
  }
1324
1374
  });
1325
1375
  updateGlow();
1326
- if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1376
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs.current));
1327
1377
  }, [selectedMotifIds]);
1328
1378
  useEffect(() => {
1329
1379
  const unsubscribe = CanvasDataManager.subscribe("lockedMotifIds" /* LOCKED_MOTIF_IDS */, () => {
@@ -1364,8 +1414,8 @@ function Canvas({
1364
1414
  if (!showRMSD) return;
1365
1415
  if (CanvasDataManager.kabschRMSD !== kabschRMSD) {
1366
1416
  CanvasDataManager.setKabschRMSD(kabschRMSD);
1367
- } else if (CanvasDataManager.kabschRMSD.length !== motifs.length) {
1368
- CanvasDataManager.setKabschRMSD(calculateAllKabschRMSD(motifs));
1417
+ } else if (CanvasDataManager.kabschRMSD.length !== motifs.current.length) {
1418
+ CanvasDataManager.setKabschRMSD(calculateAllKabschRMSD(motifs.current));
1369
1419
  }
1370
1420
  }, [kabschRMSD]);
1371
1421
  useEffect(() => {
@@ -1384,84 +1434,91 @@ function Canvas({
1384
1434
  rendererSizeIsWindow ? window.innerWidth : rendererWidth,
1385
1435
  rendererSizeIsWindow ? window.innerHeight : rendererHeight
1386
1436
  );
1387
- }
1388
- if (motifs.length > 0) {
1389
- if (showRMSD) setKabschRMSD(calculateAllKabschRMSD(motifs));
1390
- if (scene.current.children.size !== motifs.length) {
1391
- const positions = calculatePositions(motifs.length);
1392
- motifs.forEach((motifMesh, index) => {
1393
- scene.current?.add(motifMesh);
1394
- if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
1395
- motifMesh.setPosition(positions[index].x, positions[index].y, positions[index].z);
1396
- if (motifProps[index].rotation) motifMesh.setQuaternion(motifProps[index].rotation);
1397
- let scale = canvasRef.current.width / 250;
1398
- if (motifProps[index].scale) scale = motifProps[index].scale;
1399
- motifMesh.multiplyScalar(scale);
1437
+ const eventManager = scene.current?.eventManager;
1438
+ eventManager.on(Events.EventType.OBJECT_SELECTED, onSelectMotif);
1439
+ eventManager.on(Events.EventType.OBJECT_DESELECTED, onDeselectMotif);
1440
+ eventManager.on(Events.EventType.POINTER_MOVE, onMouseMove);
1441
+ eventManager.on(Events.EventType.POINTER_WHEEL, onMouseScroll);
1442
+ eventManager.on(Events.EventType.POINTER_DOWN, onMouseDown);
1443
+ eventManager.on(Events.EventType.POINTER_UP, onMouseUp);
1444
+ eventManager.on(Events.EventType.KEY_DOWN, onKeyboardRotate);
1445
+ eventManager.on(Events.EventType.KEY_DOWN, onKeyboardTranslate);
1446
+ eventManager.on(Events.EventType.KEY_DOWN, onKeyboardSelect);
1447
+ if (customEventProps) {
1448
+ customEventProps.forEach((customEventProp) => {
1449
+ switch (customEventProp.eventType) {
1450
+ // Handle Pointer Events
1451
+ case Events.EventType.POINTER_DOWN:
1452
+ case Events.EventType.POINTER_UP:
1453
+ case Events.EventType.POINTER_MOVE:
1454
+ case Events.EventType.POINTER_WHEEL:
1455
+ case Events.EventType.TOUCH_END:
1456
+ case Events.EventType.TOUCH_MOVE:
1457
+ case Events.EventType.TOUCH_START:
1458
+ eventManager.on(
1459
+ customEventProp.eventType,
1460
+ customEventProp.callback
1461
+ );
1462
+ break;
1463
+ // Handle Keyboard Events
1464
+ case Events.EventType.KEY_DOWN:
1465
+ case Events.EventType.KEY_UP:
1466
+ eventManager.on(
1467
+ customEventProp.eventType,
1468
+ customEventProp.callback
1469
+ );
1470
+ break;
1471
+ // Handle Pinch Events
1472
+ case Events.EventType.PINCH:
1473
+ case Events.EventType.PINCH_END:
1474
+ case Events.EventType.PINCH_START:
1475
+ eventManager.on(
1476
+ customEventProp.eventType,
1477
+ customEventProp.callback
1478
+ );
1479
+ break;
1480
+ // Handle Selection Events
1481
+ case Events.EventType.OBJECT_SELECTED:
1482
+ case Events.EventType.OBJECT_DESELECTED:
1483
+ eventManager.on(
1484
+ customEventProp.eventType,
1485
+ customEventProp.callback
1486
+ );
1487
+ break;
1488
+ // Handle Events
1489
+ default:
1490
+ eventManager.on(
1491
+ customEventProp.eventType,
1492
+ customEventProp.callback
1493
+ );
1494
+ break;
1495
+ }
1400
1496
  });
1401
- const eventManager = scene.current?.eventManager;
1402
- eventManager.on(Events.EventType.OBJECT_SELECTED, onSelectMotif);
1403
- eventManager.on(Events.EventType.OBJECT_DESELECTED, onDeselectMotif);
1404
- eventManager.on(Events.EventType.POINTER_MOVE, onMouseMove);
1405
- eventManager.on(Events.EventType.POINTER_WHEEL, onMouseScroll);
1406
- eventManager.on(Events.EventType.POINTER_DOWN, onMouseDown);
1407
- eventManager.on(Events.EventType.POINTER_UP, onMouseUp);
1408
- eventManager.on(Events.EventType.KEY_DOWN, onKeyboardRotate);
1409
- eventManager.on(Events.EventType.KEY_DOWN, onKeyboardTranslate);
1410
- eventManager.on(Events.EventType.KEY_DOWN, onKeyboardSelect);
1411
- if (customEventProps) {
1412
- customEventProps.forEach((customEventProp) => {
1413
- switch (customEventProp.eventType) {
1414
- // Handle Pointer Events
1415
- case Events.EventType.POINTER_DOWN:
1416
- case Events.EventType.POINTER_UP:
1417
- case Events.EventType.POINTER_MOVE:
1418
- case Events.EventType.POINTER_WHEEL:
1419
- case Events.EventType.TOUCH_END:
1420
- case Events.EventType.TOUCH_MOVE:
1421
- case Events.EventType.TOUCH_START:
1422
- eventManager.on(
1423
- customEventProp.eventType,
1424
- customEventProp.callback
1425
- );
1426
- break;
1427
- // Handle Keyboard Events
1428
- case Events.EventType.KEY_DOWN:
1429
- case Events.EventType.KEY_UP:
1430
- eventManager.on(
1431
- customEventProp.eventType,
1432
- customEventProp.callback
1433
- );
1434
- break;
1435
- // Handle Pinch Events
1436
- case Events.EventType.PINCH:
1437
- case Events.EventType.PINCH_END:
1438
- case Events.EventType.PINCH_START:
1439
- eventManager.on(
1440
- customEventProp.eventType,
1441
- customEventProp.callback
1442
- );
1443
- break;
1444
- // Handle Selection Events
1445
- case Events.EventType.OBJECT_SELECTED:
1446
- case Events.EventType.OBJECT_DESELECTED:
1447
- eventManager.on(
1448
- customEventProp.eventType,
1449
- customEventProp.callback
1450
- );
1451
- break;
1452
- // Handle Events
1453
- default:
1454
- eventManager.on(
1455
- customEventProp.eventType,
1456
- customEventProp.callback
1457
- );
1458
- break;
1459
- }
1460
- });
1461
- }
1462
1497
  }
1498
+ scene.current.start();
1499
+ }
1500
+ }, []);
1501
+ useEffect(() => {
1502
+ if (!scene.current || !canvasRef.current) return;
1503
+ motifs.current.length = 0;
1504
+ motifProps.forEach((motifProp) => {
1505
+ motifs.current.push(motifProp.motif);
1506
+ });
1507
+ if (motifs.current.length > 0) {
1508
+ if (showRMSD) setKabschRMSD(calculateAllKabschRMSD(motifs.current));
1509
+ scene.current.removeAll();
1510
+ const positions = calculatePositions(motifs.current.length);
1511
+ motifs.current.forEach((motifMesh, index) => {
1512
+ scene.current?.add(motifMesh);
1513
+ if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
1514
+ motifMesh.setPosition(positions[index].x, positions[index].y, positions[index].z);
1515
+ if (motifProps[index].rotation) motifMesh.setQuaternion(motifProps[index].rotation);
1516
+ let scale = canvasRef.current.width / 250;
1517
+ if (motifProps[index].scale) scale = motifProps[index].scale;
1518
+ motifMesh.setScale(scale);
1519
+ if (motifProps[index].locked) lockedMotifIdState.current.add(motifMesh.uuid);
1520
+ });
1463
1521
  }
1464
- scene.current?.start();
1465
1522
  }, [rendererWidth, rendererHeight, rendererSizeIsWindow, motifProps]);
1466
1523
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("canvas", { ref: canvasRef }));
1467
1524
  }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@judah-silva/rnacanvas",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "A 3D Canvas for displaying and interacting with custom RNA models. Powered by BabylonJS.",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/RNAStructureAnalysisLab/RNACanvas"
9
+ "url": "git+https://github.com/RNAStructureAnalysisLab/RNACanvas.git"
10
10
  },
11
11
  "module": "./dist/index.mjs",
12
12
  "scripts": {