@judah-silva/rnacanvas 0.0.8 → 0.0.9

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/README.md CHANGED
@@ -129,3 +129,7 @@ const props: MotifProps = {
129
129
  locked: false,
130
130
  };
131
131
  ```
132
+
133
+
134
+
135
+ test
package/dist/index.d.mts CHANGED
@@ -306,6 +306,7 @@ declare class RenderScene {
306
306
  dispose(): void;
307
307
  add(motif: Motif): void;
308
308
  remove(motif: Motif): void;
309
+ removeAll(): void;
309
310
  setBackgroundColor(hexColor: string): void;
310
311
  private _reattachToScene;
311
312
  private _handleResize;
@@ -469,8 +470,8 @@ declare class CanvasDataManager {
469
470
  private static _listeners;
470
471
  static get selectedMotifIds(): Set<string>;
471
472
  static setSelectedMotifIds(selectedMotifIds: Set<string>): void;
472
- static get lockedMotifIds(): string[];
473
- static setLockedMotifIds(lockedMotifIds: string[]): void;
473
+ static get lockedMotifIds(): Set<string>;
474
+ static setLockedMotifIds(lockedMotifIds: Set<string>): void;
474
475
  static get hardLockedMotifIds(): string[];
475
476
  static setHardLockedMotifIds(hardLockedMotifIds: string[]): void;
476
477
  static get scoreRMSD(): ScoreInfo[][];
package/dist/index.d.ts CHANGED
@@ -306,6 +306,7 @@ declare class RenderScene {
306
306
  dispose(): void;
307
307
  add(motif: Motif): void;
308
308
  remove(motif: Motif): void;
309
+ removeAll(): void;
309
310
  setBackgroundColor(hexColor: string): void;
310
311
  private _reattachToScene;
311
312
  private _handleResize;
@@ -469,8 +470,8 @@ declare class CanvasDataManager {
469
470
  private static _listeners;
470
471
  static get selectedMotifIds(): Set<string>;
471
472
  static setSelectedMotifIds(selectedMotifIds: Set<string>): void;
472
- static get lockedMotifIds(): string[];
473
- static setLockedMotifIds(lockedMotifIds: string[]): void;
473
+ static get lockedMotifIds(): Set<string>;
474
+ static setLockedMotifIds(lockedMotifIds: Set<string>): void;
474
475
  static get hardLockedMotifIds(): string[];
475
476
  static setHardLockedMotifIds(hardLockedMotifIds: string[]): void;
476
477
  static get scoreRMSD(): ScoreInfo[][];
package/dist/index.js CHANGED
@@ -378,6 +378,7 @@ var MeshObject = class {
378
378
  const color3 = import_core7.Color3.FromHexString(`#${color.replace(/^0x/, "")}`);
379
379
  mat.diffuseColor = color3;
380
380
  mat.specularColor = color3;
381
+ mat.backFaceCulling = false;
381
382
  this._mesh.material = mat;
382
383
  }
