@cornerstonejs/core 0.41.1 → 0.42.0
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/cjs/RenderingEngine/RenderingEngine.js +1 -0
- package/dist/cjs/RenderingEngine/RenderingEngine.js.map +1 -1
- package/dist/cjs/RenderingEngine/StackViewport.js +4 -2
- package/dist/cjs/RenderingEngine/StackViewport.js.map +1 -1
- package/dist/cjs/RenderingEngine/Viewport.d.ts +5 -1
- package/dist/cjs/RenderingEngine/Viewport.js +66 -0
- package/dist/cjs/RenderingEngine/Viewport.js.map +1 -1
- package/dist/cjs/enums/Events.d.ts +1 -0
- package/dist/cjs/enums/Events.js +1 -0
- package/dist/cjs/enums/Events.js.map +1 -1
- package/dist/cjs/types/EventTypes.d.ts +9 -1
- package/dist/cjs/types/IViewport.d.ts +3 -0
- package/dist/cjs/types/ViewportInputOptions.d.ts +2 -0
- package/dist/cjs/types/displayArea.d.ts +9 -0
- package/dist/cjs/types/displayArea.js +3 -0
- package/dist/cjs/types/displayArea.js.map +1 -0
- package/dist/cjs/types/index.d.ts +2 -1
- package/dist/cjs/utilities/isEqual.d.ts +1 -1
- package/dist/cjs/utilities/isEqual.js +30 -4
- package/dist/cjs/utilities/isEqual.js.map +1 -1
- package/dist/esm/RenderingEngine/RenderingEngine.js +1 -0
- package/dist/esm/RenderingEngine/RenderingEngine.js.map +1 -1
- package/dist/esm/RenderingEngine/StackViewport.js +4 -2
- package/dist/esm/RenderingEngine/StackViewport.js.map +1 -1
- package/dist/esm/RenderingEngine/Viewport.d.ts +5 -1
- package/dist/esm/RenderingEngine/Viewport.js +63 -0
- package/dist/esm/RenderingEngine/Viewport.js.map +1 -1
- package/dist/esm/enums/Events.d.ts +1 -0
- package/dist/esm/enums/Events.js +1 -0
- package/dist/esm/enums/Events.js.map +1 -1
- package/dist/esm/types/EventTypes.d.ts +9 -1
- package/dist/esm/types/IViewport.d.ts +3 -0
- package/dist/esm/types/ViewportInputOptions.d.ts +2 -0
- package/dist/esm/types/displayArea.d.ts +9 -0
- package/dist/esm/types/displayArea.js +2 -0
- package/dist/esm/types/displayArea.js.map +1 -0
- package/dist/esm/types/index.d.ts +2 -1
- package/dist/esm/utilities/isEqual.d.ts +1 -1
- package/dist/esm/utilities/isEqual.js +30 -4
- package/dist/esm/utilities/isEqual.js.map +1 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +2 -2
- package/src/RenderingEngine/RenderingEngine.ts +1 -0
- package/src/RenderingEngine/StackViewport.ts +6 -2
- package/src/RenderingEngine/Viewport.ts +105 -3
- package/src/enums/Events.ts +7 -0
- package/src/types/EventTypes.ts +23 -0
- package/src/types/IViewport.ts +9 -0
- package/src/types/ViewportInputOptions.ts +3 -1
- package/src/types/displayArea.ts +10 -0
- package/src/types/index.ts +2 -0
- package/src/utilities/isEqual.ts +60 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cornerstonejs/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/umd/index.js",
|
|
6
6
|
"types": "dist/esm/index.d.ts",
|
|
@@ -51,5 +51,5 @@
|
|
|
51
51
|
"type": "individual",
|
|
52
52
|
"url": "https://ohif.org/donate"
|
|
53
53
|
},
|
|
54
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "1ddefd3cf71aff541732dd6879dd282866e37217"
|
|
55
55
|
}
|
|
@@ -1536,11 +1536,15 @@ class StackViewport extends Viewport implements IStackViewport {
|
|
|
1536
1536
|
const columnCosines = direction.slice(3, 6);
|
|
1537
1537
|
const dataType = imageData.getPointData().getScalars().getDataType();
|
|
1538
1538
|
|
|
1539
|
+
// using epsilon comparison for float numbers comparison.
|
|
1540
|
+
const isSameXSpacing = isEqual(xSpacing, image.rowPixelSpacing);
|
|
1541
|
+
const isSameYSpacing = isEqual(ySpacing, image.columnPixelSpacing);
|
|
1542
|
+
|
|
1539
1543
|
// using spacing, size, and direction only for now
|
|
1540
1544
|
return (
|
|
1541
|
-
(
|
|
1545
|
+
(isSameXSpacing ||
|
|
1542
1546
|
(image.rowPixelSpacing === null && xSpacing === 1.0)) &&
|
|
1543
|
-
(
|
|
1547
|
+
(isSameYSpacing ||
|
|
1544
1548
|
(image.columnPixelSpacing === null && ySpacing === 1.0)) &&
|
|
1545
1549
|
xVoxels === image.columns &&
|
|
1546
1550
|
yVoxels === image.rows &&
|
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
Point3,
|
|
22
22
|
FlipDirection,
|
|
23
23
|
EventTypes,
|
|
24
|
+
DisplayArea,
|
|
24
25
|
} from '../types';
|
|
25
26
|
import type { ViewportInput, IViewport } from '../types/IViewport';
|
|
26
27
|
import type { vtkSlabCamera } from './vtkClasses/vtkSlabCamera';
|
|
@@ -62,7 +63,7 @@ class Viewport implements IViewport {
|
|
|
62
63
|
_actors: Map<string, any>;
|
|
63
64
|
/** Default options for the viewport which includes orientation, viewPlaneNormal and backgroundColor */
|
|
64
65
|
readonly defaultOptions: any;
|
|
65
|
-
/** options for the viewport which includes orientation axis and
|
|
66
|
+
/** options for the viewport which includes orientation axis, backgroundColor and displayArea */
|
|
66
67
|
options: ViewportInputOptions;
|
|
67
68
|
private _suppressCameraModifiedEvents = false;
|
|
68
69
|
/** A flag representing if viewport methods should fire events or not */
|
|
@@ -72,6 +73,10 @@ class Viewport implements IViewport {
|
|
|
72
73
|
* the relative pan/zoom
|
|
73
74
|
*/
|
|
74
75
|
protected initialCamera: ICamera;
|
|
76
|
+
/** The camera that is defined for resetting displayArea to ensure absolute displayArea
|
|
77
|
+
* settings
|
|
78
|
+
*/
|
|
79
|
+
private fitToCanvasCamera: ICamera;
|
|
75
80
|
|
|
76
81
|
constructor(props: ViewportInput) {
|
|
77
82
|
this.id = props.id;
|
|
@@ -155,7 +160,9 @@ class Viewport implements IViewport {
|
|
|
155
160
|
|
|
156
161
|
// TODO When this is needed we need to move the camera position.
|
|
157
162
|
// We can steal some logic from the tools we build to do this.
|
|
158
|
-
|
|
163
|
+
if (this.options?.displayArea) {
|
|
164
|
+
this.setDisplayArea(this.options?.displayArea);
|
|
165
|
+
}
|
|
159
166
|
if (immediate) {
|
|
160
167
|
this.render();
|
|
161
168
|
}
|
|
@@ -528,6 +535,80 @@ class Viewport implements IViewport {
|
|
|
528
535
|
return intersections;
|
|
529
536
|
}
|
|
530
537
|
|
|
538
|
+
/**
|
|
539
|
+
* Sets the camera to an initial bounds. If
|
|
540
|
+
* resetPan and resetZoom are true it places the focal point at the center of
|
|
541
|
+
* the volume (or slice); otherwise, only the camera zoom and camera Pan or Zoom
|
|
542
|
+
* is reset for the current view.
|
|
543
|
+
* @param displayArea - The display area of interest.
|
|
544
|
+
* @param suppressEvents - If true, don't fire displayArea event.
|
|
545
|
+
*/
|
|
546
|
+
public setDisplayArea(
|
|
547
|
+
displayArea: DisplayArea,
|
|
548
|
+
suppressEvents = false
|
|
549
|
+
): void {
|
|
550
|
+
const { storeAsInitialCamera } = displayArea;
|
|
551
|
+
|
|
552
|
+
// make calculations relative to the fitToCanvasCamera view
|
|
553
|
+
this.setCamera(this.fitToCanvasCamera, false);
|
|
554
|
+
|
|
555
|
+
const { imageArea, imageCanvasPoint } = displayArea;
|
|
556
|
+
|
|
557
|
+
if (imageArea) {
|
|
558
|
+
const [areaX, areaY] = imageArea;
|
|
559
|
+
const zoom = Math.min(this.getZoom() / areaX, this.getZoom() / areaY);
|
|
560
|
+
this.setZoom(zoom, storeAsInitialCamera);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// getting the image info
|
|
564
|
+
const imageData = this.getDefaultImageData();
|
|
565
|
+
if (imageCanvasPoint && imageData) {
|
|
566
|
+
const { imagePoint, canvasPoint } = imageCanvasPoint;
|
|
567
|
+
const [canvasX, canvasY] = canvasPoint;
|
|
568
|
+
const devicePixelRatio = window?.devicePixelRatio || 1;
|
|
569
|
+
const validateCanvasPanX = this.sWidth / devicePixelRatio;
|
|
570
|
+
const validateCanvasPanY = this.sHeight / devicePixelRatio;
|
|
571
|
+
const canvasPanX = validateCanvasPanX * (canvasX - 0.5);
|
|
572
|
+
const canvasPanY = validateCanvasPanY * (canvasY - 0.5);
|
|
573
|
+
|
|
574
|
+
const dimensions = imageData.getDimensions();
|
|
575
|
+
const canvasZero = this.worldToCanvas([0, 0, 0]);
|
|
576
|
+
const canvasEdge = this.worldToCanvas(dimensions);
|
|
577
|
+
const canvasImage = [
|
|
578
|
+
canvasEdge[0] - canvasZero[0],
|
|
579
|
+
canvasEdge[1] - canvasZero[1],
|
|
580
|
+
];
|
|
581
|
+
const [imgWidth, imgHeight] = canvasImage;
|
|
582
|
+
const [imageX, imageY] = imagePoint;
|
|
583
|
+
const imagePanX = imgWidth * (0.5 - imageX);
|
|
584
|
+
const imagePanY = imgHeight * (0.5 - imageY);
|
|
585
|
+
|
|
586
|
+
const newPositionX = imagePanX + canvasPanX;
|
|
587
|
+
const newPositionY = imagePanY + canvasPanY;
|
|
588
|
+
|
|
589
|
+
const deltaPoint2: Point2 = [newPositionX, newPositionY];
|
|
590
|
+
this.setPan(deltaPoint2, storeAsInitialCamera);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (storeAsInitialCamera) {
|
|
594
|
+
this.options.displayArea = displayArea;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (!suppressEvents) {
|
|
598
|
+
const eventDetail: EventTypes.DisplayAreaModifiedEventDetail = {
|
|
599
|
+
viewportId: this.id,
|
|
600
|
+
displayArea: displayArea,
|
|
601
|
+
storeAsInitialCamera: storeAsInitialCamera,
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
triggerEvent(this.element, Events.DISPLAY_AREA_MODIFIED, eventDetail);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
public getDisplayArea(): DisplayArea | undefined {
|
|
609
|
+
return this.options?.displayArea;
|
|
610
|
+
}
|
|
611
|
+
|
|
531
612
|
/**
|
|
532
613
|
* Resets the camera based on the rendering volume(s) bounds. If
|
|
533
614
|
* resetPan and resetZoom are true it places the focal point at the center of
|
|
@@ -556,7 +637,6 @@ class Viewport implements IViewport {
|
|
|
556
637
|
});
|
|
557
638
|
|
|
558
639
|
const previousCamera = _cloneDeep(this.getCamera());
|
|
559
|
-
|
|
560
640
|
const bounds = renderer.computeVisiblePropBounds();
|
|
561
641
|
const focalPoint = <Point3>[0, 0, 0];
|
|
562
642
|
const imageData = this.getDefaultImageData();
|
|
@@ -673,6 +753,8 @@ class Viewport implements IViewport {
|
|
|
673
753
|
|
|
674
754
|
const modifiedCamera = _cloneDeep(this.getCamera());
|
|
675
755
|
|
|
756
|
+
this.setFitToCanvasCamera(_cloneDeep(this.getCamera()));
|
|
757
|
+
|
|
676
758
|
if (storeAsInitialCamera) {
|
|
677
759
|
this.setInitialCamera(modifiedCamera);
|
|
678
760
|
}
|
|
@@ -688,6 +770,16 @@ class Viewport implements IViewport {
|
|
|
688
770
|
|
|
689
771
|
this.triggerCameraModifiedEventIfNecessary(previousCamera, modifiedCamera);
|
|
690
772
|
|
|
773
|
+
if (
|
|
774
|
+
imageData &&
|
|
775
|
+
this.options?.displayArea &&
|
|
776
|
+
resetZoom &&
|
|
777
|
+
resetPan &&
|
|
778
|
+
resetToCenter
|
|
779
|
+
) {
|
|
780
|
+
this.setDisplayArea(this.options?.displayArea);
|
|
781
|
+
}
|
|
782
|
+
|
|
691
783
|
return true;
|
|
692
784
|
}
|
|
693
785
|
|
|
@@ -701,6 +793,16 @@ class Viewport implements IViewport {
|
|
|
701
793
|
this.initialCamera = camera;
|
|
702
794
|
}
|
|
703
795
|
|
|
796
|
+
/**
|
|
797
|
+
* Sets the provided camera as the displayArea camera.
|
|
798
|
+
* This allows computing differences applied later as compared to the initial
|
|
799
|
+
* position, for things like zoom and pan.
|
|
800
|
+
* @param camera - to store as the initial value.
|
|
801
|
+
*/
|
|
802
|
+
protected setFitToCanvasCamera(camera: ICamera): void {
|
|
803
|
+
this.fitToCanvasCamera = camera;
|
|
804
|
+
}
|
|
805
|
+
|
|
704
806
|
/**
|
|
705
807
|
* Helper function to return the current canvas pan value.
|
|
706
808
|
*
|
package/src/enums/Events.ts
CHANGED
|
@@ -38,6 +38,13 @@ enum Events {
|
|
|
38
38
|
* and see what event detail is included in {@link EventTypes.VoiModifiedEventDetail | VoiModified Event Detail }
|
|
39
39
|
*/
|
|
40
40
|
VOI_MODIFIED = 'CORNERSTONE_VOI_MODIFIED',
|
|
41
|
+
/**
|
|
42
|
+
* Triggers on the HTML element when viewport modifies its display area
|
|
43
|
+
*
|
|
44
|
+
* Make use of {@link EventTypes.DisplayAreaModifiedEvent | DisplayAreaModified Event Type } for typing your event listeners for DISPLAY_AREA_MODIFIED event,
|
|
45
|
+
* and see what event detail is included in {@link EventTypes.DisplayAreaModifiedEventDetail | DisplayAreaModified Event Detail }
|
|
46
|
+
*/
|
|
47
|
+
DISPLAY_AREA_MODIFIED = 'CORNERSTONE_DISPLAY_AREA_MODIFIED',
|
|
41
48
|
/**
|
|
42
49
|
* Triggers on the eventTarget when the element is disabled
|
|
43
50
|
*
|
package/src/types/EventTypes.ts
CHANGED
|
@@ -8,6 +8,8 @@ import type IImage from './IImage';
|
|
|
8
8
|
import type IImageVolume from './IImageVolume';
|
|
9
9
|
import type { VOIRange } from './voi';
|
|
10
10
|
import type VOILUTFunctionType from '../enums/VOILUTFunctionType';
|
|
11
|
+
import type DisplayArea from './displayArea';
|
|
12
|
+
|
|
11
13
|
/**
|
|
12
14
|
* CAMERA_MODIFIED Event's data
|
|
13
15
|
*/
|
|
@@ -40,6 +42,20 @@ type VoiModifiedEventDetail = {
|
|
|
40
42
|
VOILUTFunction?: VOILUTFunctionType;
|
|
41
43
|
};
|
|
42
44
|
|
|
45
|
+
/**
|
|
46
|
+
* DISPLAY_AREA_MODIFIED Event's data
|
|
47
|
+
*/
|
|
48
|
+
type DisplayAreaModifiedEventDetail = {
|
|
49
|
+
/** Viewport Unique ID in the renderingEngine */
|
|
50
|
+
viewportId: string;
|
|
51
|
+
/** new display area */
|
|
52
|
+
displayArea: DisplayArea;
|
|
53
|
+
/** Unique ID for the volume in the cache */
|
|
54
|
+
volumeId?: string;
|
|
55
|
+
/** Whether displayArea was stored as initial view */
|
|
56
|
+
storeAsInitialCamera?: boolean;
|
|
57
|
+
};
|
|
58
|
+
|
|
43
59
|
/**
|
|
44
60
|
* ELEMENT_DISABLED Event's data
|
|
45
61
|
*/
|
|
@@ -259,6 +275,11 @@ type CameraModifiedEvent = CustomEventType<CameraModifiedEventDetail>;
|
|
|
259
275
|
*/
|
|
260
276
|
type VoiModifiedEvent = CustomEventType<VoiModifiedEventDetail>;
|
|
261
277
|
|
|
278
|
+
/**
|
|
279
|
+
* DISPLAY_AREA_MODIFIED Event type
|
|
280
|
+
*/
|
|
281
|
+
type DisplayAreaModifiedEvent = CustomEventType<DisplayAreaModifiedEventDetail>;
|
|
282
|
+
|
|
262
283
|
/**
|
|
263
284
|
* ELEMENT_DISABLED Event type
|
|
264
285
|
*/
|
|
@@ -362,6 +383,8 @@ export type {
|
|
|
362
383
|
CameraModifiedEvent,
|
|
363
384
|
VoiModifiedEvent,
|
|
364
385
|
VoiModifiedEventDetail,
|
|
386
|
+
DisplayAreaModifiedEvent,
|
|
387
|
+
DisplayAreaModifiedEventDetail,
|
|
365
388
|
ElementDisabledEvent,
|
|
366
389
|
ElementDisabledEventDetail,
|
|
367
390
|
ElementEnabledEvent,
|
package/src/types/IViewport.ts
CHANGED
|
@@ -4,6 +4,7 @@ import Point3 from './Point3';
|
|
|
4
4
|
import ViewportInputOptions from './ViewportInputOptions';
|
|
5
5
|
import { ActorEntry } from './IActor';
|
|
6
6
|
import ViewportType from '../enums/ViewportType';
|
|
7
|
+
import DisplayArea from './displayArea';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Viewport interface for cornerstone viewports
|
|
@@ -73,6 +74,14 @@ interface IViewport {
|
|
|
73
74
|
render(): void;
|
|
74
75
|
/** set options for the viewport */
|
|
75
76
|
setOptions(options: ViewportInputOptions, immediate: boolean): void;
|
|
77
|
+
/** set displayArea for the viewport */
|
|
78
|
+
setDisplayArea(
|
|
79
|
+
displayArea: DisplayArea,
|
|
80
|
+
callResetCamera?: boolean,
|
|
81
|
+
suppressEvents?: boolean
|
|
82
|
+
);
|
|
83
|
+
/** returns the displayArea */
|
|
84
|
+
getDisplayArea(): DisplayArea | undefined;
|
|
76
85
|
/** reset camera and options*/
|
|
77
86
|
reset(immediate: boolean): void;
|
|
78
87
|
/** returns the canvas */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { OrientationAxis } from '../enums';
|
|
2
2
|
import OrientationVectors from './OrientationVectors';
|
|
3
|
-
|
|
3
|
+
import DisplayArea from './displayArea';
|
|
4
4
|
/**
|
|
5
5
|
* This type defines the shape of viewport input options, so we can throw when it is incorrect.
|
|
6
6
|
*/
|
|
@@ -9,6 +9,8 @@ type ViewportInputOptions = {
|
|
|
9
9
|
background?: [number, number, number];
|
|
10
10
|
/** orientation of the viewport which can be either an Enum for axis Enums.OrientationAxis.[AXIAL|SAGITTAL|CORONAL|DEFAULT] or an object with viewPlaneNormal and viewUp */
|
|
11
11
|
orientation?: OrientationAxis | OrientationVectors;
|
|
12
|
+
/** displayArea of interest */
|
|
13
|
+
displayArea?: DisplayArea;
|
|
12
14
|
/** whether the events should be suppressed and not fired*/
|
|
13
15
|
suppressEvents?: boolean;
|
|
14
16
|
/**
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type DisplayArea = {
|
|
2
|
+
imageArea: [number, number]; // areaX, areaY
|
|
3
|
+
imageCanvasPoint: {
|
|
4
|
+
imagePoint: [number, number]; // imageX, imageY
|
|
5
|
+
canvasPoint: [number, number]; // canvasX, canvasY
|
|
6
|
+
};
|
|
7
|
+
storeAsInitialCamera: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default DisplayArea;
|
package/src/types/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type IEnabledElement from './IEnabledElement';
|
|
|
5
5
|
import type ICache from './ICache';
|
|
6
6
|
import type { IVolume, VolumeScalarData } from './IVolume';
|
|
7
7
|
import type { VOI, VOIRange } from './voi';
|
|
8
|
+
import type DisplayArea from './displayArea';
|
|
8
9
|
import type ImageLoaderFn from './ImageLoaderFn';
|
|
9
10
|
import type IImageVolume from './IImageVolume';
|
|
10
11
|
import type IDynamicImageVolume from './IDynamicImageVolume';
|
|
@@ -125,6 +126,7 @@ export type {
|
|
|
125
126
|
ViewportInputOptions,
|
|
126
127
|
VOIRange,
|
|
127
128
|
VOI,
|
|
129
|
+
DisplayArea,
|
|
128
130
|
FlipDirection,
|
|
129
131
|
ICachedImage,
|
|
130
132
|
ICachedVolume,
|
package/src/utilities/isEqual.ts
CHANGED
|
@@ -1,27 +1,74 @@
|
|
|
1
|
+
function areNumbersEqualWithTolerance(
|
|
2
|
+
num1: number,
|
|
3
|
+
num2: number,
|
|
4
|
+
tolerance: number
|
|
5
|
+
): boolean {
|
|
6
|
+
return Math.abs(num1 - num2) <= tolerance;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function areArraysEqual(
|
|
10
|
+
arr1: ArrayLike<number>,
|
|
11
|
+
arr2: ArrayLike<number>,
|
|
12
|
+
tolerance = 1e-5
|
|
13
|
+
): boolean {
|
|
14
|
+
if (arr1.length !== arr2.length) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
for (let i = 0; i < arr1.length; i++) {
|
|
19
|
+
if (!areNumbersEqualWithTolerance(arr1[i], arr2[i], tolerance)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isNumberType(value: any): value is number {
|
|
28
|
+
return typeof value === 'number';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isNumberArrayLike(value: any): value is ArrayLike<number> {
|
|
32
|
+
return 'length' in value && typeof value[0] === 'number';
|
|
33
|
+
}
|
|
34
|
+
|
|
1
35
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
36
|
+
* Returns whether two values are equal or not, based on epsilon comparison.
|
|
37
|
+
* For array comparison, it does NOT strictly compare them but only compare its values.
|
|
38
|
+
* It can compare array of numbers and also typed array. Otherwise it will just return false.
|
|
4
39
|
*
|
|
5
|
-
* @param v1 - The first
|
|
6
|
-
* @param v2 - The second
|
|
40
|
+
* @param v1 - The first value to compare
|
|
41
|
+
* @param v2 - The second value to compare
|
|
7
42
|
* @param tolerance - The acceptable tolerance, the default is 0.00001
|
|
8
43
|
*
|
|
9
44
|
* @returns True if the two values are within the tolerance levels.
|
|
10
45
|
*/
|
|
11
|
-
export default function isEqual(
|
|
12
|
-
v1:
|
|
13
|
-
v2:
|
|
46
|
+
export default function isEqual<ValueType>(
|
|
47
|
+
v1: ValueType,
|
|
48
|
+
v2: ValueType,
|
|
14
49
|
tolerance = 1e-5
|
|
15
50
|
): boolean {
|
|
16
|
-
|
|
51
|
+
// values must be the same type or not null
|
|
52
|
+
if (typeof v1 !== typeof v2 || v1 === null || v2 === null) {
|
|
17
53
|
return false;
|
|
18
54
|
}
|
|
19
55
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
56
|
+
// typeof object must have same constructor
|
|
57
|
+
if (
|
|
58
|
+
typeof v1 === 'object' &&
|
|
59
|
+
typeof v2 === 'object' &&
|
|
60
|
+
v1.constructor !== v2.constructor
|
|
61
|
+
) {
|
|
62
|
+
return false;
|
|
24
63
|
}
|
|
25
64
|
|
|
26
|
-
|
|
65
|
+
if (isNumberType(v1) && isNumberType(v2)) {
|
|
66
|
+
return areNumbersEqualWithTolerance(v1, v2, tolerance);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (isNumberArrayLike(v1) && isNumberArrayLike(v2)) {
|
|
70
|
+
return areArraysEqual(v1, v2, tolerance);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return false;
|
|
27
74
|
}
|