@judah-silva/rnacanvas 0.0.1

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.js ADDED
@@ -0,0 +1,1466 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ Canvas: () => Canvas,
34
+ CanvasAttributeTypes: () => CanvasAttributeTypes,
35
+ CanvasDataManager: () => CanvasDataManager,
36
+ EventManager: () => EventManager,
37
+ Events: () => Events,
38
+ Group: () => Group,
39
+ Matrix4: () => Matrix4,
40
+ MeshObject: () => MeshObject,
41
+ Motif: () => Motif,
42
+ Quat: () => Quat,
43
+ RenderScene: () => RenderScene,
44
+ Residue: () => Residue,
45
+ Vec3: () => Vec3,
46
+ calculateAllKabschRMSD: () => calculateAllKabschRMSD,
47
+ calculateRMSD: () => calculateRMSD,
48
+ calculateRMSDSlide: () => calculateRMSDSlide,
49
+ getMotif: () => getMotif,
50
+ getPoints: () => getPoints,
51
+ getRMSD: () => getRMSD,
52
+ kabschSlidingWindow: () => kabschSlidingWindow,
53
+ rotateAllPoints: () => rotateAllPoints,
54
+ updateAllMotifs: () => updateAllMotifs
55
+ });
56
+ module.exports = __toCommonJS(index_exports);
57
+
58
+ // src/3D/Group.ts
59
+ var import_core = require("@babylonjs/core");
60
+ var Group = class {
61
+ _node;
62
+ _children;
63
+ constructor(name, scene) {
64
+ this._node = new import_core.TransformNode(name, scene);
65
+ this._children = /* @__PURE__ */ new Set();
66
+ this._node.rotationQuaternion = this._node.rotation.toQuaternion();
67
+ }
68
+ setParent(parent) {
69
+ this._node.parent = parent ? parent.node : null;
70
+ }
71
+ removeChild(child) {
72
+ if (!this._children.has(child)) {
73
+ return;
74
+ }
75
+ this._children.delete(child);
76
+ child.setParent(null);
77
+ }
78
+ get node() {
79
+ return this._node;
80
+ }
81
+ get children() {
82
+ return this._children;
83
+ }
84
+ };
85
+
86
+ // src/3D/Motif.ts
87
+ var import_core5 = require("@babylonjs/core");
88
+
89
+ // src/Math/Vec3.ts
90
+ var import_core2 = require("@babylonjs/core");
91
+ var Vec3 = class _Vec3 {
92
+ _vector3;
93
+ static Zero = new _Vec3(0, 0, 0);
94
+ constructor(x, y, z) {
95
+ this._vector3 = new import_core2.Vector3(x, y, z);
96
+ }
97
+ clone() {
98
+ return new _Vec3(this.x, this.y, this.z);
99
+ }
100
+ normalize() {
101
+ this._vector3.normalize();
102
+ }
103
+ applyAxisAngle(axis, angle) {
104
+ const rotationQuat = import_core2.Quaternion.RotationAxis(axis._vector3, angle);
105
+ this._vector3.rotateByQuaternionAroundPointToRef(rotationQuat, import_core2.Vector3.Zero(), this._vector3);
106
+ return this;
107
+ }
108
+ applyQuaternion(quat) {
109
+ this._vector3.applyRotationQuaternionInPlace(quat.quaternion);
110
+ }
111
+ multiplyScalar(scalar) {
112
+ this._vector3.scaleInPlace(scalar);
113
+ }
114
+ add(vec) {
115
+ this._vector3.addInPlace(vec._vector3);
116
+ }
117
+ length() {
118
+ return this._vector3.length();
119
+ }
120
+ equals(other) {
121
+ return this.x === other.x && this.y === other.y && this.z === other.z;
122
+ }
123
+ get x() {
124
+ return this._vector3.x;
125
+ }
126
+ get y() {
127
+ return this._vector3.y;
128
+ }
129
+ get z() {
130
+ return this._vector3.z;
131
+ }
132
+ };
133
+
134
+ // src/Math/Quat.ts
135
+ var import_core3 = require("@babylonjs/core");
136
+ var Quat = class {
137
+ _quaternion;
138
+ constructor() {
139
+ this._quaternion = new import_core3.Quaternion();
140
+ }
141
+ rotateByQuaternion(quaternion) {
142
+ quaternion.quaternion.multiplyToRef(this._quaternion, this._quaternion);
143
+ }
144
+ setToQuaternion(quaternion) {
145
+ if (quaternion === null) {
146
+ throw new Error("Cannot set to null quaternion");
147
+ }
148
+ this._quaternion = quaternion;
149
+ }
150
+ setFromMatrix(matrix) {
151
+ this._quaternion = import_core3.Quaternion.FromRotationMatrix(matrix.matrix);
152
+ return this;
153
+ }
154
+ setFromEuler(eulerAngle) {
155
+ this._quaternion = import_core3.Quaternion.FromEulerAngles(eulerAngle.x, eulerAngle.y, eulerAngle.z);
156
+ return this;
157
+ }
158
+ get quaternion() {
159
+ return this._quaternion;
160
+ }
161
+ };
162
+
163
+ // src/Math/Matrix4.ts
164
+ var import_core4 = require("@babylonjs/core");
165
+ var Matrix4 = class {
166
+ _matrix;
167
+ constructor() {
168
+ this._matrix = import_core4.Matrix.Identity();
169
+ }
170
+ fromArray(array) {
171
+ let matArray = array;
172
+ if (array.length === 9) {
173
+ matArray = [
174
+ array[0],
175
+ array[1],
176
+ array[2],
177
+ 0,
178
+ array[3],
179
+ array[4],
180
+ array[5],
181
+ 0,
182
+ array[6],
183
+ array[7],
184
+ array[8],
185
+ 1
186
+ ];
187
+ }
188
+ this._matrix = import_core4.Matrix.FromArray(matArray);
189
+ return this;
190
+ }
191
+ get matrix() {
192
+ return this._matrix;
193
+ }
194
+ };
195
+
196
+ // src/3D/Motif.ts
197
+ var Motif = class extends Group {
198
+ userData;
199
+ _quat;
200
+ constructor(name) {
201
+ const tempEngine = new import_core5.NullEngine();
202
+ const tempScene = new import_core5.Scene(tempEngine);
203
+ super(name, tempScene);
204
+ this._quat = new Quat();
205
+ this.userData = {
206
+ atomInfo: [],
207
+ fileName: ""
208
+ };
209
+ }
210
+ addChild(child) {
211
+ if (this._children.has(child)) {
212
+ return;
213
+ }
214
+ child.setParent(this);
215
+ this._children.add(child);
216
+ }
217
+ setPosition(x, y, z) {
218
+ this._node.setAbsolutePosition(new import_core5.Vector3(x, y, z));
219
+ }
220
+ translate(x, y, z) {
221
+ const newX = this.position.x + x;
222
+ const newY = this.position.y + y;
223
+ const newZ = this.position.z + z;
224
+ this.setPosition(newX, newY, newZ);
225
+ }
226
+ rotate(axis, angle) {
227
+ this._node.rotate(new import_core5.Vector3(
228
+ axis.x,
229
+ axis.y,
230
+ axis.z
231
+ ), angle, import_core5.Space.WORLD);
232
+ this._nanCheck();
233
+ }
234
+ rotateByQuaternion(quat) {
235
+ if (this._node.rotationQuaternion === null) {
236
+ this._node.rotationQuaternion = this._node.rotation.toQuaternion();
237
+ }
238
+ quat.quaternion.multiplyToRef(this._node.rotationQuaternion, this._node.rotationQuaternion);
239
+ this._nanCheck();
240
+ }
241
+ setQuaternion(quat) {
242
+ if (this._node.rotationQuaternion === null) {
243
+ this._node.rotationQuaternion = this._node.rotation.toQuaternion();
244
+ }
245
+ this._quat.setToQuaternion(quat.quaternion);
246
+ }
247
+ multiplyScalar(scalar) {
248
+ this._node.scaling = new import_core5.Vector3(
249
+ this._node.scaling.x * scalar,
250
+ this._node.scaling.y * scalar,
251
+ this._node.scaling.z * scalar
252
+ );
253
+ }
254
+ _nanCheck() {
255
+ if (Number.isNaN(this._quat.quaternion.w) || Number.isNaN(this._quat.quaternion.x) || Number.isNaN(this._quat.quaternion.y) || Number.isNaN(this._quat.quaternion.z)) {
256
+ this._quat.setToQuaternion(import_core5.Quaternion.Identity());
257
+ throw new Error(`Quaternion is NaN for motif ${this._node.name}`);
258
+ }
259
+ }
260
+ get uuid() {
261
+ return this._node.uniqueId.toString();
262
+ }
263
+ get quat() {
264
+ this._quat.setToQuaternion(this._node.rotationQuaternion);
265
+ return this._quat;
266
+ }
267
+ get position() {
268
+ return new Vec3(
269
+ this._node.absolutePosition.x,
270
+ this._node.absolutePosition.y,
271
+ this._node.absolutePosition.z
272
+ );
273
+ }
274
+ };
275
+
276
+ // src/3D/Residue.ts
277
+ var import_core6 = require("@babylonjs/core");
278
+ var Residue = class extends Group {
279
+ constructor(name) {
280
+ const tempEngine = new import_core6.NullEngine();
281
+ const tempScene = new import_core6.Scene(tempEngine);
282
+ super(name, tempScene);
283
+ }
284
+ addChild(child) {
285
+ if (this._children.has(child)) {
286
+ return;
287
+ }
288
+ child.setParent(this);
289
+ this._children.add(child);
290
+ }
291
+ // Temporary function to find if Residue contains mesh with uuid
292
+ hasMesh(uuid) {
293
+ let found = false;
294
+ this._children.forEach((child) => {
295
+ if (child.uuid === uuid) {
296
+ found = true;
297
+ }
298
+ });
299
+ return found;
300
+ }
301
+ };
302
+
303
+ // src/3D/MeshObject.ts
304
+ var import_core7 = require("@babylonjs/core");
305
+ var MeshObject = class {
306
+ _mesh;
307
+ userData;
308
+ constructor(name) {
309
+ this._mesh = new import_core7.Mesh(name);
310
+ this.userData = {};
311
+ }
312
+ setParent(parent) {
313
+ if (parent === null) {
314
+ this._mesh.parent = null;
315
+ return;
316
+ }
317
+ this._mesh.parent = parent.node;
318
+ }
319
+ applyVertexData(positions, indices) {
320
+ const normals = [];
321
+ import_core7.VertexData.ComputeNormals(positions, indices, normals);
322
+ const vertexData = new import_core7.VertexData();
323
+ vertexData.positions = positions;
324
+ vertexData.indices = indices;
325
+ vertexData.normals = normals;
326
+ vertexData.applyToMesh(this._mesh);
327
+ }
328
+ applyHighlight() {
329
+ const mat = this._mesh.material;
330
+ mat.emissiveColor = mat.diffuseColor.scale(0.75);
331
+ }
332
+ resetHighlight() {
333
+ const mat = this._mesh.material;
334
+ mat.emissiveColor = import_core7.Color3.Black();
335
+ }
336
+ /**
337
+ * Cretes a material with the given color and sets it to the mesh
338
+ * @param color String in the format of #RRGGBB
339
+ */
340
+ createAndSetMaterial(color) {
341
+ const mat = new import_core7.StandardMaterial("mat");
342
+ const color3 = import_core7.Color3.FromHexString(`#${color.replace(/^0x/, "")}`);
343
+ mat.diffuseColor = color3;
344
+ mat.specularColor = color3;
345
+ this._mesh.material = mat;
346
+ }
347
+ setNewMesh(mesh) {
348
+ this._mesh = mesh;
349
+ return this;
350
+ }
351
+ get mesh() {
352
+ return this._mesh;
353
+ }
354
+ get uuid() {
355
+ return this._mesh.uniqueId.toString();
356
+ }
357
+ };
358
+
359
+ // src/3D/RenderScene.ts
360
+ var import_core9 = require("@babylonjs/core");
361
+
362
+ // src/Events/EventTypes.ts
363
+ var Events;
364
+ ((Events2) => {
365
+ let EventType;
366
+ ((EventType2) => {
367
+ EventType2["POINTER_DOWN"] = "pointerDown";
368
+ EventType2["POINTER_UP"] = "pointerUp";
369
+ EventType2["POINTER_MOVE"] = "pointerMove";
370
+ EventType2["POINTER_WHEEL"] = "pointerWheel";
371
+ EventType2["KEY_DOWN"] = "keyDown";
372
+ EventType2["KEY_UP"] = "keyUp";
373
+ EventType2["TOUCH_START"] = "touchStart";
374
+ EventType2["TOUCH_END"] = "touchEnd";
375
+ EventType2["TOUCH_MOVE"] = "touchMove";
376
+ EventType2["PINCH_START"] = "pinchStart";
377
+ EventType2["PINCH"] = "pinch";
378
+ EventType2["PINCH_END"] = "pinchEnd";
379
+ EventType2["OBJECT_SELECTED"] = "objectSelected";
380
+ EventType2["OBJECT_DESELECTED"] = "objectDeselected";
381
+ EventType2["RESIZE"] = "resize";
382
+ EventType2["RENDER"] = "render";
383
+ EventType2["ENGINE_STARTED"] = "engineStarted";
384
+ EventType2["ENGINE_STOPPED"] = "engineStopped";
385
+ })(EventType = Events2.EventType || (Events2.EventType = {}));
386
+ })(Events || (Events = {}));
387
+
388
+ // src/Events/EventManager.ts
389
+ var import_core8 = require("@babylonjs/core");
390
+ var EventManager = class {
391
+ // Custom observables for each event type
392
+ _eventObservables = /* @__PURE__ */ new Map();
393
+ // Babylon.js observer references for cleanup
394
+ _pointerObserver = null;
395
+ _keyboardObserver = null;
396
+ _renderObserver = null;
397
+ _activeKeys = /* @__PURE__ */ new Set();
398
+ // Event state variables
399
+ _pendingDeselect = false;
400
+ // private _listeningForEvents: boolean = true;
401
+ constructor() {
402
+ this._initializeObservables();
403
+ }
404
+ /**
405
+ * Dispose of the event listeners and triggers
406
+ */
407
+ dispose() {
408
+ this._eventObservables.forEach((observable) => observable.clear());
409
+ this._eventObservables.clear();
410
+ this._pointerObserver?.remove();
411
+ this._pointerObserver = null;
412
+ this._keyboardObserver?.remove();
413
+ this._keyboardObserver = null;
414
+ this._renderObserver?.remove();
415
+ this._renderObserver = null;
416
+ }
417
+ /**
418
+ * Register an event of a certain type
419
+ * @param eventType String event type. Can be custom or standard.
420
+ * @param callback Callback function to run when the event is triggered
421
+ * @returns Babylon Observer instance for later removal
422
+ */
423
+ on(eventType, callback) {
424
+ if (!this._eventObservables.has(eventType)) {
425
+ this._eventObservables.set(eventType, new import_core8.Observable());
426
+ }
427
+ return this._eventObservables.get(eventType).add(callback);
428
+ }
429
+ /**
430
+ * Remove a registered event observer
431
+ * @param observer Babylon Observer with the registered callback
432
+ */
433
+ off(observer) {
434
+ observer.remove();
435
+ }
436
+ /**
437
+ * Emit a custom event, triggering listeners registered with that event type.
438
+ * @param eventType A string event type. Can be custom or standard.
439
+ * @param eventData Event info object partial.
440
+ */
441
+ emit(eventType, eventData) {
442
+ const event = {
443
+ type: eventType,
444
+ canceled: false,
445
+ timestamp: performance.now(),
446
+ ...eventData
447
+ };
448
+ try {
449
+ this.notifyObservers(eventType, event);
450
+ } catch (err) {
451
+ throw new Error(`Error emitting event ${eventType}:`);
452
+ }
453
+ }
454
+ /**
455
+ * Method to notify listeners of a type of event
456
+ * @param eventType A string event type. Can be custom or standard.
457
+ * @param event Event info object
458
+ */
459
+ notifyObservers(eventType, event) {
460
+ if (this._eventObservables.has(eventType)) {
461
+ this._eventObservables.get(eventType).notifyObservers(event);
462
+ }
463
+ }
464
+ /**
465
+ * Method to set up pointer and keyboard events on the scene.
466
+ * Comes preset with POINTER_DOWN, POINTER_UP, POINTER_MOVE, KEYBOARD_DOWN, and KEYBOARD_UP event triggering.
467
+ * @param scene A RenderScene object
468
+ */
469
+ setupEventHandling(scene) {
470
+ if (scene.isRunning) {
471
+ throw new Error("Cannot setup event handling on running scene.\n");
472
+ }
473
+ const babylonScene = scene.scene;
474
+ this._pointerObserver = babylonScene.onPointerObservable.add((pointerInfo) => {
475
+ const event = pointerInfo.event;
476
+ const pointerEvent = {
477
+ type: Events.EventType.POINTER_DOWN,
478
+ // will be overwritten below
479
+ position: { x: event.clientX, y: event.clientY },
480
+ button: event.button,
481
+ buttons: event.buttons,
482
+ ctrlKey: event.ctrlKey,
483
+ altKey: event.altKey,
484
+ shiftKey: event.shiftKey,
485
+ metaKey: event.metaKey,
486
+ originalEvent: event,
487
+ canceled: false,
488
+ timestamp: performance.now()
489
+ };
490
+ let motifNode = null;
491
+ let pickedMesh = null;
492
+ if (pointerInfo.pickInfo?.hit && pointerInfo.pickInfo?.pickedMesh) {
493
+ pickedMesh = pointerInfo.pickInfo.pickedMesh;
494
+ motifNode = pointerInfo.pickInfo.pickedMesh.parent?.parent;
495
+ }
496
+ let motifObj = null;
497
+ let residue = null;
498
+ if (motifNode && pickedMesh) {
499
+ motifObj = scene.children.get(motifNode.uniqueId.toString());
500
+ residue = this._getResidueFromMotifNode(
501
+ motifNode,
502
+ pickedMesh?.uniqueId.toString(),
503
+ scene.children
504
+ );
505
+ }
506
+ if (motifNode && residue) {
507
+ pointerEvent.pickedResidue = residue;
508
+ }
509
+ switch (pointerInfo.type) {
510
+ case import_core8.PointerEventTypes.POINTERDOWN:
511
+ pointerEvent.type = Events.EventType.POINTER_DOWN;
512
+ this.notifyObservers(Events.EventType.POINTER_DOWN, pointerEvent);
513
+ if (motifObj && residue) {
514
+ const selectionEvent = {
515
+ type: Events.EventType.OBJECT_SELECTED,
516
+ residue,
517
+ motif: motifObj,
518
+ multiSelect: event.ctrlKey,
519
+ canceled: false,
520
+ timestamp: performance.now()
521
+ };
522
+ this.notifyObservers(Events.EventType.OBJECT_SELECTED, selectionEvent);
523
+ } else {
524
+ this._pendingDeselect = true;
525
+ }
526
+ break;
527
+ case import_core8.PointerEventTypes.POINTERUP:
528
+ pointerEvent.type = Events.EventType.POINTER_UP;
529
+ this.notifyObservers(Events.EventType.POINTER_UP, pointerEvent);
530
+ if (this._pendingDeselect) {
531
+ const deselectionEvent = {
532
+ type: Events.EventType.OBJECT_DESELECTED,
533
+ canceled: false,
534
+ timestamp: performance.now()
535
+ };
536
+ this.notifyObservers(Events.EventType.OBJECT_DESELECTED, deselectionEvent);
537
+ this._pendingDeselect = false;
538
+ }
539
+ break;
540
+ case import_core8.PointerEventTypes.POINTERMOVE:
541
+ pointerEvent.type = Events.EventType.POINTER_MOVE;
542
+ pointerEvent.deltaX = event.movementX;
543
+ pointerEvent.deltaY = event.movementY;
544
+ this.notifyObservers(Events.EventType.POINTER_MOVE, pointerEvent);
545
+ this._pendingDeselect = false;
546
+ break;
547
+ case import_core8.PointerEventTypes.POINTERWHEEL:
548
+ pointerEvent.type = Events.EventType.POINTER_WHEEL;
549
+ this.notifyObservers(Events.EventType.POINTER_WHEEL, pointerEvent);
550
+ break;
551
+ }
552
+ });
553
+ this._keyboardObserver = babylonScene.onKeyboardObservable.add((keyboardInfo) => {
554
+ const event = keyboardInfo.event;
555
+ const keyboardEvent = {
556
+ type: keyboardInfo.type === import_core8.KeyboardEventTypes.KEYDOWN ? Events.EventType.KEY_DOWN : Events.EventType.KEY_UP,
557
+ key: event.key,
558
+ code: event.code,
559
+ ctrlKey: event.ctrlKey,
560
+ altKey: event.altKey,
561
+ shiftKey: event.shiftKey,
562
+ metaKey: event.metaKey,
563
+ repeat: event.repeat,
564
+ rotationAxis: new Vec3(0, 0, 0),
565
+ translationDirection: new Vec3(0, 0, 0),
566
+ originalEvent: event,
567
+ canceled: false,
568
+ timestamp: performance.now()
569
+ };
570
+ if (keyboardInfo.type === import_core8.KeyboardEventTypes.KEYDOWN) {
571
+ this._activeKeys.add(event.key);
572
+ } else if (keyboardInfo.type === import_core8.KeyboardEventTypes.KEYUP) {
573
+ this._activeKeys.delete(event.key);
574
+ }
575
+ if (this._activeKeys.has("w")) keyboardEvent.rotationAxis.add(new Vec3(-1, 0, 0));
576
+ if (this._activeKeys.has("a")) keyboardEvent.rotationAxis.add(new Vec3(0, 1, 0));
577
+ if (this._activeKeys.has("s")) keyboardEvent.rotationAxis.add(new Vec3(1, 0, 0));
578
+ if (this._activeKeys.has("d")) keyboardEvent.rotationAxis.add(new Vec3(0, -1, 0));
579
+ if (this._activeKeys.has("q")) keyboardEvent.rotationAxis.add(new Vec3(0, 0, -1));
580
+ if (this._activeKeys.has("e")) keyboardEvent.rotationAxis.add(new Vec3(0, 0, 1));
581
+ if (this._activeKeys.has("W")) keyboardEvent.translationDirection.add(new Vec3(0, 1, 0));
582
+ if (this._activeKeys.has("A")) keyboardEvent.translationDirection.add(new Vec3(1, 0, 0));
583
+ if (this._activeKeys.has("S")) keyboardEvent.translationDirection.add(new Vec3(0, -1, 0));
584
+ if (this._activeKeys.has("D")) keyboardEvent.translationDirection.add(new Vec3(-1, 0, 0));
585
+ if (keyboardInfo.type === import_core8.KeyboardEventTypes.KEYDOWN) {
586
+ this.notifyObservers(Events.EventType.KEY_DOWN, keyboardEvent);
587
+ } else if (keyboardInfo.type === import_core8.KeyboardEventTypes.KEYUP) {
588
+ this.notifyObservers(Events.EventType.KEY_UP, keyboardEvent);
589
+ }
590
+ });
591
+ }
592
+ /**
593
+ * Initialize observables for all standard event types
594
+ */
595
+ _initializeObservables() {
596
+ Object.values(Events.EventType).forEach((eventType) => {
597
+ this._eventObservables.set(eventType, new import_core8.Observable());
598
+ });
599
+ }
600
+ /**
601
+ * Helper function to get the residue that contains a picked mesh using the transform node of a motif
602
+ * @param node {TransformNode} Motif's TransformNode
603
+ * @param meshUUID Stringified uniqueId of the picked mesh
604
+ * @param sceneChildren Map of Motifs that exist in the scene
605
+ * @returns The Residue that contains the picked mesh, or null if it does not exist
606
+ */
607
+ _getResidueFromMotifNode(node, meshUUID, sceneChildren) {
608
+ const motifUUID = node.uniqueId.toString();
609
+ if (!sceneChildren.has(motifUUID)) {
610
+ return null;
611
+ }
612
+ const motif = sceneChildren.get(motifUUID);
613
+ let residue = null;
614
+ motif.children.forEach((child) => {
615
+ if (child.hasMesh(meshUUID)) residue = child;
616
+ });
617
+ return residue;
618
+ }
619
+ };
620
+
621
+ // src/3D/RenderScene.ts
622
+ var RenderScene = class {
623
+ _canvas;
624
+ _scene;
625
+ _engine;
626
+ _camera;
627
+ // For access to motif objects in the scene
628
+ _children = /* @__PURE__ */ new Map();
629
+ // Event variables
630
+ _eventManager;
631
+ // State variables
632
+ _isDisposed = false;
633
+ isRunning = false;
634
+ constructor(canvas, hexColor, cameraPositionZ, renderWidth, renderHeight) {
635
+ this._canvas = canvas;
636
+ this._engine = new import_core9.Engine(this._canvas, true);
637
+ this._engine.setSize(renderWidth, renderHeight);
638
+ this._scene = new import_core9.Scene(this._engine);
639
+ this._scene.clearColor = import_core9.Color4.FromHexString(`${hexColor.replace(/^0x/, "")}`);
640
+ this._camera = new import_core9.UniversalCamera("camera", new import_core9.Vector3(0, 0, cameraPositionZ));
641
+ this._camera.setTarget(new import_core9.Vector3(0, 0, 0));
642
+ this._camera.mode = import_core9.Camera.ORTHOGRAPHIC_CAMERA;
643
+ this._camera.orthoTop = this._engine.getRenderHeight() / 2;
644
+ this._camera.orthoBottom = this._engine.getRenderHeight() / -2;
645
+ this._camera.orthoLeft = this._engine.getRenderWidth() / -2;
646
+ this._camera.orthoRight = this._engine.getRenderWidth() / 2;
647
+ this._camera.minZ = 10;
648
+ const light = new import_core9.HemisphericLight("light", import_core9.Vector3.Up(), this._scene);
649
+ light.intensity = 1.25;
650
+ this._eventManager = new EventManager();
651
+ window.addEventListener("resize", this._handleResize.bind(this));
652
+ }
653
+ // Start the render loop
654
+ start() {
655
+ if (this._isDisposed) {
656
+ throw new Error("Cannot start a disposed RenderScene.");
657
+ }
658
+ if (this.isRunning) {
659
+ return;
660
+ }
661
+ this._eventManager.setupEventHandling(this);
662
+ this._engine.runRenderLoop(() => {
663
+ this._scene.render();
664
+ this._eventManager.notifyObservers(Events.EventType.RENDER, {
665
+ type: Events.EventType.RENDER,
666
+ canceled: false,
667
+ timestamp: performance.now()
668
+ });
669
+ });
670
+ this._eventManager.notifyObservers(Events.EventType.ENGINE_STARTED, {
671
+ type: Events.EventType.ENGINE_STARTED,
672
+ canceled: false,
673
+ timestamp: performance.now()
674
+ });
675
+ this.isRunning = true;
676
+ }
677
+ // Stop the render loop without disposing resources
678
+ stop() {
679
+ if (this.isRunning) {
680
+ this._engine.stopRenderLoop();
681
+ this._eventManager.notifyObservers(Events.EventType.ENGINE_STOPPED, {
682
+ type: Events.EventType.ENGINE_STOPPED,
683
+ canceled: false,
684
+ timestamp: performance.now()
685
+ });
686
+ this.isRunning = false;
687
+ }
688
+ }
689
+ // Stop and dispose of all resources
690
+ dispose() {
691
+ this.stop();
692
+ window.removeEventListener("resize", this._handleResize);
693
+ this._eventManager.dispose();
694
+ this._scene.dispose();
695
+ this._engine.dispose();
696
+ this.isRunning = false;
697
+ this._isDisposed = true;
698
+ }
699
+ add(motif) {
700
+ if (this._children.has(motif.uuid)) {
701
+ return;
702
+ }
703
+ this._reattachToScene(motif.node, motif);
704
+ this._children.set(motif.uuid, motif);
705
+ this._scene.addTransformNode(motif.node);
706
+ }
707
+ remove(motif) {
708
+ if (!this._children.has(motif.uuid)) {
709
+ return;
710
+ }
711
+ this._children.delete(motif.uuid);
712
+ this._scene.removeTransformNode(motif.node);
713
+ }
714
+ setBackgroundColor(hexColor) {
715
+ this._scene.clearColor = import_core9.Color4.FromHexString(`${hexColor.replace(/^0x/, "")}`);
716
+ }
717
+ _reattachToScene(node, currObj) {
718
+ node._scene = this._scene;
719
+ if (node instanceof import_core9.Mesh && currObj instanceof MeshObject) {
720
+ const meshSerialized = node.serialize();
721
+ const verticeData = node.geometry?.serializeVerticeData();
722
+ const materialData = node.material?.serialize();
723
+ const mesh = import_core9.Mesh.Parse(meshSerialized, this._scene, "");
724
+ const geo = import_core9.Geometry.Parse(verticeData, this._scene, "");
725
+ const mat = import_core9.Material.Parse(materialData, this._scene, "");
726
+ geo?.applyToMesh(mesh);
727
+ mesh.material = mat;
728
+ currObj.setNewMesh(mesh);
729
+ node.dispose();
730
+ }
731
+ if (!(currObj instanceof MeshObject)) {
732
+ currObj.children.forEach((childObj) => {
733
+ if (childObj instanceof Residue) {
734
+ this._reattachToScene(childObj.node, childObj);
735
+ } else if (currObj instanceof Residue) {
736
+ this._reattachToScene(childObj.mesh, childObj);
737
+ childObj.setParent(currObj);
738
+ }
739
+ });
740
+ }
741
+ }
742
+ _handleResize = () => {
743
+ this._engine.setSize(window.innerWidth, window.innerHeight);
744
+ this._camera.orthoTop = this._engine.getRenderHeight() / 2;
745
+ this._camera.orthoBottom = this._engine.getRenderHeight() / -2;
746
+ this._camera.orthoLeft = this._engine.getRenderWidth() / -2;
747
+ this._camera.orthoRight = this._engine.getRenderWidth() / 2;
748
+ this._eventManager.notifyObservers(Events.EventType.RESIZE, {
749
+ type: Events.EventType.RESIZE,
750
+ canceled: false,
751
+ timestamp: performance.now()
752
+ });
753
+ };
754
+ /**
755
+ * Returns the Babylon.js Scene object.
756
+ */
757
+ get scene() {
758
+ return this._scene;
759
+ }
760
+ /**
761
+ * Returns the engine.
762
+ */
763
+ get engine() {
764
+ return this._engine;
765
+ }
766
+ /**
767
+ * Returns the camera.
768
+ */
769
+ get camera() {
770
+ return this._camera;
771
+ }
772
+ /**
773
+ * Returns the event manager
774
+ */
775
+ get eventManager() {
776
+ return this._eventManager;
777
+ }
778
+ /**
779
+ * Returns the set of children.
780
+ */
781
+ get children() {
782
+ return this._children;
783
+ }
784
+ /**
785
+ * Returns the current render width
786
+ */
787
+ get renderWidth() {
788
+ return this._engine.getRenderWidth();
789
+ }
790
+ /**
791
+ * Returns the current render height
792
+ */
793
+ get renderHeight() {
794
+ return this._engine.getRenderHeight();
795
+ }
796
+ };
797
+
798
+ // src/3D/utils/GetAtomInfo.ts
799
+ async function parseAtomCoords(fileName) {
800
+ const response = await fetch(`/${fileName}`);
801
+ const jsonData = await response.json();
802
+ const coordinates = [];
803
+ for (const [key] of Object.entries(jsonData)) {
804
+ const atomMap = jsonData[key][0];
805
+ if (atomMap[`"C1'"`]) {
806
+ coordinates.push(
807
+ new Vec3(
808
+ parseFloat(atomMap[`"C1'"`][0]),
809
+ parseFloat(atomMap[`"C1'"`][1]),
810
+ parseFloat(atomMap[`"C1'"`][2])
811
+ )
812
+ );
813
+ }
814
+ }
815
+ return coordinates;
816
+ }
817
+ async function getAtomCoords(fileName) {
818
+ const coordinates = await parseAtomCoords(fileName);
819
+ return coordinates;
820
+ }
821
+ async function updateAllMotifs(motifMeshArray) {
822
+ const fileNames = [];
823
+ for (const motif of motifMeshArray) {
824
+ fileNames.push(motif.userData.fileName);
825
+ }
826
+ const atomInfoLists = await Promise.all(fileNames.map((o) => getAtomCoords(o)));
827
+ for (let i = 0; i < motifMeshArray.length; i += 1) {
828
+ motifMeshArray[i].userData.atomInfo = atomInfoLists[i];
829
+ }
830
+ }
831
+
832
+ // src/3D/utils/GetPoints.ts
833
+ function getPoints(nucleotideData) {
834
+ const vertices = [];
835
+ const indices = [];
836
+ for (let i = 1; i < nucleotideData.length; i += 2) {
837
+ vertices.push(nucleotideData[i]);
838
+ indices.push(nucleotideData[i + 1]);
839
+ }
840
+ return { vertices, indices };
841
+ }
842
+
843
+ // src/3D/utils/GetMotif.ts
844
+ async function getMotif(motifJSONFileName, motifColorHex = "0xcc2900") {
845
+ const motif = new Motif(`${motifJSONFileName}_motif`);
846
+ const motifJSONFileData = await fetch(`/${motifJSONFileName}`);
847
+ const jsonObject = await motifJSONFileData.json();
848
+ for (const [key] of Object.entries(jsonObject)) {
849
+ const { vertices, indices } = getPoints(jsonObject[key]);
850
+ const residue = new Residue("residue");
851
+ const backboneMesh = new MeshObject(`backbone_${key}`);
852
+ backboneMesh.applyVertexData(vertices[0], indices[0]);
853
+ backboneMesh.createAndSetMaterial(motifColorHex);
854
+ const ringMesh = new MeshObject(`ring_${key}`);
855
+ ringMesh.applyVertexData(vertices[1], indices[1]);
856
+ ringMesh.createAndSetMaterial(motifColorHex);
857
+ residue.addChild(backboneMesh);
858
+ residue.addChild(ringMesh);
859
+ motif.addChild(residue);
860
+ }
861
+ motif.userData.fileName = motifJSONFileName;
862
+ return motif;
863
+ }
864
+
865
+ // src/Canvas/Canvas.tsx
866
+ var import_react = __toESM(require("react"));
867
+
868
+ // src/Canvas/CanvasDataManager.ts
869
+ var CanvasAttributeTypes = /* @__PURE__ */ ((CanvasAttributeTypes2) => {
870
+ CanvasAttributeTypes2["SELECTED_MOTIFS"] = "selectedMotifs";
871
+ CanvasAttributeTypes2["LOCKED_MOTIF_IDS"] = "lockedMotifIds";
872
+ CanvasAttributeTypes2["HLOCKED_MOTIF_IDS"] = "hardLockedMotifIds";
873
+ CanvasAttributeTypes2["SCORE_RMSD"] = "scoreRMSD";
874
+ CanvasAttributeTypes2["KABSCH_RMSD"] = "kabschRMSD";
875
+ return CanvasAttributeTypes2;
876
+ })(CanvasAttributeTypes || {});
877
+ var CanvasDataManager = class {
878
+ static _selectedMotifIds = /* @__PURE__ */ new Set();
879
+ static _lockedMotifIds = [];
880
+ static _hardLockedMotifIds = [];
881
+ static _scoreRMSD = [];
882
+ static _kabschRMSD = [];
883
+ static _listeners = /* @__PURE__ */ new Map();
884
+ // private static _backgroundColor: string = '#040a20';
885
+ static get selectedMotifIds() {
886
+ return this._selectedMotifIds;
887
+ }
888
+ static setSelectedMotifIds(selectedMotifIds) {
889
+ this._selectedMotifIds = selectedMotifIds;
890
+ this._listeners.get("selectedMotifs" /* SELECTED_MOTIFS */)?.forEach((fn) => fn());
891
+ }
892
+ static get lockedMotifIds() {
893
+ return this._lockedMotifIds;
894
+ }
895
+ static setLockedMotifIds(lockedMotifIds) {
896
+ this._lockedMotifIds = lockedMotifIds;
897
+ this._listeners.get("lockedMotifIds" /* LOCKED_MOTIF_IDS */)?.forEach((fn) => fn());
898
+ }
899
+ static get hardLockedMotifIds() {
900
+ return this._hardLockedMotifIds;
901
+ }
902
+ static setHardLockedMotifIds(hardLockedMotifIds) {
903
+ this._hardLockedMotifIds = hardLockedMotifIds;
904
+ this._listeners.get("hardLockedMotifIds" /* HLOCKED_MOTIF_IDS */)?.forEach((fn) => fn());
905
+ }
906
+ static get scoreRMSD() {
907
+ return this._scoreRMSD;
908
+ }
909
+ static setScoreRMSD(scoreRMSD) {
910
+ this._scoreRMSD = scoreRMSD;
911
+ this._listeners.get("scoreRMSD" /* SCORE_RMSD */)?.forEach((fn) => fn());
912
+ }
913
+ static get kabschRMSD() {
914
+ return this._kabschRMSD;
915
+ }
916
+ static setKabschRMSD(kabschRMSD) {
917
+ this._kabschRMSD = kabschRMSD;
918
+ this._listeners.get("kabschRMSD" /* KABSCH_RMSD */)?.forEach((fn) => fn());
919
+ }
920
+ static subscribe(canvasAttributeType, callback) {
921
+ if (!this._listeners.has(canvasAttributeType)) {
922
+ this._listeners.set(canvasAttributeType, /* @__PURE__ */ new Set());
923
+ }
924
+ this._listeners.get(canvasAttributeType).add(callback);
925
+ return () => this._listeners.get(canvasAttributeType).delete(callback);
926
+ }
927
+ // static get backgroundColor(): string {
928
+ // return this._backgroundColor;
929
+ // }
930
+ // static setBackgroundColor(backgroundColor: string): void {
931
+ // this._backgroundColor = backgroundColor;
932
+ // }
933
+ };
934
+
935
+ // src/Kabsch/Kabsch.ts
936
+ var import_numeric = __toESM(require("numeric"));
937
+
938
+ // src/RMSD/RotateAtoms.ts
939
+ function rotateAllPoints(atomCoords, quat) {
940
+ const newCoordinates = [];
941
+ for (let i = 0; i < atomCoords.length; i += 1) {
942
+ newCoordinates.push(atomCoords[i].clone());
943
+ newCoordinates[i].applyQuaternion(quat);
944
+ }
945
+ return newCoordinates;
946
+ }
947
+
948
+ // src/RMSD/RMSDSlidingWindow.ts
949
+ function getRMSD(a, b) {
950
+ let newCoords1;
951
+ let newCoords2;
952
+ if (!(Array.isArray(a) || Array.isArray(b))) {
953
+ if (a instanceof Motif && b instanceof Motif) {
954
+ newCoords1 = rotateAllPoints(a.userData.atomInfo, a.quat);
955
+ newCoords2 = rotateAllPoints(b.userData.atomInfo, b.quat);
956
+ } else {
957
+ return -1;
958
+ }
959
+ } else if (a.every((item) => item instanceof Vec3)) {
960
+ newCoords1 = a;
961
+ newCoords2 = b;
962
+ } else {
963
+ return -1;
964
+ }
965
+ const squaredDistances = newCoords1.reduce((sum, coord1, index) => {
966
+ const coord2 = newCoords2[index];
967
+ const distanceSquared = (coord1.x - coord2.x) ** 2 + (coord1.y - coord2.y) ** 2 + (coord1.z - coord2.z) ** 2;
968
+ return sum + distanceSquared;
969
+ }, 0);
970
+ const minLength = newCoords1.length;
971
+ const meanSquaredDistance = squaredDistances / minLength;
972
+ return Math.sqrt(meanSquaredDistance);
973
+ }
974
+ function calculateRMSDSlide(coordinates1, coordinates2) {
975
+ try {
976
+ const minLength = Math.min(coordinates1.length, coordinates2.length);
977
+ const smallObj = minLength === coordinates1.length ? coordinates1 : coordinates2;
978
+ const largeObj = minLength === coordinates1.length ? coordinates2 : coordinates1;
979
+ const iterations = largeObj.length - smallObj.length + 1;
980
+ const trimmedCoordinates1 = smallObj.slice(0, minLength);
981
+ let minRMSD = 1e3;
982
+ for (let startPosition = 0; startPosition < iterations; startPosition += 1) {
983
+ const trimmedCoordinates2 = largeObj.slice(startPosition, startPosition + minLength);
984
+ const rmsd = getRMSD(trimmedCoordinates1, trimmedCoordinates2);
985
+ if (rmsd < minRMSD) {
986
+ minRMSD = rmsd;
987
+ }
988
+ }
989
+ return minRMSD;
990
+ } catch (error) {
991
+ throw new Error("Error in RMSD calculation");
992
+ }
993
+ }
994
+
995
+ // src/RMSD/GetRMSD.ts
996
+ function calculateRMSD(selectedMotifMeshArray, motifMeshArray) {
997
+ const idToIdx = {};
998
+ const rotatedCoordinates = [];
999
+ for (let i = 0; i < motifMeshArray.length; i += 1) {
1000
+ rotatedCoordinates.push(rotateAllPoints(
1001
+ motifMeshArray[i].userData.atomInfo,
1002
+ motifMeshArray[i].quat
1003
+ ));
1004
+ idToIdx[motifMeshArray[i].uuid] = i;
1005
+ }
1006
+ const scores = [];
1007
+ for (let i = 0; i < selectedMotifMeshArray.length; i += 1) {
1008
+ const currScores = [];
1009
+ for (let j = 0; j < motifMeshArray.length; j += 1) {
1010
+ if (selectedMotifMeshArray[i].uuid !== motifMeshArray[j].uuid) {
1011
+ const score = calculateRMSDSlide(
1012
+ rotatedCoordinates[idToIdx[selectedMotifMeshArray[i].uuid]],
1013
+ rotatedCoordinates[idToIdx[motifMeshArray[j].uuid]]
1014
+ );
1015
+ currScores.push({
1016
+ score,
1017
+ selId: selectedMotifMeshArray[i].uuid,
1018
+ refId: motifMeshArray[j].uuid
1019
+ });
1020
+ }
1021
+ }
1022
+ if (currScores.length !== 0) {
1023
+ scores.push(currScores);
1024
+ }
1025
+ }
1026
+ return scores;
1027
+ }
1028
+
1029
+ // src/Kabsch/Kabsch.ts
1030
+ function dot(matrix1, matrix2) {
1031
+ const result = [];
1032
+ for (let i = 0; i < matrix1.length; i += 1) {
1033
+ result[i] = [];
1034
+ for (let j = 0; j < 3; j += 1) {
1035
+ result[i][j] = 0;
1036
+ for (let k = 0; k < matrix1[0].length; k += 1) {
1037
+ result[i][j] += matrix1[i][k] * matrix2[k][j];
1038
+ }
1039
+ }
1040
+ }
1041
+ return result;
1042
+ }
1043
+ function calculateKabsch(coordinates1, coordinates2) {
1044
+ try {
1045
+ const convertedCoordinates1 = [];
1046
+ for (let i = 0; i < coordinates1.length; i += 1) {
1047
+ convertedCoordinates1.push([coordinates1[i].x, coordinates1[i].y, coordinates1[i].z]);
1048
+ }
1049
+ const convertedCoordinates2 = [];
1050
+ for (let i = 0; i < coordinates2.length; i += 1) {
1051
+ convertedCoordinates2.push([coordinates2[i].x, coordinates2[i].y, coordinates2[i].z]);
1052
+ }
1053
+ const covarianceMatrix = dot(import_numeric.default.transpose(convertedCoordinates1), convertedCoordinates2);
1054
+ const { U, V } = import_numeric.default.svd(covarianceMatrix);
1055
+ let rotationMatrix = import_numeric.default.dot(V, import_numeric.default.transpose(U));
1056
+ if (import_numeric.default.det(rotationMatrix) < 0) {
1057
+ V[2] = V[2].map((val) => -val);
1058
+ rotationMatrix = dot(V, import_numeric.default.transpose(U));
1059
+ }
1060
+ return rotationMatrix;
1061
+ } catch (error) {
1062
+ throw new Error("error");
1063
+ }
1064
+ }
1065
+ function getKabschCoords(coordinates2, mat) {
1066
+ const flatMatrix = mat.flat();
1067
+ const newMat4 = new Matrix4().fromArray(flatMatrix);
1068
+ const quat = new Quat().setFromMatrix(newMat4);
1069
+ return rotateAllPoints(coordinates2, quat);
1070
+ }
1071
+ function updateCoords(motif1, motif2) {
1072
+ const rotatedCoords = [];
1073
+ rotatedCoords.push(rotateAllPoints(motif1.userData.atomInfo, motif1.quat));
1074
+ rotatedCoords.push(rotateAllPoints(motif2.userData.atomInfo, motif2.quat));
1075
+ return rotatedCoords;
1076
+ }
1077
+ function kabschSlidingWindow(motif1, motif2) {
1078
+ try {
1079
+ const rotatedCoords = updateCoords(motif1, motif2);
1080
+ const coordinates1 = rotatedCoords[0].slice();
1081
+ const coordinates2 = rotatedCoords[1].slice();
1082
+ const minLength = Math.min(coordinates1.length, coordinates2.length);
1083
+ const smallObj = minLength === coordinates1.length ? coordinates1 : coordinates2;
1084
+ const largeObj = minLength === coordinates1.length ? coordinates2 : coordinates1;
1085
+ const iterations = largeObj.length - smallObj.length + 1;
1086
+ const trimmedCoordinates1 = smallObj.slice(0, minLength);
1087
+ let minRMSD = 1e3;
1088
+ let bestRotation = [];
1089
+ for (let startPosition = 0; startPosition < iterations; startPosition += 1) {
1090
+ const trimmedCoordinates2 = largeObj.slice(startPosition, startPosition + minLength);
1091
+ const optimalRotation = calculateKabsch(trimmedCoordinates1, trimmedCoordinates2);
1092
+ const kabschPoints = getKabschCoords(trimmedCoordinates2, optimalRotation);
1093
+ const rmsd = getRMSD(trimmedCoordinates1, kabschPoints);
1094
+ if (rmsd < minRMSD) {
1095
+ minRMSD = rmsd;
1096
+ bestRotation = optimalRotation;
1097
+ }
1098
+ }
1099
+ return { matrix: bestRotation, rmsd: minRMSD };
1100
+ } catch (error) {
1101
+ throw new Error(`Error in Kabsch calculation between motifs ${motif1.uuid} and ${motif2.uuid}.`);
1102
+ }
1103
+ }
1104
+ function calculateAllKabschRMSD(motifMeshArray) {
1105
+ const res = [];
1106
+ for (let i = 0; i < motifMeshArray.length; i += 1) {
1107
+ res.push([]);
1108
+ for (let j = 0; j < motifMeshArray.length; j += 1) {
1109
+ res[i].push(0);
1110
+ }
1111
+ }
1112
+ for (let i = 0; i < motifMeshArray.length - 1; i += 1) {
1113
+ for (let j = i + 1; j < motifMeshArray.length; j += 1) {
1114
+ const { rmsd } = kabschSlidingWindow(motifMeshArray[i], motifMeshArray[j]);
1115
+ res[i][j] = rmsd;
1116
+ res[j][i] = rmsd;
1117
+ }
1118
+ }
1119
+ return res;
1120
+ }
1121
+
1122
+ // src/Canvas/Canvas.tsx
1123
+ function Canvas({
1124
+ rendererHeight = 500,
1125
+ rendererWidth = 500,
1126
+ rendererBackgroundColor = "#040a20",
1127
+ rendererSizeIsWindow = false,
1128
+ cameraPositionZ = 1e3,
1129
+ motifProps,
1130
+ customEventProps
1131
+ }) {
1132
+ const canvasRef = (0, import_react.useRef)(null);
1133
+ const scene = (0, import_react.useRef)(null);
1134
+ const motifs = [];
1135
+ let hardLockedMotifIds = [];
1136
+ motifProps.forEach((motifProp) => {
1137
+ motifs.push(motifProp.motif);
1138
+ if (motifProp.locked) hardLockedMotifIds.push(motifProp.motif.uuid);
1139
+ });
1140
+ CanvasDataManager.setHardLockedMotifIds(hardLockedMotifIds);
1141
+ const selectedMotifMeshState = (0, import_react.useRef)(/* @__PURE__ */ new Set());
1142
+ const lockedMotifIdState = (0, import_react.useRef)([]);
1143
+ const [cursorStyle, setCursorStyle] = (0, import_react.useState)("auto");
1144
+ const [selectedMotifIds, setSelectedmotifIds] = (0, import_react.useState)(/* @__PURE__ */ new Set());
1145
+ const [scoreRMSD, setScoreRMSD] = (0, import_react.useState)([]);
1146
+ const [kabschRMSD, setKabschRMSD] = (0, import_react.useState)([]);
1147
+ const [lockedMotifIds, setLockedMotifIds] = (0, import_react.useState)([]);
1148
+ const addMotif = (motif) => {
1149
+ if (selectedMotifIds.has(motif.uuid)) {
1150
+ return;
1151
+ }
1152
+ const newSet = /* @__PURE__ */ new Set();
1153
+ for (let i = 0; i < motifs.length; i += 1) {
1154
+ if (selectedMotifMeshState.current.has(motifs[i]) || motifs[i].uuid === motif.uuid) {
1155
+ newSet.add(motifs[i].uuid);
1156
+ }
1157
+ }
1158
+ setSelectedmotifIds(newSet);
1159
+ selectedMotifMeshState.current.add(motif);
1160
+ };
1161
+ const removeMotif = (motif) => {
1162
+ if (!selectedMotifIds.has(motif.uuid)) {
1163
+ return;
1164
+ }
1165
+ selectedMotifMeshState.current.delete(motif);
1166
+ setSelectedmotifIds((prevState) => {
1167
+ const newState = prevState;
1168
+ newState.delete(motif.uuid);
1169
+ return newState;
1170
+ });
1171
+ };
1172
+ function updateGlow() {
1173
+ motifs.forEach((motif) => {
1174
+ motif.children.forEach((residue) => {
1175
+ residue.children.forEach((childMesh) => {
1176
+ if (selectedMotifIds.has(motif.uuid)) {
1177
+ childMesh.applyHighlight();
1178
+ } else {
1179
+ childMesh.resetHighlight();
1180
+ }
1181
+ });
1182
+ });
1183
+ });
1184
+ }
1185
+ function onSelectMotif(event) {
1186
+ if (event.type !== Events.EventType.OBJECT_SELECTED) {
1187
+ return;
1188
+ }
1189
+ const { motif } = event;
1190
+ if (!motif || selectedMotifMeshState.current.has(motif) || lockedMotifIdState.current.includes(motif.uuid) || hardLockedMotifIds.includes(motif.uuid)) {
1191
+ return;
1192
+ }
1193
+ if (event.multiSelect && motif) {
1194
+ addMotif(motif);
1195
+ } else if (motif) {
1196
+ setSelectedmotifIds(/* @__PURE__ */ new Set([motif.uuid]));
1197
+ selectedMotifMeshState.current = /* @__PURE__ */ new Set([motif]);
1198
+ }
1199
+ }
1200
+ function onDeselectMotif(event) {
1201
+ if (event.type !== Events.EventType.OBJECT_DESELECTED) {
1202
+ return;
1203
+ }
1204
+ if (selectedMotifMeshState.current.size === 0) {
1205
+ return;
1206
+ }
1207
+ setSelectedmotifIds(/* @__PURE__ */ new Set());
1208
+ selectedMotifMeshState.current.clear();
1209
+ }
1210
+ function onMouseMove(event) {
1211
+ if (event.deltaX === 0 && event.deltaY === 0) {
1212
+ return;
1213
+ }
1214
+ if (event.buttons === 2 && selectedMotifMeshState.current.size > 0) {
1215
+ if (scene.current) {
1216
+ const { renderWidth, renderHeight } = scene.current;
1217
+ const rawDeltaX = event.deltaX;
1218
+ const rawDeltaY = event.deltaY;
1219
+ const deltaX = rawDeltaX / renderWidth * canvasRef.current.width;
1220
+ const deltaY = rawDeltaY / renderHeight * canvasRef.current.height;
1221
+ selectedMotifMeshState.current.forEach((element) => {
1222
+ if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1223
+ element.translate(-deltaX, -deltaY, 0);
1224
+ }
1225
+ });
1226
+ }
1227
+ } else if (event.buttons === 1 && selectedMotifMeshState.current.size > 0) {
1228
+ const { deltaX, deltaY } = event;
1229
+ const directionVec = new Vec3(-deltaX, -deltaY, 0);
1230
+ const axisVec = directionVec.clone().applyAxisAngle(new Vec3(0, 0, 1), Math.PI / 2);
1231
+ axisVec.normalize();
1232
+ if (scene.current) {
1233
+ const angle = directionVec.length() / scene.current.renderWidth * (3 * Math.PI);
1234
+ selectedMotifMeshState.current.forEach((element) => {
1235
+ if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1236
+ element.rotate(axisVec, angle);
1237
+ }
1238
+ });
1239
+ setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1240
+ }
1241
+ }
1242
+ }
1243
+ function onMouseScroll(event) {
1244
+ if (!event.originalEvent || !(event.originalEvent instanceof WheelEvent)) {
1245
+ throw new Error("Tried to trigger Wheel event, but the event info was not present or not of the correct type");
1246
+ }
1247
+ event.originalEvent.preventDefault();
1248
+ if (selectedMotifMeshState.current.size > 0) {
1249
+ const zoomSpeed = 0.1;
1250
+ const zoomDirection = event.originalEvent.deltaY > 0 ? -1 : 1;
1251
+ selectedMotifMeshState.current.forEach((element) => {
1252
+ if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1253
+ const scaleFactor = 1 + zoomDirection * zoomSpeed;
1254
+ element.multiplyScalar(scaleFactor);
1255
+ }
1256
+ });
1257
+ }
1258
+ }
1259
+ function onMouseUp(event) {
1260
+ if (event.type !== Events.EventType.POINTER_UP) {
1261
+ return;
1262
+ }
1263
+ setCursorStyle("auto");
1264
+ }
1265
+ function onMouseDown(event) {
1266
+ if (event.button === 2 && selectedMotifMeshState.current.size > 0) {
1267
+ setCursorStyle("move");
1268
+ } else if (event.button === 1 && event.ctrlKey) {
1269
+ setCursorStyle("crosshair");
1270
+ }
1271
+ }
1272
+ function onKeyboardRotate(event) {
1273
+ if (event.rotationAxis.equals(Vec3.Zero)) {
1274
+ return;
1275
+ }
1276
+ const angle = event.rotationAxis.length() / 500 * (6 * Math.PI);
1277
+ selectedMotifMeshState.current.forEach((element) => {
1278
+ if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1279
+ element.rotate(event.rotationAxis, angle);
1280
+ }
1281
+ });
1282
+ setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1283
+ }
1284
+ function onKeyboardTranslate(event) {
1285
+ if (event.translationDirection.equals(Vec3.Zero)) {
1286
+ return;
1287
+ }
1288
+ event.translationDirection.multiplyScalar(0.5);
1289
+ selectedMotifMeshState.current.forEach((element) => {
1290
+ if (!lockedMotifIdState.current.includes(element.uuid) && !hardLockedMotifIds.includes(element.uuid)) {
1291
+ element.translate(
1292
+ event.translationDirection.x,
1293
+ event.translationDirection.y,
1294
+ event.translationDirection.z
1295
+ );
1296
+ }
1297
+ });
1298
+ }
1299
+ function onKeyboardSelect(event) {
1300
+ if (!event.rotationAxis.equals(Vec3.Zero) || !event.translationDirection.equals(Vec3.Zero)) {
1301
+ return;
1302
+ }
1303
+ if (!/^[0-9]$/.test(event.key)) {
1304
+ return;
1305
+ }
1306
+ const motif = motifs[Number(event.key) - 1];
1307
+ if (selectedMotifMeshState.current.has(motif)) {
1308
+ removeMotif(motif);
1309
+ } else {
1310
+ addMotif(motif);
1311
+ }
1312
+ }
1313
+ const calculatePositions = (numMotifs) => {
1314
+ if (!canvasRef.current) {
1315
+ return [];
1316
+ }
1317
+ const totalWidth = canvasRef.current.width;
1318
+ const subdividedWidth = totalWidth / numMotifs;
1319
+ const halfWidth = subdividedWidth / 2;
1320
+ const totalHeight = canvasRef.current.height;
1321
+ const subdividedHeight = totalHeight * (numMotifs > 3 ? 1 : 0) / 5;
1322
+ const positions = [];
1323
+ for (let i = 0, x = totalWidth / 2; i < numMotifs; i += 1, x -= subdividedWidth) {
1324
+ positions.push(new Vec3(x - halfWidth, subdividedHeight * (i % 2 ? 1 : -1), -100));
1325
+ }
1326
+ return positions;
1327
+ };
1328
+ (0, import_react.useEffect)(() => {
1329
+ const unsubscribe = CanvasDataManager.subscribe("selectedMotifs" /* SELECTED_MOTIFS */, () => {
1330
+ setSelectedmotifIds(CanvasDataManager.selectedMotifIds);
1331
+ });
1332
+ return () => unsubscribe();
1333
+ }, []);
1334
+ (0, import_react.useEffect)(() => {
1335
+ if (CanvasDataManager.selectedMotifIds !== selectedMotifIds) {
1336
+ CanvasDataManager.setSelectedMotifIds(selectedMotifIds);
1337
+ }
1338
+ selectedMotifMeshState.current.clear();
1339
+ motifs.forEach((motif) => {
1340
+ if (selectedMotifIds.has(motif.uuid)) {
1341
+ selectedMotifMeshState.current.add(motif);
1342
+ }
1343
+ });
1344
+ updateGlow();
1345
+ setScoreRMSD(calculateRMSD(Array.from(selectedMotifMeshState.current), motifs));
1346
+ }, [selectedMotifIds]);
1347
+ (0, import_react.useEffect)(() => {
1348
+ const unsubscribe = CanvasDataManager.subscribe("lockedMotifIds" /* LOCKED_MOTIF_IDS */, () => {
1349
+ setLockedMotifIds(CanvasDataManager.lockedMotifIds);
1350
+ });
1351
+ return () => unsubscribe();
1352
+ }, []);
1353
+ (0, import_react.useEffect)(() => {
1354
+ lockedMotifIdState.current = lockedMotifIds;
1355
+ if (CanvasDataManager.lockedMotifIds !== lockedMotifIds) {
1356
+ CanvasDataManager.setLockedMotifIds(lockedMotifIds);
1357
+ }
1358
+ }, [lockedMotifIds]);
1359
+ (0, import_react.useEffect)(() => {
1360
+ hardLockedMotifIds = CanvasDataManager.hardLockedMotifIds;
1361
+ }, [CanvasDataManager.hardLockedMotifIds]);
1362
+ (0, import_react.useEffect)(() => {
1363
+ const unsubscribe = CanvasDataManager.subscribe("scoreRMSD" /* SCORE_RMSD */, () => {
1364
+ setScoreRMSD(CanvasDataManager.scoreRMSD);
1365
+ });
1366
+ return () => unsubscribe();
1367
+ }, []);
1368
+ (0, import_react.useEffect)(() => {
1369
+ if (CanvasDataManager.scoreRMSD !== scoreRMSD) {
1370
+ CanvasDataManager.setScoreRMSD(scoreRMSD);
1371
+ }
1372
+ }, [scoreRMSD]);
1373
+ (0, import_react.useEffect)(() => {
1374
+ const unsubscribe = CanvasDataManager.subscribe("kabschRMSD" /* KABSCH_RMSD */, () => {
1375
+ setKabschRMSD(CanvasDataManager.kabschRMSD);
1376
+ });
1377
+ return () => unsubscribe();
1378
+ }, []);
1379
+ (0, import_react.useEffect)(() => {
1380
+ if (CanvasDataManager.kabschRMSD !== kabschRMSD) {
1381
+ CanvasDataManager.setKabschRMSD(kabschRMSD);
1382
+ }
1383
+ }, [kabschRMSD]);
1384
+ (0, import_react.useEffect)(() => {
1385
+ document.body.style.cursor = cursorStyle;
1386
+ }, [cursorStyle]);
1387
+ (0, import_react.useEffect)(() => {
1388
+ scene.current?.setBackgroundColor(rendererBackgroundColor);
1389
+ }, [rendererBackgroundColor]);
1390
+ (0, import_react.useEffect)(() => {
1391
+ if (!canvasRef.current) return;
1392
+ if (!scene.current) {
1393
+ scene.current = new RenderScene(
1394
+ canvasRef.current,
1395
+ rendererBackgroundColor,
1396
+ cameraPositionZ,
1397
+ rendererSizeIsWindow ? window.innerWidth : rendererWidth,
1398
+ rendererSizeIsWindow ? window.innerHeight : rendererHeight
1399
+ );
1400
+ }
1401
+ const positions = calculatePositions(motifs.length);
1402
+ if (motifs.length > 0) {
1403
+ updateAllMotifs(motifs).then(() => {
1404
+ setKabschRMSD(calculateAllKabschRMSD(motifs));
1405
+ });
1406
+ if (scene.current.children.size !== motifs.length) {
1407
+ motifs.forEach((motifMesh, index) => {
1408
+ if (motifProps[index].position) positions[index] = motifProps[index].position;
1409
+ motifMesh.setPosition(positions[index].x, positions[index].y, positions[index].z);
1410
+ if (motifProps[index].rotation) motifMesh.setQuaternion(motifProps[index].rotation);
1411
+ motifMesh.multiplyScalar(canvasRef.current.width / 250);
1412
+ scene.current?.add(motifMesh);
1413
+ });
1414
+ const eventManager = scene.current?.eventManager;
1415
+ eventManager.on(Events.EventType.OBJECT_SELECTED, onSelectMotif);
1416
+ eventManager.on(Events.EventType.OBJECT_DESELECTED, onDeselectMotif);
1417
+ eventManager.on(Events.EventType.POINTER_MOVE, onMouseMove);
1418
+ eventManager.on(Events.EventType.POINTER_WHEEL, onMouseScroll);
1419
+ eventManager.on(Events.EventType.POINTER_DOWN, onMouseDown);
1420
+ eventManager.on(Events.EventType.POINTER_UP, onMouseUp);
1421
+ eventManager.on(Events.EventType.KEY_DOWN, onKeyboardRotate);
1422
+ eventManager.on(Events.EventType.KEY_DOWN, onKeyboardTranslate);
1423
+ eventManager.on(Events.EventType.KEY_DOWN, onKeyboardSelect);
1424
+ if (customEventProps) {
1425
+ customEventProps.forEach((customEventProp) => {
1426
+ eventManager.on(
1427
+ customEventProp.eventType,
1428
+ customEventProp.callback
1429
+ );
1430
+ });
1431
+ }
1432
+ } else {
1433
+ motifs.forEach((motifMesh) => {
1434
+ scene.current?.add(motifMesh);
1435
+ });
1436
+ }
1437
+ }
1438
+ scene.current?.start();
1439
+ }, [rendererWidth, rendererHeight, rendererSizeIsWindow, motifProps]);
1440
+ return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("canvas", { ref: canvasRef }));
1441
+ }
1442
+ // Annotate the CommonJS export names for ESM import in node:
1443
+ 0 && (module.exports = {
1444
+ Canvas,
1445
+ CanvasAttributeTypes,
1446
+ CanvasDataManager,
1447
+ EventManager,
1448
+ Events,
1449
+ Group,
1450
+ Matrix4,
1451
+ MeshObject,
1452
+ Motif,
1453
+ Quat,
1454
+ RenderScene,
1455
+ Residue,
1456
+ Vec3,
1457
+ calculateAllKabschRMSD,
1458
+ calculateRMSD,
1459
+ calculateRMSDSlide,
1460
+ getMotif,
1461
+ getPoints,
1462
+ getRMSD,
1463
+ kabschSlidingWindow,
1464
+ rotateAllPoints,
1465
+ updateAllMotifs
1466
+ });