383
384
  setNewMesh(mesh) {
@@ -603,9 +604,19 @@ var EventManager = class {
603
604
  canceled: false,
604
605
  timestamp: performance.now()
605
606
  };
606
- if (keyboardInfo.type === import_core8.KeyboardEventTypes.KEYDOWN) {
607
+ if (keyboardInfo.type === import_core8.KeyboardEventTypes.KEYDOWN && /^[wasdqeWASD]$/.test(event.key)) {
608
+ if (event.key.charCodeAt(0) >= 97) {
609
+ this._activeKeys.delete(event.key.toUpperCase());
610
+ } else {
611
+ this._activeKeys.delete(event.key.toLowerCase());
612
+ }
607
613
  this._activeKeys.add(event.key);
608
614
  } else if (keyboardInfo.type === import_core8.KeyboardEventTypes.KEYUP) {
615
+ if (event.key.charCodeAt(0) >= 97) {
616
+ this._activeKeys.delete(event.key.toUpperCase());
617
+ } else {
618
+ this._activeKeys.delete(event.key.toLowerCase());
619
+ }
609
620
  this._activeKeys.delete(event.key);
610
621
  }
611
622
  if (this._activeKeys.has("w")) keyboardEvent.rotationAxis.add(new Vec3(-1, 0, 0));
@@ -745,7 +756,15 @@ var RenderScene = class {
745
756
  return;
746
757
  }
747
758
  this._children.delete(motif.uuid);
748
- this._scene.removeTransformNode(motif.node);
759
+ motif.node.dispose(false, true);
760
+ }
761
+ removeAll() {
762
+ this._scene.meshes.slice().forEach((mesh) => {
763
+ if (mesh && this._scene.getMeshById(mesh.id)) {
764
+ this._scene.removeMesh(mesh);
765
+ }
766
+ });
767
+ this._children.clear();
749
768
  }
750
769
  setBackgroundColor(hexColor) {
751
770
  this._scene.clearColor = import_core9.Color4.FromHexString(`${hexColor.replace(/^0x/, "")}`);
@@ -762,7 +781,7 @@ var RenderScene = class {
762
781
  geo?.applyToMesh(mesh);
763
782
  mesh.material = mat;
764
783
  currObj.setNewMesh(mesh);
765
- node.dispose();
784
+ node.dispose(false, true);
766
785
  }
767
786
  if (!(currObj instanceof MeshObject)) {
768
787
  currObj.children.forEach((childObj) => {
@@ -895,7 +914,7 @@ var CanvasAttributeTypes = /* @__PURE__ */ ((CanvasAttributeTypes2) => {
895
914
  })(CanvasAttributeTypes || {});
896
915
  var CanvasDataManager = class {
897
916
  static _selectedMotifIds = /* @__PURE__ */ new Set();
898
- static _lockedMotifIds = [];
917
+ static _lockedMotifIds = /* @__PURE__ */ new Set();
899
918
  static _hardLockedMotifIds = [];
900
919
  static _scoreRMSD = [];
901
920
  static _kabschRMSD = [];
@@ -1152,28 +1171,27 @@ function Canvas({
1152
1171
  }) {
1153
1172
  const canvasRef = (0, import_react.useRef)(null);
1154
1173
  const scene = (0, import_react.useRef)(null);
1155
- const motifs = [];
1174
+ const motifs = (0, import_react.useRef)([]);
1156
1175
  let hardLockedMotifIds = [];
1157
1176
  motifProps.forEach((motifProp) => {
1158
- motifs.push(motifProp.motif);
1159
1177
  if (motifProp.locked) hardLockedMotifIds.push(motifProp.motif.uuid);
1160
1178
  });
1161
1179
  CanvasDataManager.setHardLockedMotifIds(hardLockedMotifIds);
1162
1180
  const selectedMotifMeshState = (0, import_react.useRef)(/* @__PURE__ */ new Set());
1163
- const lockedMotifIdState = (0, import_react.useRef)([]);
1181
+ const lockedMotifIdState = (0, import_react.useRef)(/* @__PURE__ */ new Set());
1164
1182
  const [cursorStyle, setCursorStyle] = (0, import_react.useState)("auto");
1165
1183
  const [selectedMotifIds, setSelectedmotifIds] = (0, import_react.useState)(/* @__PURE__ */ new Set());
1166
1184
  const [scoreRMSD, setScoreRMSD] = (0, import_react.useState)([]);
1167
1185
  const [kabschRMSD, setKabschRMSD] = (0, import_react.useState)([]);
1168
- const [lockedMotifIds, setLockedMotifIds] = (0, import_react.useState)([]);
1186
+ const [lockedMotifIds, setLockedMotifIds] = (0, import_react.useState)(/* @__PURE__ */ new Set());
1169
1187
  const addMotif = (motif) => {
1170
1188
  if (selectedMotifMeshState.current.has(motif)) {
1171
1189
  return;
1172
1190
  }
1173
1191
  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);
1192
+ for (let i = 0; i < motifs.current.length; i += 1) {
1193
+ if (selectedMotifMeshState.current.has(motifs.current[i]) || motifs.current[i].uuid === motif.uuid) {
1194
+ newSet.add(motifs.current[i].uuid);
1177
1195
  }
1178
1196
  }
1179
1197
  setSelectedmotifIds(newSet);
@@ -1191,7 +1209,7 @@ function Canvas({
1191
1209
  });
1192
1210
  };
1193
1211
  function updateGlow() {
1194
- motifs.forEach((motif) => {
1212
+ motifs.current.forEach((motif) => {
1195
1213
  motif.children.forEach((residue) => {
1196
1214
  residue.children.forEach((childMesh) => {
1197
1215
  if (selectedMotifIds.has(motif.uuid)) {
@@ -1208,7 +1226,7 @@ function Canvas({
1208
1226
  return;
1209
1227
  }
1210
1228
  const { motif } = event;
1211
- if (!motif || selectedMotifMeshState.current.has(motif) || lockedMotifIdState.current.includes(motif.uuid) || hardLockedMotifIds.includes(motif.uuid)) {
1229
+ if (!motif || selectedMotifMeshState.current.has(motif)) {
1212
1230
  return;
1213
1231
  }
1214
1232
  if (event.multiSelect && motif) {
@@ -1240,7 +1258,7 @@ function Canvas({
1240
1258
  const deltaX = rawDeltaX / renderWidth * canvasRef.current.width;
1241
1259
  const deltaY = rawDeltaY / renderHeight * canvasRef.current.height;
1242
1260
  selectedMotifMeshState.current.forEach((element) => {
1243
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1261
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1244
1262
  element.translate(-deltaX, -deltaY, 0);
1245
1263
  }
1246
1264
  });
@@ -1253,11 +1271,11 @@ function Canvas({
1253
1271
  if (scene.current) {
1254
1272
  const angle = directionVec.length() / scene.current.renderWidth * (3 * Math.PI);
1255
1273
  selectedMotifMeshState.current.forEach((element) => {
1256
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1274
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1257
1275
  element.rotate(axisVec, angle);
1258
1276
  }
1259
1277
  });
1260
- if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1278
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs.current));
1261
1279
  }
1262
1280
  }
1263
1281
  }
@@ -1270,7 +1288,7 @@ function Canvas({
1270
1288
  const zoomSpeed = 0.1;
1271
1289
  const zoomDirection = event.originalEvent.deltaY > 0 ? -1 : 1;
1272
1290
  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)) {
1291
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid) && !(element.scale <= 1 && zoomDirection === -1) && !(element.scale >= 30 && zoomDirection === 1)) {
1274
1292
  const scaleFactor = 1 + zoomDirection * zoomSpeed;
1275
1293
  element.multiplyScalar(scaleFactor);
1276
1294
  }
@@ -1298,19 +1316,19 @@ function Canvas({
1298
1316
  }
1299
1317
  const angle = event.rotationAxis.length() / 500 * (6 * Math.PI);
1300
1318
  selectedMotifMeshState.current.forEach((element) => {
1301
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1319
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1302
1320
  element.rotate(event.rotationAxis, angle);
1303
1321
  }
1304
1322
  });
1305
- if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1323
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs.current));
1306
1324
  }
1307
1325
  function onKeyboardTranslate(event) {
1308
1326
  if (event.translationDirection.equals(Vec3.Zero)) {
1309
1327
  return;
1310
1328
  }
1311
- event.translationDirection.multiplyScalar(0.5);
1329
+ event.translationDirection.multiplyScalar(4.5);
1312
1330
  selectedMotifMeshState.current.forEach((element) => {
1313
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1331
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1314
1332
  element.translate(
1315
1333
  event.translationDirection.x,
1316
1334
  event.translationDirection.y,
@@ -1323,10 +1341,10 @@ function Canvas({
1323
1341
  if (!event.rotationAxis.equals(Vec3.Zero) || !event.translationDirection.equals(Vec3.Zero)) {
1324
1342
  return;
1325
1343
  }
1326
- if (!/^[1-9]$/.test(event.key) || Number(event.key) > motifs.length) {
1344
+ if (!/^[1-9]$/.test(event.key) || Number(event.key) > motifs.current.length) {
1327
1345
  return;
1328
1346
  }
1329
- const motif = motifs[Number(event.key) - 1];
1347
+ const motif = motifs.current[Number(event.key) - 1];
1330
1348
  if (selectedMotifMeshState.current.has(motif)) {
1331
1349
  removeMotif(motif);
1332
1350
  } else {
@@ -1349,8 +1367,8 @@ function Canvas({
1349
1367
  return positions;
1350
1368
  };
1351
1369
  const updateMotifs = () => {
1352
- const positions = calculatePositions(motifs.length);
1353
- motifs.forEach((motifMesh, index) => {
1370
+ const positions = calculatePositions(motifs.current.length);
1371
+ motifs.current.forEach((motifMesh, index) => {
1354
1372
  if (!scene.current?.children.has(motifMesh.uuid)) return;
1355
1373
  if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
1356
1374
  motifMesh.setPosition(positions[index].x, positions[index].y, positions[index].z);
@@ -1360,9 +1378,6 @@ function Canvas({
1360
1378
  motifMesh.setScale(scale);
1361
1379
  });
1362
1380
  };
1363
- (0, import_react.useEffect)(() => {
1364
- updateMotifs();
1365
- }, [motifProps]);
1366
1381
  (0, import_react.useEffect)(() => {
1367
1382
  const unsubscribe = CanvasDataManager.subscribe("selectedMotifs" /* SELECTED_MOTIFS */, () => {
1368
1383
  setSelectedmotifIds(CanvasDataManager.selectedMotifIds);
@@ -1374,13 +1389,13 @@ function Canvas({
1374
1389
  CanvasDataManager.setSelectedMotifIds(selectedMotifIds);
1375
1390
  }
1376
1391
  selectedMotifMeshState.current.clear();
1377
- motifs.forEach((motif) => {
1392
+ motifs.current.forEach((motif) => {
1378
1393
  if (selectedMotifIds.has(motif.uuid)) {
1379
1394
  selectedMotifMeshState.current.add(motif);
1380
1395
  }
1381
1396
  });
1382
1397
  updateGlow();
1383
- if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1398
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs.current));
1384
1399
  }, [selectedMotifIds]);
1385
1400
  (0, import_react.useEffect)(() => {
1386
1401
  const unsubscribe = CanvasDataManager.subscribe("lockedMotifIds" /* LOCKED_MOTIF_IDS */, () => {
@@ -1421,8 +1436,8 @@ function Canvas({
1421
1436
  if (!showRMSD) return;
1422
1437
  if (CanvasDataManager.kabschRMSD !== kabschRMSD) {
1423
1438
  CanvasDataManager.setKabschRMSD(kabschRMSD);
1424
- } else if (CanvasDataManager.kabschRMSD.length !== motifs.length) {
1425
- CanvasDataManager.setKabschRMSD(calculateAllKabschRMSD(motifs));
1439
+ } else if (CanvasDataManager.kabschRMSD.length !== motifs.current.length) {
1440
+ CanvasDataManager.setKabschRMSD(calculateAllKabschRMSD(motifs.current));
1426
1441
  }
1427
1442
  }, [kabschRMSD]);
1428
1443
  (0, import_react.useEffect)(() => {
@@ -1441,84 +1456,91 @@ function Canvas({
1441
1456
  rendererSizeIsWindow ? window.innerWidth : rendererWidth,
1442
1457
  rendererSizeIsWindow ? window.innerHeight : rendererHeight
1443
1458
  );
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);
1459
+ const eventManager = scene.current?.eventManager;
1460
+ eventManager.on(Events.EventType.OBJECT_SELECTED, onSelectMotif);
1461
+ eventManager.on(Events.EventType.OBJECT_DESELECTED, onDeselectMotif);
1462
+ eventManager.on(Events.EventType.POINTER_MOVE, onMouseMove);
1463
+ eventManager.on(Events.EventType.POINTER_WHEEL, onMouseScroll);
1464
+ eventManager.on(Events.EventType.POINTER_DOWN, onMouseDown);
1465
+ eventManager.on(Events.EventType.POINTER_UP, onMouseUp);
1466
+ eventManager.on(Events.EventType.KEY_DOWN, onKeyboardRotate);
1467
+ eventManager.on(Events.EventType.KEY_DOWN, onKeyboardTranslate);
1468
+ eventManager.on(Events.EventType.KEY_DOWN, onKeyboardSelect);
1469
+ if (customEventProps) {
1470
+ customEventProps.forEach((customEventProp) => {
1471
+ switch (customEventProp.eventType) {
1472
+ // Handle Pointer Events
1473
+ case Events.EventType.POINTER_DOWN:
1474
+ case Events.EventType.POINTER_UP:
1475
+ case Events.EventType.POINTER_MOVE:
1476
+ case Events.EventType.POINTER_WHEEL:
1477
+ case Events.EventType.TOUCH_END:
1478
+ case Events.EventType.TOUCH_MOVE:
1479
+ case Events.EventType.TOUCH_START:
1480
+ eventManager.on(
1481
+ customEventProp.eventType,
1482
+ customEventProp.callback
1483
+ );
1484
+ break;
1485
+ // Handle Keyboard Events
1486
+ case Events.EventType.KEY_DOWN:
1487
+ case Events.EventType.KEY_UP:
1488
+ eventManager.on(
1489
+ customEventProp.eventType,
1490
+ customEventProp.callback
1491
+ );
1492
+ break;
1493
+ // Handle Pinch Events
1494
+ case Events.EventType.PINCH:
1495
+ case Events.EventType.PINCH_END:
1496
+ case Events.EventType.PINCH_START:
1497
+ eventManager.on(
1498
+ customEventProp.eventType,
1499
+ customEventProp.callback
1500
+ );
1501
+ break;
1502
+ // Handle Selection Events
1503
+ case Events.EventType.OBJECT_SELECTED:
1504
+ case Events.EventType.OBJECT_DESELECTED:
1505
+ eventManager.on(
1506
+ customEventProp.eventType,
1507
+ customEventProp.callback
1508
+ );
1509
+ break;
1510
+ // Handle Events
1511
+ default:
1512
+ eventManager.on(
1513
+ customEventProp.eventType,
1514
+ customEventProp.callback
1515
+ );
1516
+ break;
1517
+ }
1457
1518
  });
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
1519
  }
1520
+ scene.current.start();
1521
+ }
1522
+ }, []);
1523
+ (0, import_react.useEffect)(() => {
1524
+ if (!scene.current || !canvasRef.current) return;
1525
+ motifs.current.length = 0;
1526
+ motifProps.forEach((motifProp) => {
1527
+ motifs.current.push(motifProp.motif);
1528
+ });
1529
+ if (motifs.current.length > 0) {
1530
+ if (showRMSD) setKabschRMSD(calculateAllKabschRMSD(motifs.current));
1531
+ scene.current.removeAll();
1532
+ const positions = calculatePositions(motifs.current.length);
1533
+ motifs.current.forEach((motifMesh, index) => {
1534
+ scene.current?.add(motifMesh);
1535
+ if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
1536
+ motifMesh.setPosition(positions[index].x, positions[index].y, positions[index].z);
1537
+ if (motifProps[index].rotation) motifMesh.setQuaternion(motifProps[index].rotation);
1538
+ let scale = canvasRef.current.width / 250;
1539
+ if (motifProps[index].scale) scale = motifProps[index].scale;
1540
+ motifMesh.setScale(scale);
1541
+ if (motifProps[index].locked) lockedMotifIdState.current.add(motifMesh.uuid);
1542
+ });
1520
1543
  }
1521
- scene.current?.start();
1522
1544
  }, [rendererWidth, rendererHeight, rendererSizeIsWindow, motifProps]);
1523
1545
  return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("canvas", { ref: canvasRef }));
1524
1546
  }
package/dist/index.mjs CHANGED
@@ -321,6 +321,7 @@ var MeshObject = class {
321
321
  const color3 = Color3.FromHexString(`#${color.replace(/^0x/, "")}`);
322
322
  mat.diffuseColor = color3;
323
323
  mat.specularColor = color3;
324
+ mat.backFaceCulling = false;
324
325
  this._mesh.material = mat;
325
326
  }
326
327
  setNewMesh(mesh) {
@@ -546,9 +547,19 @@ var EventManager = class {
546
547
  canceled: false,
547
548
  timestamp: performance.now()
548
549
  };
549
- if (keyboardInfo.type === KeyboardEventTypes.KEYDOWN) {
550
+ if (keyboardInfo.type === KeyboardEventTypes.KEYDOWN && /^[wasdqeWASD]$/.test(event.key)) {
551
+ if (event.key.charCodeAt(0) >= 97) {
552
+ this._activeKeys.delete(event.key.toUpperCase());
553
+ } else {
554
+ this._activeKeys.delete(event.key.toLowerCase());
555
+ }
550
556
  this._activeKeys.add(event.key);
551
557
  } else if (keyboardInfo.type === KeyboardEventTypes.KEYUP) {
558
+ if (event.key.charCodeAt(0) >= 97) {
559
+ this._activeKeys.delete(event.key.toUpperCase());
560
+ } else {
561
+ this._activeKeys.delete(event.key.toLowerCase());
562
+ }
552
563
  this._activeKeys.delete(event.key);
553
564
  }
554
565
  if (this._activeKeys.has("w")) keyboardEvent.rotationAxis.add(new Vec3(-1, 0, 0));
@@ -688,7 +699,15 @@ var RenderScene = class {
688
699
  return;
689
700
  }
690
701
  this._children.delete(motif.uuid);
691
- this._scene.removeTransformNode(motif.node);
702
+ motif.node.dispose(false, true);
703
+ }
704
+ removeAll() {
705
+ this._scene.meshes.slice().forEach((mesh) => {
706
+ if (mesh && this._scene.getMeshById(mesh.id)) {
707
+ this._scene.removeMesh(mesh);
708
+ }
709
+ });
710
+ this._children.clear();
692
711
  }
693
712
  setBackgroundColor(hexColor) {
694
713
  this._scene.clearColor = Color4.FromHexString(`${hexColor.replace(/^0x/, "")}`);
@@ -705,7 +724,7 @@ var RenderScene = class {
705
724
  geo?.applyToMesh(mesh);
706
725
  mesh.material = mat;
707
726
  currObj.setNewMesh(mesh);
708
- node.dispose();
727
+ node.dispose(false, true);
709
728
  }
710
729
  if (!(currObj instanceof MeshObject)) {
711
730
  currObj.children.forEach((childObj) => {
@@ -838,7 +857,7 @@ var CanvasAttributeTypes = /* @__PURE__ */ ((CanvasAttributeTypes2) => {
838
857
  })(CanvasAttributeTypes || {});
839
858
  var CanvasDataManager = class {
840
859
  static _selectedMotifIds = /* @__PURE__ */ new Set();
841
- static _lockedMotifIds = [];
860
+ static _lockedMotifIds = /* @__PURE__ */ new Set();
842
861
  static _hardLockedMotifIds = [];
843
862
  static _scoreRMSD = [];
844
863
  static _kabschRMSD = [];
@@ -1095,28 +1114,27 @@ function Canvas({
1095
1114
  }) {
1096
1115
  const canvasRef = useRef(null);
1097
1116
  const scene = useRef(null);
1098
- const motifs = [];
1117
+ const motifs = useRef([]);
1099
1118
  let hardLockedMotifIds = [];
1100
1119
  motifProps.forEach((motifProp) => {
1101
- motifs.push(motifProp.motif);
1102
1120
  if (motifProp.locked) hardLockedMotifIds.push(motifProp.motif.uuid);
1103
1121
  });
1104
1122
  CanvasDataManager.setHardLockedMotifIds(hardLockedMotifIds);
1105
1123
  const selectedMotifMeshState = useRef(/* @__PURE__ */ new Set());
1106
- const lockedMotifIdState = useRef([]);
1124
+ const lockedMotifIdState = useRef(/* @__PURE__ */ new Set());
1107
1125
  const [cursorStyle, setCursorStyle] = useState("auto");
1108
1126
  const [selectedMotifIds, setSelectedmotifIds] = useState(/* @__PURE__ */ new Set());
1109
1127
  const [scoreRMSD, setScoreRMSD] = useState([]);
1110
1128
  const [kabschRMSD, setKabschRMSD] = useState([]);
1111
- const [lockedMotifIds, setLockedMotifIds] = useState([]);
1129
+ const [lockedMotifIds, setLockedMotifIds] = useState(/* @__PURE__ */ new Set());
1112
1130
  const addMotif = (motif) => {
1113
1131
  if (selectedMotifMeshState.current.has(motif)) {
1114
1132
  return;
1115
1133
  }
1116
1134
  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);
1135
+ for (let i = 0; i < motifs.current.length; i += 1) {
1136
+ if (selectedMotifMeshState.current.has(motifs.current[i]) || motifs.current[i].uuid === motif.uuid) {
1137
+ newSet.add(motifs.current[i].uuid);
1120
1138
  }
1121
1139
  }
1122
1140
  setSelectedmotifIds(newSet);
@@ -1134,7 +1152,7 @@ function Canvas({
1134
1152
  });
1135
1153
  };
1136
1154
  function updateGlow() {
1137
- motifs.forEach((motif) => {
1155
+ motifs.current.forEach((motif) => {
1138
1156
  motif.children.forEach((residue) => {
1139
1157
  residue.children.forEach((childMesh) => {
1140
1158
  if (selectedMotifIds.has(motif.uuid)) {
@@ -1151,7 +1169,7 @@ function Canvas({
1151
1169
  return;
1152
1170
  }
1153
1171
  const { motif } = event;
1154
- if (!motif || selectedMotifMeshState.current.has(motif) || lockedMotifIdState.current.includes(motif.uuid) || hardLockedMotifIds.includes(motif.uuid)) {
1172
+ if (!motif || selectedMotifMeshState.current.has(motif)) {
1155
1173
  return;
1156
1174
  }
1157
1175
  if (event.multiSelect && motif) {
@@ -1183,7 +1201,7 @@ function Canvas({
1183
1201
  const deltaX = rawDeltaX / renderWidth * canvasRef.current.width;
1184
1202
  const deltaY = rawDeltaY / renderHeight * canvasRef.current.height;
1185
1203
  selectedMotifMeshState.current.forEach((element) => {
1186
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1204
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1187
1205
  element.translate(-deltaX, -deltaY, 0);
1188
1206
  }
1189
1207
  });
@@ -1196,11 +1214,11 @@ function Canvas({
1196
1214
  if (scene.current) {
1197
1215
  const angle = directionVec.length() / scene.current.renderWidth * (3 * Math.PI);
1198
1216
  selectedMotifMeshState.current.forEach((element) => {
1199
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1217
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1200
1218
  element.rotate(axisVec, angle);
1201
1219
  }
1202
1220
  });
1203
- if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1221
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs.current));
1204
1222
  }
1205
1223
  }
1206
1224
  }
@@ -1213,7 +1231,7 @@ function Canvas({
1213
1231
  const zoomSpeed = 0.1;
1214
1232
  const zoomDirection = event.originalEvent.deltaY > 0 ? -1 : 1;
1215
1233
  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)) {
1234
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid) && !(element.scale <= 1 && zoomDirection === -1) && !(element.scale >= 30 && zoomDirection === 1)) {
1217
1235
  const scaleFactor = 1 + zoomDirection * zoomSpeed;
1218
1236
  element.multiplyScalar(scaleFactor);
1219
1237
  }
@@ -1241,19 +1259,19 @@ function Canvas({
1241
1259
  }
1242
1260
  const angle = event.rotationAxis.length() / 500 * (6 * Math.PI);
1243
1261
  selectedMotifMeshState.current.forEach((element) => {
1244
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1262
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1245
1263
  element.rotate(event.rotationAxis, angle);
1246
1264
  }
1247
1265
  });
1248
- if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1266
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs.current));
1249
1267
  }
1250
1268
  function onKeyboardTranslate(event) {
1251
1269
  if (event.translationDirection.equals(Vec3.Zero)) {
1252
1270
  return;
1253
1271
  }
1254
- event.translationDirection.multiplyScalar(0.5);
1272
+ event.translationDirection.multiplyScalar(4.5);
1255
1273
  selectedMotifMeshState.current.forEach((element) => {
1256
- if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1274
+ if (!lockedMotifIdState.current.has(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1257
1275
  element.translate(
1258
1276
  event.translationDirection.x,
1259
1277
  event.translationDirection.y,
@@ -1266,10 +1284,10 @@ function Canvas({
1266
1284
  if (!event.rotationAxis.equals(Vec3.Zero) || !event.translationDirection.equals(Vec3.Zero)) {
1267
1285
  return;
1268
1286
  }
1269
- if (!/^[1-9]$/.test(event.key) || Number(event.key) > motifs.length) {
1287
+ if (!/^[1-9]$/.test(event.key) || Number(event.key) > motifs.current.length) {
1270
1288
  return;
1271
1289
  }
1272
- const motif = motifs[Number(event.key) - 1];
1290
+ const motif = motifs.current[Number(event.key) - 1];
1273
1291
  if (selectedMotifMeshState.current.has(motif)) {
1274
1292
  removeMotif(motif);
1275
1293
  } else {
@@ -1292,8 +1310,8 @@ function Canvas({
1292
1310
  return positions;
1293
1311
  };
1294
1312
  const updateMotifs = () => {
1295
- const positions = calculatePositions(motifs.length);
1296
- motifs.forEach((motifMesh, index) => {
1313
+ const positions = calculatePositions(motifs.current.length);
1314
+ motifs.current.forEach((motifMesh, index) => {
1297
1315
  if (!scene.current?.children.has(motifMesh.uuid)) return;
1298
1316
  if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
1299
1317
  motifMesh.setPosition(positions[index].x, positions[index].y, positions[index].z);
@@ -1303,9 +1321,6 @@ function Canvas({
1303
1321
  motifMesh.setScale(scale);
1304
1322
  });
1305
1323
  };
1306
- useEffect(() => {
1307
- updateMotifs();
1308
- }, [motifProps]);
1309
1324
  useEffect(() => {
1310
1325
  const unsubscribe = CanvasDataManager.subscribe("selectedMotifs" /* SELECTED_MOTIFS */, () => {
1311
1326
  setSelectedmotifIds(CanvasDataManager.selectedMotifIds);
@@ -1317,13 +1332,13 @@ function Canvas({
1317
1332
  CanvasDataManager.setSelectedMotifIds(selectedMotifIds);
1318
1333
  }
1319
1334
  selectedMotifMeshState.current.clear();
1320
- motifs.forEach((motif) => {
1335
+ motifs.current.forEach((motif) => {
1321
1336
  if (selectedMotifIds.has(motif.uuid)) {
1322
1337
  selectedMotifMeshState.current.add(motif);
1323
1338
  }
1324
1339
  });
1325
1340
  updateGlow();
1326
- if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1341
+ if (showRMSD) setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs.current));
1327
1342
  }, [selectedMotifIds]);
1328
1343
  useEffect(() => {
1329
1344
  const unsubscribe = CanvasDataManager.subscribe("lockedMotifIds" /* LOCKED_MOTIF_IDS */, () => {
@@ -1364,8 +1379,8 @@ function Canvas({
1364
1379
  if (!showRMSD) return;
1365
1380
  if (CanvasDataManager.kabschRMSD !== kabschRMSD) {
1366
1381
  CanvasDataManager.setKabschRMSD(kabschRMSD);
1367
- } else if (CanvasDataManager.kabschRMSD.length !== motifs.length) {
1368
- CanvasDataManager.setKabschRMSD(calculateAllKabschRMSD(motifs));
1382
+ } else if (CanvasDataManager.kabschRMSD.length !== motifs.current.length) {
1383
+ CanvasDataManager.setKabschRMSD(calculateAllKabschRMSD(motifs.current));
1369
1384
  }
1370
1385
  }, [kabschRMSD]);
1371
1386
  useEffect(() => {
@@ -1384,84 +1399,91 @@ function Canvas({
1384
1399
  rendererSizeIsWindow ? window.innerWidth : rendererWidth,
1385
1400
  rendererSizeIsWindow ? window.innerHeight : rendererHeight
1386
1401
  );
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);
1402
+ const eventManager = scene.current?.eventManager;
1403
+ eventManager.on(Events.EventType.OBJECT_SELECTED, onSelectMotif);
1404
+ eventManager.on(Events.EventType.OBJECT_DESELECTED, onDeselectMotif);
1405
+ eventManager.on(Events.EventType.POINTER_MOVE, onMouseMove);
1406
+ eventManager.on(Events.EventType.POINTER_WHEEL, onMouseScroll);
1407
+ eventManager.on(Events.EventType.POINTER_DOWN, onMouseDown);
1408
+ eventManager.on(Events.EventType.POINTER_UP, onMouseUp);
1409
+ eventManager.on(Events.EventType.KEY_DOWN, onKeyboardRotate);
1410
+ eventManager.on(Events.EventType.KEY_DOWN, onKeyboardTranslate);
1411
+ eventManager.on(Events.EventType.KEY_DOWN, onKeyboardSelect);
1412
+ if (customEventProps) {
1413
+ customEventProps.forEach((customEventProp) => {
1414
+ switch (customEventProp.eventType) {
1415
+ // Handle Pointer Events
1416
+ case Events.EventType.POINTER_DOWN:
1417
+ case Events.EventType.POINTER_UP:
1418
+ case Events.EventType.POINTER_MOVE:
1419
+ case Events.EventType.POINTER_WHEEL:
1420
+ case Events.EventType.TOUCH_END:
1421
+ case Events.EventType.TOUCH_MOVE:
1422
+ case Events.EventType.TOUCH_START:
1423
+ eventManager.on(
1424
+ customEventProp.eventType,
1425
+ customEventProp.callback
1426
+ );
1427
+ break;
1428
+ // Handle Keyboard Events
1429
+ case Events.EventType.KEY_DOWN:
1430
+ case Events.EventType.KEY_UP:
1431
+ eventManager.on(
1432
+ customEventProp.eventType,
1433
+ customEventProp.callback
1434
+ );
1435
+ break;
1436
+ // Handle Pinch Events
1437
+ case Events.EventType.PINCH:
1438
+ case Events.EventType.PINCH_END:
1439
+ case Events.EventType.PINCH_START:
1440
+ eventManager.on(
1441
+ customEventProp.eventType,
1442
+ customEventProp.callback
1443
+ );
1444
+ break;
1445
+ // Handle Selection Events
1446
+ case Events.EventType.OBJECT_SELECTED:
1447
+ case Events.EventType.OBJECT_DESELECTED:
1448
+ eventManager.on(
1449
+ customEventProp.eventType,
1450
+ customEventProp.callback
1451
+ );
1452
+ break;
1453
+ // Handle Events
1454
+ default:
1455
+ eventManager.on(
1456
+ customEventProp.eventType,
1457
+ customEventProp.callback
1458
+ );
1459
+ break;
1460
+ }
1400
1461
  });
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
1462
  }
1463
+ scene.current.start();
1464
+ }
1465
+ }, []);
1466
+ useEffect(() => {
1467
+ if (!scene.current || !canvasRef.current) return;
1468
+ motifs.current.length = 0;
1469
+ motifProps.forEach((motifProp) => {
1470
+ motifs.current.push(motifProp.motif);
1471
+ });
1472
+ if (motifs.current.length > 0) {
1473
+ if (showRMSD) setKabschRMSD(calculateAllKabschRMSD(motifs.current));
1474
+ scene.current.removeAll();
1475
+ const positions = calculatePositions(motifs.current.length);
1476
+ motifs.current.forEach((motifMesh, index) => {
1477
+ scene.current?.add(motifMesh);
1478
+ if (motifProps[index].position) positions[index] = motifProps[index].position.clone();
1479
+ motifMesh.setPosition(positions[index].x, positions[index].y, positions[index].z);
1480
+ if (motifProps[index].rotation) motifMesh.setQuaternion(motifProps[index].rotation);
1481
+ let scale = canvasRef.current.width / 250;
1482
+ if (motifProps[index].scale) scale = motifProps[index].scale;
1483
+ motifMesh.setScale(scale);
1484
+ if (motifProps[index].locked) lockedMotifIdState.current.add(motifMesh.uuid);
1485
+ });
1463
1486
  }
1464
- scene.current?.start();
1465
1487
  }, [rendererWidth, rendererHeight, rendererSizeIsWindow, motifProps]);
1466
1488
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("canvas", { ref: canvasRef }));
1467
1489
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@judah-silva/rnacanvas",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
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",