@cornerstonejs/tools 4.21.6 → 4.21.8
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/esm/tools/OrientationControllerTool.d.ts +2 -0
- package/dist/esm/tools/OrientationControllerTool.js +60 -31
- package/dist/esm/tools/VolumeCroppingTool.d.ts +2 -0
- package/dist/esm/tools/VolumeCroppingTool.js +23 -1
- package/dist/esm/utilities/interactionDragCoordinator.d.ts +5 -0
- package/dist/esm/utilities/interactionDragCoordinator.js +16 -0
- package/dist/esm/utilities/segmentation/utilsForWorker.js +4 -0
- package/dist/esm/utilities/vtkjs/OrientationControllerWidget/index.d.ts +5 -3
- package/dist/esm/utilities/vtkjs/OrientationControllerWidget/index.js +335 -48
- package/dist/esm/utilities/vtkjs/RhombicuboctahedronSource/index.js +25 -25
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +4 -4
|
@@ -4,6 +4,8 @@ declare class OrientationControllerTool extends BaseTool {
|
|
|
4
4
|
private widget;
|
|
5
5
|
private resizeObservers;
|
|
6
6
|
private cameraHandlers;
|
|
7
|
+
private animationFrameHandles;
|
|
8
|
+
private animationTokens;
|
|
7
9
|
constructor(toolProps?: {}, defaultToolProps?: {
|
|
8
10
|
supportedInteractionTypes: string[];
|
|
9
11
|
configuration: {
|
|
@@ -84,6 +84,8 @@ class OrientationControllerTool extends BaseTool {
|
|
|
84
84
|
this.widget = new vtkOrientationControllerWidget();
|
|
85
85
|
this.resizeObservers = new Map();
|
|
86
86
|
this.cameraHandlers = new Map();
|
|
87
|
+
this.animationFrameHandles = new Map();
|
|
88
|
+
this.animationTokens = new Map();
|
|
87
89
|
this._getViewportsInfo = () => {
|
|
88
90
|
const viewports = getToolGroup(this.toolGroupId)?.viewportsInfo;
|
|
89
91
|
return viewports || [];
|
|
@@ -177,6 +179,7 @@ class OrientationControllerTool extends BaseTool {
|
|
|
177
179
|
}
|
|
178
180
|
const volumeViewport = viewport;
|
|
179
181
|
this.widget.positionActors(volumeViewport, actors, this.getPositionConfig());
|
|
182
|
+
this.widget.syncOverlayViewport(viewportId, volumeViewport);
|
|
180
183
|
viewport.render();
|
|
181
184
|
};
|
|
182
185
|
}
|
|
@@ -242,6 +245,9 @@ class OrientationControllerTool extends BaseTool {
|
|
|
242
245
|
this.resizeObservers.forEach((observer) => observer.disconnect());
|
|
243
246
|
this.resizeObservers.clear();
|
|
244
247
|
this.cameraHandlers.clear();
|
|
248
|
+
this.animationFrameHandles.forEach((handle) => cancelAnimationFrame(handle));
|
|
249
|
+
this.animationFrameHandles.clear();
|
|
250
|
+
this.animationTokens.clear();
|
|
245
251
|
}
|
|
246
252
|
createAnnotatedRhombActor() {
|
|
247
253
|
const faceColors = this.getFaceColors();
|
|
@@ -255,6 +261,9 @@ class OrientationControllerTool extends BaseTool {
|
|
|
255
261
|
});
|
|
256
262
|
}
|
|
257
263
|
addMarkerToViewport(viewportId, renderingEngineId) {
|
|
264
|
+
if (this.widget.getActors(viewportId)) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
258
267
|
const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
|
|
259
268
|
if (!enabledElement) {
|
|
260
269
|
console.warn('OrientationControllerTool: No enabled element found');
|
|
@@ -295,8 +304,8 @@ class OrientationControllerTool extends BaseTool {
|
|
|
295
304
|
}
|
|
296
305
|
},
|
|
297
306
|
onFaceHover: (result) => {
|
|
298
|
-
if (result
|
|
299
|
-
this.widget.highlightFace(result.pickedActor, result.cellId, volumeViewport,
|
|
307
|
+
if (result) {
|
|
308
|
+
this.widget.highlightFace(result.pickedActor, result.cellId, volumeViewport, result.actorIndex === 0);
|
|
300
309
|
}
|
|
301
310
|
else {
|
|
302
311
|
this.widget.clearHighlight();
|
|
@@ -335,6 +344,14 @@ class OrientationControllerTool extends BaseTool {
|
|
|
335
344
|
viewport.render();
|
|
336
345
|
}
|
|
337
346
|
animateCameraToOrientation(viewport, targetViewPlaneNormal, targetViewUp) {
|
|
347
|
+
const viewportId = viewport.id;
|
|
348
|
+
const existingHandle = this.animationFrameHandles.get(viewportId);
|
|
349
|
+
if (existingHandle !== undefined) {
|
|
350
|
+
cancelAnimationFrame(existingHandle);
|
|
351
|
+
this.animationFrameHandles.delete(viewportId);
|
|
352
|
+
}
|
|
353
|
+
const nextToken = (this.animationTokens.get(viewportId) ?? 0) + 1;
|
|
354
|
+
this.animationTokens.set(viewportId, nextToken);
|
|
338
355
|
const keepOrientationUp = this.configuration.keepOrientationUp !== false;
|
|
339
356
|
const renderer = viewport.getRenderer();
|
|
340
357
|
const camera = renderer.getActiveCamera();
|
|
@@ -346,34 +363,33 @@ class OrientationControllerTool extends BaseTool {
|
|
|
346
363
|
vec3.cross(startRight, startUp, startForward);
|
|
347
364
|
vec3.normalize(startRight, startRight);
|
|
348
365
|
const startMatrix = mat4.fromValues(startRight[0], startRight[1], startRight[2], 0, startUp[0], startUp[1], startUp[2], 0, startForward[0], startForward[1], startForward[2], 0, 0, 0, 0, 1);
|
|
366
|
+
const targetForward = vec3.normalize(vec3.create(), targetViewPlaneNormal);
|
|
349
367
|
let targetUp;
|
|
350
368
|
if (keepOrientationUp) {
|
|
351
369
|
targetUp = vec3.fromValues(targetViewUp[0], targetViewUp[1], targetViewUp[2]);
|
|
352
370
|
}
|
|
353
371
|
else {
|
|
354
|
-
const
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
const dot = vec3.dot(currentUp, normalizedForward);
|
|
372
|
+
const currentFwd = vec3.normalize(vec3.create(), startForward);
|
|
373
|
+
const rotQuat = quat.create();
|
|
374
|
+
quat.rotationTo(rotQuat, currentFwd, targetForward);
|
|
358
375
|
targetUp = vec3.create();
|
|
359
|
-
vec3.
|
|
376
|
+
vec3.transformQuat(targetUp, startUp, rotQuat);
|
|
360
377
|
vec3.normalize(targetUp, targetUp);
|
|
361
|
-
if (vec3.length(targetUp) < 0.001) {
|
|
362
|
-
if (Math.abs(normalizedForward[2]) < 0.9) {
|
|
363
|
-
targetUp = vec3.fromValues(0, 0, 1);
|
|
364
|
-
}
|
|
365
|
-
else {
|
|
366
|
-
targetUp = vec3.fromValues(0, 1, 0);
|
|
367
|
-
}
|
|
368
|
-
const dot2 = vec3.dot(targetUp, normalizedForward);
|
|
369
|
-
vec3.scaleAndAdd(targetUp, targetUp, normalizedForward, -dot2);
|
|
370
|
-
vec3.normalize(targetUp, targetUp);
|
|
371
|
-
}
|
|
372
378
|
}
|
|
379
|
+
const upDotForward = vec3.dot(targetUp, targetForward);
|
|
380
|
+
vec3.scaleAndAdd(targetUp, targetUp, targetForward, -upDotForward);
|
|
381
|
+
if (vec3.length(targetUp) < 0.0001) {
|
|
382
|
+
targetUp = vec3.clone(startUp);
|
|
383
|
+
const fallbackDot = vec3.dot(targetUp, targetForward);
|
|
384
|
+
vec3.scaleAndAdd(targetUp, targetUp, targetForward, -fallbackDot);
|
|
385
|
+
}
|
|
386
|
+
vec3.normalize(targetUp, targetUp);
|
|
373
387
|
const targetRight = vec3.create();
|
|
374
|
-
vec3.cross(targetRight, targetUp,
|
|
388
|
+
vec3.cross(targetRight, targetUp, targetForward);
|
|
375
389
|
vec3.normalize(targetRight, targetRight);
|
|
376
|
-
|
|
390
|
+
vec3.cross(targetUp, targetForward, targetRight);
|
|
391
|
+
vec3.normalize(targetUp, targetUp);
|
|
392
|
+
const targetMatrix = mat4.fromValues(targetRight[0], targetRight[1], targetRight[2], 0, targetUp[0], targetUp[1], targetUp[2], 0, targetForward[0], targetForward[1], targetForward[2], 0, 0, 0, 0, 1);
|
|
377
393
|
const startQuat = mat4.getRotation(quat.create(), startMatrix);
|
|
378
394
|
const targetQuat = mat4.getRotation(quat.create(), targetMatrix);
|
|
379
395
|
let dotProduct = quat.dot(startQuat, targetQuat);
|
|
@@ -385,13 +401,21 @@ class OrientationControllerTool extends BaseTool {
|
|
|
385
401
|
if (dotProduct > threshold) {
|
|
386
402
|
return;
|
|
387
403
|
}
|
|
388
|
-
const steps = 10;
|
|
389
404
|
const duration = 150;
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
405
|
+
const animationStart = performance.now();
|
|
406
|
+
const finalNormal = [
|
|
407
|
+
targetForward[0],
|
|
408
|
+
targetForward[1],
|
|
409
|
+
targetForward[2],
|
|
410
|
+
];
|
|
411
|
+
const finalUp = [targetUp[0], targetUp[1], targetUp[2]];
|
|
412
|
+
const animate = (now) => {
|
|
413
|
+
if (this.animationTokens.get(viewportId) !== nextToken) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const elapsed = now - animationStart;
|
|
417
|
+
const t = Math.min(1, elapsed / duration);
|
|
418
|
+
const isLastStep = t >= 1;
|
|
395
419
|
const easedT = t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
396
420
|
const interpolatedQuat = quat.create();
|
|
397
421
|
quat.slerp(interpolatedQuat, startQuat, targetQuat, easedT);
|
|
@@ -400,16 +424,21 @@ class OrientationControllerTool extends BaseTool {
|
|
|
400
424
|
const interpolatedForward = interpolatedMatrix.slice(8, 11);
|
|
401
425
|
const interpolatedUp = interpolatedMatrix.slice(4, 7);
|
|
402
426
|
viewport.setCamera({
|
|
403
|
-
viewPlaneNormal: interpolatedForward,
|
|
404
|
-
viewUp: interpolatedUp,
|
|
427
|
+
viewPlaneNormal: isLastStep ? finalNormal : interpolatedForward,
|
|
428
|
+
viewUp: isLastStep ? finalUp : interpolatedUp,
|
|
405
429
|
});
|
|
406
430
|
viewport.resetCamera(ANIMATE_RESET_CAMERA_OPTIONS);
|
|
407
431
|
viewport.render();
|
|
408
|
-
if (
|
|
409
|
-
|
|
432
|
+
if (!isLastStep) {
|
|
433
|
+
const handle = requestAnimationFrame(animate);
|
|
434
|
+
this.animationFrameHandles.set(viewportId, handle);
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
this.animationFrameHandles.delete(viewportId);
|
|
410
438
|
}
|
|
411
439
|
};
|
|
412
|
-
animate
|
|
440
|
+
const handle = requestAnimationFrame(animate);
|
|
441
|
+
this.animationFrameHandles.set(viewportId, handle);
|
|
413
442
|
}
|
|
414
443
|
}
|
|
415
444
|
export default OrientationControllerTool;
|
|
@@ -17,6 +17,7 @@ declare class VolumeCroppingTool extends BaseTool {
|
|
|
17
17
|
originalClippingPlanes: ClippingPlane[];
|
|
18
18
|
draggingSphereIndex: number | null;
|
|
19
19
|
rotatePlanesOnDrag: boolean;
|
|
20
|
+
suppressPlaneRotationForCurrentDrag: boolean;
|
|
20
21
|
cornerDragOffset: [number, number, number] | null;
|
|
21
22
|
faceDragOffset: number | null;
|
|
22
23
|
volumeDirectionVectors: {
|
|
@@ -50,6 +51,7 @@ declare class VolumeCroppingTool extends BaseTool {
|
|
|
50
51
|
preMouseDownCallback: (evt: EventTypes.InteractionEventType) => boolean;
|
|
51
52
|
setHandlesVisible(visible: boolean): void;
|
|
52
53
|
getHandlesVisible(): any;
|
|
54
|
+
setHandleRadius(radius: number): void;
|
|
53
55
|
getClippingPlanesVisible(): any;
|
|
54
56
|
setClippingPlanesVisible(visible: boolean): void;
|
|
55
57
|
getRotatePlanesOnDrag(): boolean;
|
|
@@ -11,6 +11,7 @@ import { getToolGroup } from '../store/ToolGroupManager';
|
|
|
11
11
|
import { Events } from '../enums';
|
|
12
12
|
import { PLANEINDEX, SPHEREINDEX, NUM_CLIPPING_PLANES, extractVolumeDirectionVectors, parseCornerKey, copyClippingPlanes, } from '../utilities/volumeCropping';
|
|
13
13
|
import { addLine3DBetweenPoints, calculateAdaptiveSphereRadius, } from '../utilities/draw3D';
|
|
14
|
+
import { isDragOwnedBy } from '../utilities/interactionDragCoordinator';
|
|
14
15
|
class VolumeCroppingTool extends BaseTool {
|
|
15
16
|
constructor(toolProps = {}, defaultToolProps = {
|
|
16
17
|
configuration: {
|
|
@@ -41,6 +42,7 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
41
42
|
this.originalClippingPlanes = [];
|
|
42
43
|
this.draggingSphereIndex = null;
|
|
43
44
|
this.rotatePlanesOnDrag = false;
|
|
45
|
+
this.suppressPlaneRotationForCurrentDrag = false;
|
|
44
46
|
this.cornerDragOffset = null;
|
|
45
47
|
this.faceDragOffset = null;
|
|
46
48
|
this.volumeDirectionVectors = null;
|
|
@@ -63,6 +65,7 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
63
65
|
const { element } = eventDetail;
|
|
64
66
|
const enabledElement = getEnabledElement(element);
|
|
65
67
|
const { viewport } = enabledElement;
|
|
68
|
+
this.suppressPlaneRotationForCurrentDrag = isDragOwnedBy(viewport.id, 'orientation-controller');
|
|
66
69
|
const actorEntry = viewport.getDefaultActor();
|
|
67
70
|
const actor = actorEntry.actor;
|
|
68
71
|
const mapper = actor.getMapper();
|
|
@@ -135,6 +138,7 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
135
138
|
this.draggingSphereIndex = null;
|
|
136
139
|
this.cornerDragOffset = null;
|
|
137
140
|
this.faceDragOffset = null;
|
|
141
|
+
this.suppressPlaneRotationForCurrentDrag = false;
|
|
138
142
|
viewport.render();
|
|
139
143
|
this._hasResolutionChanged = false;
|
|
140
144
|
};
|
|
@@ -768,6 +772,23 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
768
772
|
getHandlesVisible() {
|
|
769
773
|
return this.configuration.showHandles;
|
|
770
774
|
}
|
|
775
|
+
setHandleRadius(radius) {
|
|
776
|
+
this.configuration.sphereRadius = radius;
|
|
777
|
+
this.sphereStates.forEach((state) => {
|
|
778
|
+
if (state?.sphereSource?.setRadius) {
|
|
779
|
+
state.sphereSource.setRadius(radius);
|
|
780
|
+
state.sphereSource.modified();
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
const viewportsInfo = this._getViewportsInfo();
|
|
784
|
+
const [viewport3D] = viewportsInfo;
|
|
785
|
+
if (!viewport3D) {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
const renderingEngine = getRenderingEngine(viewport3D.renderingEngineId);
|
|
789
|
+
const viewport = renderingEngine?.getViewport(viewport3D.viewportId);
|
|
790
|
+
viewport?.render();
|
|
791
|
+
}
|
|
771
792
|
getClippingPlanesVisible() {
|
|
772
793
|
return this.configuration.showClippingPlanes;
|
|
773
794
|
}
|
|
@@ -803,7 +824,8 @@ class VolumeCroppingTool extends BaseTool {
|
|
|
803
824
|
}
|
|
804
825
|
else {
|
|
805
826
|
const shiftKey = evt.detail.event?.shiftKey ?? false;
|
|
806
|
-
if (this.rotatePlanesOnDrag === true || shiftKey)
|
|
827
|
+
if ((this.rotatePlanesOnDrag === true || shiftKey) &&
|
|
828
|
+
!this.suppressPlaneRotationForCurrentDrag) {
|
|
807
829
|
this._rotateClippingPlanes(evt);
|
|
808
830
|
return;
|
|
809
831
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
type DragOwner = 'orientation-controller';
|
|
2
|
+
export declare function beginOwnedDrag(viewportId: string, owner: DragOwner): boolean;
|
|
3
|
+
export declare function endOwnedDrag(viewportId: string, owner: DragOwner): void;
|
|
4
|
+
export declare function isDragOwnedBy(viewportId: string, owner: DragOwner): boolean;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const dragOwnerByViewportId = new Map();
|
|
2
|
+
export function beginOwnedDrag(viewportId, owner) {
|
|
3
|
+
if (dragOwnerByViewportId.has(viewportId)) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
dragOwnerByViewportId.set(viewportId, owner);
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
export function endOwnedDrag(viewportId, owner) {
|
|
10
|
+
if (dragOwnerByViewportId.get(viewportId) === owner) {
|
|
11
|
+
dragOwnerByViewportId.delete(viewportId);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function isDragOwnedBy(viewportId, owner) {
|
|
15
|
+
return dragOwnerByViewportId.get(viewportId) === owner;
|
|
16
|
+
}
|
|
@@ -12,6 +12,10 @@ export const triggerWorkerProgress = (workerType, progress) => {
|
|
|
12
12
|
};
|
|
13
13
|
export const getSegmentationDataForWorker = (segmentationId, segmentIndices) => {
|
|
14
14
|
const segmentation = getSegmentation(segmentationId);
|
|
15
|
+
if (!segmentation?.representationData) {
|
|
16
|
+
console.debug('getSegmentationDataForWorker: segmentation missing or not ready', segmentationId);
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
15
19
|
const { representationData } = segmentation;
|
|
16
20
|
const { Labelmap } = representationData;
|
|
17
21
|
if (!Labelmap) {
|
|
@@ -35,14 +35,16 @@ export interface MouseHandlersCallbacks {
|
|
|
35
35
|
export declare class vtkOrientationControllerWidget {
|
|
36
36
|
private actors;
|
|
37
37
|
private pickers;
|
|
38
|
+
private overlayRenderers;
|
|
39
|
+
private renderWindows;
|
|
38
40
|
private highlightedFace;
|
|
39
41
|
private mouseHandlers;
|
|
40
42
|
createActors(config: OrientationControllerConfig): vtkActor[];
|
|
41
43
|
addActorsToViewport(viewportId: string, viewport: Types.IVolumeViewport, actors: vtkActor[]): void;
|
|
42
|
-
removeActorsFromViewport(viewportId: string,
|
|
44
|
+
removeActorsFromViewport(viewportId: string, _viewport: Types.IVolumeViewport): void;
|
|
43
45
|
setupPicker(viewportId: string, actors: vtkActor[]): vtkCellPicker;
|
|
44
46
|
pickAtPosition(evt: MouseEvent, viewportId: string, viewport: Types.IVolumeViewport, element: HTMLDivElement, actors: vtkActor[]): PickResult | null;
|
|
45
|
-
calculateMarkerPosition(viewport: Types.IVolumeViewport, position: PositionConfig['position']): [number, number, number] | null;
|
|
47
|
+
calculateMarkerPosition(viewport: Types.IVolumeViewport, position: PositionConfig['position'], screenSizePixels: number): [number, number, number] | null;
|
|
46
48
|
positionActors(viewport: Types.IVolumeViewport, actors: vtkActor[], config: PositionConfig): boolean;
|
|
47
49
|
highlightFace(actor: vtkActor, cellId: number, viewport: Types.IVolumeViewport, isMainFace?: boolean): void;
|
|
48
50
|
clearHighlight(): void;
|
|
@@ -50,7 +52,7 @@ export declare class vtkOrientationControllerWidget {
|
|
|
50
52
|
cleanup: () => void;
|
|
51
53
|
};
|
|
52
54
|
getActors(viewportId: string): vtkActor[] | undefined;
|
|
53
|
-
syncOverlayViewport(
|
|
55
|
+
syncOverlayViewport(viewportId: string, viewport: Types.IVolumeViewport): void;
|
|
54
56
|
getOrientationForFace(cellId: number): {
|
|
55
57
|
viewPlaneNormal: number[];
|
|
56
58
|
viewUp: number[];
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import vtkCellPicker from '@kitware/vtk.js/Rendering/Core/CellPicker';
|
|
2
2
|
import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor';
|
|
3
|
+
import vtkCellArray from '@kitware/vtk.js/Common/Core/CellArray';
|
|
4
|
+
import vtkMapper from '@kitware/vtk.js/Rendering/Core/Mapper';
|
|
5
|
+
import vtkPoints from '@kitware/vtk.js/Common/Core/Points';
|
|
6
|
+
import vtkPolyData from '@kitware/vtk.js/Common/DataModel/PolyData';
|
|
7
|
+
import vtkRenderer from '@kitware/vtk.js/Rendering/Core/Renderer';
|
|
3
8
|
import { Enums } from '@cornerstonejs/core';
|
|
4
9
|
import vtkAnnotatedRhombicuboctahedronActor from '../AnnotatedRhombicuboctahedronActor';
|
|
10
|
+
import { beginOwnedDrag, endOwnedDrag } from '../../interactionDragCoordinator';
|
|
5
11
|
export class vtkOrientationControllerWidget {
|
|
6
12
|
constructor() {
|
|
7
13
|
this.actors = new Map();
|
|
8
14
|
this.pickers = new Map();
|
|
15
|
+
this.overlayRenderers = new Map();
|
|
16
|
+
this.renderWindows = new Map();
|
|
9
17
|
this.highlightedFace = null;
|
|
10
18
|
this.mouseHandlers = new Map();
|
|
11
19
|
}
|
|
@@ -83,19 +91,48 @@ export class vtkOrientationControllerWidget {
|
|
|
83
91
|
if (existingActors) {
|
|
84
92
|
this.removeActorsFromViewport(viewportId, viewport);
|
|
85
93
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
viewport.
|
|
94
|
+
const renderWindow = viewport
|
|
95
|
+
.getRenderingEngine()
|
|
96
|
+
.getOffscreenMultiRenderWindow(viewport.id)
|
|
97
|
+
.getRenderWindow();
|
|
98
|
+
const mainRenderer = viewport
|
|
99
|
+
.getRenderingEngine()
|
|
100
|
+
?.getRenderer(viewportId) ?? viewport.getRenderer();
|
|
101
|
+
const vtkMainRenderer = mainRenderer;
|
|
102
|
+
const overlayRenderer = vtkRenderer.newInstance();
|
|
103
|
+
overlayRenderer.setLayer(1);
|
|
104
|
+
overlayRenderer.setInteractive(false);
|
|
105
|
+
overlayRenderer.setPreserveColorBuffer(true);
|
|
106
|
+
overlayRenderer.setActiveCamera(vtkMainRenderer.getActiveCamera());
|
|
107
|
+
const vp = vtkMainRenderer.getViewport();
|
|
108
|
+
overlayRenderer.setViewport(...vp);
|
|
109
|
+
if (renderWindow.getNumberOfLayers() < 2) {
|
|
110
|
+
renderWindow.setNumberOfLayers(2);
|
|
111
|
+
}
|
|
112
|
+
renderWindow.addRenderer(overlayRenderer);
|
|
113
|
+
actors.forEach((actor) => {
|
|
114
|
+
overlayRenderer.addActor(actor);
|
|
89
115
|
});
|
|
90
116
|
this.actors.set(viewportId, actors);
|
|
117
|
+
this.overlayRenderers.set(viewportId, overlayRenderer);
|
|
118
|
+
this.renderWindows.set(viewportId, renderWindow);
|
|
91
119
|
}
|
|
92
|
-
removeActorsFromViewport(viewportId,
|
|
120
|
+
removeActorsFromViewport(viewportId, _viewport) {
|
|
93
121
|
const actors = this.actors.get(viewportId);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
122
|
+
const overlayRenderer = this.overlayRenderers.get(viewportId);
|
|
123
|
+
const renderWindow = this.renderWindows.get(viewportId);
|
|
124
|
+
if (actors && overlayRenderer) {
|
|
125
|
+
actors.forEach((actor) => {
|
|
126
|
+
overlayRenderer.removeActor(actor);
|
|
127
|
+
});
|
|
128
|
+
if (renderWindow) {
|
|
129
|
+
renderWindow.removeRenderer(overlayRenderer);
|
|
130
|
+
}
|
|
131
|
+
overlayRenderer.delete();
|
|
98
132
|
}
|
|
133
|
+
this.actors.delete(viewportId);
|
|
134
|
+
this.overlayRenderers.delete(viewportId);
|
|
135
|
+
this.renderWindows.delete(viewportId);
|
|
99
136
|
}
|
|
100
137
|
setupPicker(viewportId, actors) {
|
|
101
138
|
const picker = vtkCellPicker.newInstance({ opacityThreshold: 0.0001 });
|
|
@@ -113,10 +150,11 @@ export class vtkOrientationControllerWidget {
|
|
|
113
150
|
if (!picker) {
|
|
114
151
|
return null;
|
|
115
152
|
}
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
153
|
+
const renderer = this.overlayRenderers.get(viewportId) ??
|
|
154
|
+
viewport
|
|
155
|
+
.getRenderingEngine()
|
|
156
|
+
?.getRenderer(viewportId) ??
|
|
157
|
+
viewport.getRenderer();
|
|
120
158
|
if (!renderer) {
|
|
121
159
|
return null;
|
|
122
160
|
}
|
|
@@ -151,7 +189,7 @@ export class vtkOrientationControllerWidget {
|
|
|
151
189
|
}
|
|
152
190
|
return null;
|
|
153
191
|
}
|
|
154
|
-
calculateMarkerPosition(viewport, position) {
|
|
192
|
+
calculateMarkerPosition(viewport, position, screenSizePixels) {
|
|
155
193
|
const canvas = viewport.canvas;
|
|
156
194
|
if (!canvas) {
|
|
157
195
|
return null;
|
|
@@ -159,25 +197,30 @@ export class vtkOrientationControllerWidget {
|
|
|
159
197
|
const devicePixelRatio = window.devicePixelRatio || 1;
|
|
160
198
|
const canvasWidth = canvas.clientWidth || canvas.width / devicePixelRatio;
|
|
161
199
|
const canvasHeight = canvas.clientHeight || canvas.height / devicePixelRatio;
|
|
162
|
-
const
|
|
200
|
+
const marginRatio = viewport.type === Enums.ViewportType.VOLUME_3D ? 1.3 : 1.1;
|
|
201
|
+
const marginPxRaw = marginRatio * screenSizePixels;
|
|
202
|
+
const halfPx = screenSizePixels * 0.5;
|
|
203
|
+
const maxMarginX = Math.max(0, (canvasWidth - screenSizePixels) / 2);
|
|
204
|
+
const maxMarginY = Math.max(0, (canvasHeight - screenSizePixels) / 2);
|
|
205
|
+
const marginPx = Math.min(marginPxRaw, maxMarginX, maxMarginY);
|
|
163
206
|
let canvasX;
|
|
164
207
|
let canvasY;
|
|
165
208
|
switch (position) {
|
|
166
209
|
case 'top-left':
|
|
167
|
-
canvasX =
|
|
168
|
-
canvasY =
|
|
210
|
+
canvasX = marginPx + halfPx;
|
|
211
|
+
canvasY = marginPx + halfPx;
|
|
169
212
|
break;
|
|
170
213
|
case 'top-right':
|
|
171
|
-
canvasX = canvasWidth -
|
|
172
|
-
canvasY =
|
|
214
|
+
canvasX = canvasWidth - marginPx - halfPx;
|
|
215
|
+
canvasY = marginPx + halfPx;
|
|
173
216
|
break;
|
|
174
217
|
case 'bottom-left':
|
|
175
|
-
canvasX =
|
|
176
|
-
canvasY = canvasHeight -
|
|
218
|
+
canvasX = marginPx + halfPx;
|
|
219
|
+
canvasY = canvasHeight - marginPx - halfPx;
|
|
177
220
|
break;
|
|
178
221
|
default:
|
|
179
|
-
canvasX = canvasWidth -
|
|
180
|
-
canvasY = canvasHeight -
|
|
222
|
+
canvasX = canvasWidth - marginPx - halfPx;
|
|
223
|
+
canvasY = canvasHeight - marginPx - halfPx;
|
|
181
224
|
}
|
|
182
225
|
const canvasPos = [canvasX, canvasY];
|
|
183
226
|
const worldPos = viewport.canvasToWorld(canvasPos);
|
|
@@ -207,7 +250,7 @@ export class vtkOrientationControllerWidget {
|
|
|
207
250
|
const markerSize = screenSizePixels * worldUnitsPerPixel;
|
|
208
251
|
actors.forEach((actor) => {
|
|
209
252
|
actor.setScale(markerSize, markerSize, markerSize);
|
|
210
|
-
const worldPos = this.calculateMarkerPosition(viewport, config.position);
|
|
253
|
+
const worldPos = this.calculateMarkerPosition(viewport, config.position, screenSizePixels);
|
|
211
254
|
if (!worldPos) {
|
|
212
255
|
console.warn('OrientationControllerWidget: Could not get world position');
|
|
213
256
|
return;
|
|
@@ -226,6 +269,172 @@ export class vtkOrientationControllerWidget {
|
|
|
226
269
|
}
|
|
227
270
|
this.clearHighlight();
|
|
228
271
|
if (isMainFace) {
|
|
272
|
+
const textureCollection = actor.getTextures?.();
|
|
273
|
+
const textureCandidate = Array.isArray(textureCollection)
|
|
274
|
+
? textureCollection[0]
|
|
275
|
+
: textureCollection?.getItem?.(0);
|
|
276
|
+
const texture = textureCandidate;
|
|
277
|
+
const imageData = texture?.getInputData?.();
|
|
278
|
+
const scalars = imageData?.getPointData().getScalars();
|
|
279
|
+
const pixels = scalars?.getData();
|
|
280
|
+
const dims = imageData?.getDimensions();
|
|
281
|
+
if (!imageData ||
|
|
282
|
+
!scalars ||
|
|
283
|
+
!pixels ||
|
|
284
|
+
!dims ||
|
|
285
|
+
cellId < 0 ||
|
|
286
|
+
cellId > 5) {
|
|
287
|
+
const mapper = actor.getMapper();
|
|
288
|
+
const polyData = mapper.getInputData();
|
|
289
|
+
if (!polyData?.getCellPoints) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const { cellPointIds } = polyData.getCellPoints(cellId);
|
|
293
|
+
if (!cellPointIds || cellPointIds.length < 3) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const src = polyData.getPoints().getData();
|
|
297
|
+
const coords = [];
|
|
298
|
+
Array.from(cellPointIds).forEach((pid) => {
|
|
299
|
+
const o = pid * 3;
|
|
300
|
+
coords.push(src[o], src[o + 1], src[o + 2]);
|
|
301
|
+
});
|
|
302
|
+
const points = vtkPoints.newInstance();
|
|
303
|
+
points.setData(new Float32Array(coords), 3);
|
|
304
|
+
const polys = vtkCellArray.newInstance({
|
|
305
|
+
values: new Uint32Array([
|
|
306
|
+
cellPointIds.length,
|
|
307
|
+
...Array.from(cellPointIds, (_, i) => i),
|
|
308
|
+
]),
|
|
309
|
+
});
|
|
310
|
+
const poly = vtkPolyData.newInstance();
|
|
311
|
+
poly.setPoints(points);
|
|
312
|
+
poly.setPolys(polys);
|
|
313
|
+
const faceMapper = vtkMapper.newInstance();
|
|
314
|
+
faceMapper.setInputData(poly);
|
|
315
|
+
const faceActor = vtkActor.newInstance();
|
|
316
|
+
faceActor.setMapper(faceMapper);
|
|
317
|
+
const [sx, sy, sz] = actor.getScale();
|
|
318
|
+
const [px, py, pz] = actor.getPosition();
|
|
319
|
+
const [ox, oy, oz] = actor.getOrientation();
|
|
320
|
+
faceActor.setScale(sx, sy, sz);
|
|
321
|
+
faceActor.setPosition(px, py, pz);
|
|
322
|
+
faceActor.setOrientation(ox, oy, oz);
|
|
323
|
+
faceActor.setPickable(false);
|
|
324
|
+
const p = faceActor.getProperty();
|
|
325
|
+
p.setLighting(false);
|
|
326
|
+
p.setAmbient(1);
|
|
327
|
+
p.setDiffuse(0);
|
|
328
|
+
p.setColor(1, 1, 1);
|
|
329
|
+
p.setOpacity(0.58);
|
|
330
|
+
this.overlayRenderers.get(viewport.id)?.addActor(faceActor);
|
|
331
|
+
this.highlightedFace = {
|
|
332
|
+
actor,
|
|
333
|
+
cellId,
|
|
334
|
+
originalColor: [0, 0, 0, 0],
|
|
335
|
+
viewport,
|
|
336
|
+
isMainFace: true,
|
|
337
|
+
mainFaceHighlightActor: faceActor,
|
|
338
|
+
};
|
|
339
|
+
viewport.render();
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const [imageWidth, imageHeight] = dims;
|
|
343
|
+
const tileWidth = Math.floor(imageWidth / 3);
|
|
344
|
+
const tileHeight = Math.floor(imageHeight / 2);
|
|
345
|
+
const tileCol = cellId % 3;
|
|
346
|
+
const tileRow = Math.floor(cellId / 3);
|
|
347
|
+
const x0 = tileCol * tileWidth;
|
|
348
|
+
const y0 = tileRow * tileHeight;
|
|
349
|
+
const tileBackup = new Uint8Array(tileWidth * tileHeight * 4);
|
|
350
|
+
let b = 0;
|
|
351
|
+
for (let y = 0; y < tileHeight; y++) {
|
|
352
|
+
for (let x = 0; x < tileWidth; x++) {
|
|
353
|
+
const srcIndex = ((y0 + y) * imageWidth + (x0 + x)) * 4;
|
|
354
|
+
tileBackup[b++] = pixels[srcIndex];
|
|
355
|
+
tileBackup[b++] = pixels[srcIndex + 1];
|
|
356
|
+
tileBackup[b++] = pixels[srcIndex + 2];
|
|
357
|
+
tileBackup[b++] = pixels[srcIndex + 3];
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const bgSampleIndices = [
|
|
361
|
+
((y0 + 8) * imageWidth + (x0 + 8)) * 4,
|
|
362
|
+
((y0 + 8) * imageWidth + (x0 + tileWidth - 9)) * 4,
|
|
363
|
+
((y0 + tileHeight - 9) * imageWidth + (x0 + 8)) * 4,
|
|
364
|
+
((y0 + tileHeight - 9) * imageWidth + (x0 + tileWidth - 9)) * 4,
|
|
365
|
+
];
|
|
366
|
+
const bgColor = [0, 0, 0];
|
|
367
|
+
bgSampleIndices.forEach((idx) => {
|
|
368
|
+
bgColor[0] += pixels[idx];
|
|
369
|
+
bgColor[1] += pixels[idx + 1];
|
|
370
|
+
bgColor[2] += pixels[idx + 2];
|
|
371
|
+
});
|
|
372
|
+
bgColor[0] /= bgSampleIndices.length;
|
|
373
|
+
bgColor[1] /= bgSampleIndices.length;
|
|
374
|
+
bgColor[2] /= bgSampleIndices.length;
|
|
375
|
+
const glyphThreshold = 42;
|
|
376
|
+
const faceBrighten = 72;
|
|
377
|
+
const isGlyphPixel = (x, y) => {
|
|
378
|
+
if (x < 0 || x >= tileWidth || y < 0 || y >= tileHeight) {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
const idx = ((y0 + y) * imageWidth + (x0 + x)) * 4;
|
|
382
|
+
const dr = pixels[idx] - bgColor[0];
|
|
383
|
+
const dg = pixels[idx + 1] - bgColor[1];
|
|
384
|
+
const db = pixels[idx + 2] - bgColor[2];
|
|
385
|
+
return Math.sqrt(dr * dr + dg * dg + db * db) >= glyphThreshold;
|
|
386
|
+
};
|
|
387
|
+
const borderWidth = Math.max(4, Math.floor(tileWidth * 0.035));
|
|
388
|
+
for (let y = 0; y < tileHeight; y++) {
|
|
389
|
+
for (let x = 0; x < tileWidth; x++) {
|
|
390
|
+
const onBorder = x < borderWidth ||
|
|
391
|
+
x >= tileWidth - borderWidth ||
|
|
392
|
+
y < borderWidth ||
|
|
393
|
+
y >= tileHeight - borderWidth;
|
|
394
|
+
if (onBorder || isGlyphPixel(x, y)) {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
const idx = ((y0 + y) * imageWidth + (x0 + x)) * 4;
|
|
398
|
+
pixels[idx] = Math.min(255, pixels[idx] + faceBrighten);
|
|
399
|
+
pixels[idx + 1] = Math.min(255, pixels[idx + 1] + faceBrighten);
|
|
400
|
+
pixels[idx + 2] = Math.min(255, pixels[idx + 2] + faceBrighten);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
for (let y = 0; y < tileHeight; y++) {
|
|
404
|
+
for (let x = 0; x < tileWidth; x++) {
|
|
405
|
+
const onBorder = x < borderWidth ||
|
|
406
|
+
x >= tileWidth - borderWidth ||
|
|
407
|
+
y < borderWidth ||
|
|
408
|
+
y >= tileHeight - borderWidth;
|
|
409
|
+
if (!onBorder) {
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
const idx = ((y0 + y) * imageWidth + (x0 + x)) * 4;
|
|
413
|
+
pixels[idx] = 0;
|
|
414
|
+
pixels[idx + 1] = 0;
|
|
415
|
+
pixels[idx + 2] = 0;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
scalars.modified();
|
|
419
|
+
imageData.modified();
|
|
420
|
+
texture.modified?.();
|
|
421
|
+
actor.modified?.();
|
|
422
|
+
this.highlightedFace = {
|
|
423
|
+
actor,
|
|
424
|
+
cellId,
|
|
425
|
+
originalColor: [0, 0, 0, 0],
|
|
426
|
+
viewport,
|
|
427
|
+
isMainFace: true,
|
|
428
|
+
mainFaceTextureData: tileBackup,
|
|
429
|
+
mainFaceTile: {
|
|
430
|
+
x0,
|
|
431
|
+
y0,
|
|
432
|
+
width: tileWidth,
|
|
433
|
+
height: tileHeight,
|
|
434
|
+
imageWidth,
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
viewport.render();
|
|
229
438
|
return;
|
|
230
439
|
}
|
|
231
440
|
const mapper = actor.getMapper();
|
|
@@ -266,9 +475,35 @@ export class vtkOrientationControllerWidget {
|
|
|
266
475
|
return;
|
|
267
476
|
}
|
|
268
477
|
const { actor, cellId, originalColor, viewport, isMainFace } = this.highlightedFace;
|
|
269
|
-
if (isMainFace
|
|
270
|
-
const
|
|
271
|
-
|
|
478
|
+
if (isMainFace) {
|
|
479
|
+
const backup = this.highlightedFace.mainFaceTextureData;
|
|
480
|
+
const tile = this.highlightedFace.mainFaceTile;
|
|
481
|
+
const textures = actor.getTextures?.();
|
|
482
|
+
const texture = textures?.[0];
|
|
483
|
+
const imageData = texture?.getInputData?.();
|
|
484
|
+
const scalars = imageData?.getPointData().getScalars();
|
|
485
|
+
const pixels = scalars?.getData();
|
|
486
|
+
if (backup && tile && scalars && imageData && pixels) {
|
|
487
|
+
let b = 0;
|
|
488
|
+
for (let y = 0; y < tile.height; y++) {
|
|
489
|
+
for (let x = 0; x < tile.width; x++) {
|
|
490
|
+
const dstIndex = ((tile.y0 + y) * tile.imageWidth + (tile.x0 + x)) * 4;
|
|
491
|
+
pixels[dstIndex] = backup[b++];
|
|
492
|
+
pixels[dstIndex + 1] = backup[b++];
|
|
493
|
+
pixels[dstIndex + 2] = backup[b++];
|
|
494
|
+
pixels[dstIndex + 3] = backup[b++];
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
scalars.modified();
|
|
498
|
+
imageData.modified();
|
|
499
|
+
texture.modified?.();
|
|
500
|
+
actor.modified?.();
|
|
501
|
+
}
|
|
502
|
+
else if (this.highlightedFace.mainFaceHighlightActor) {
|
|
503
|
+
const overlayRenderer = this.overlayRenderers.get(viewport.id);
|
|
504
|
+
overlayRenderer?.removeActor(this.highlightedFace.mainFaceHighlightActor);
|
|
505
|
+
this.highlightedFace.mainFaceHighlightActor.delete();
|
|
506
|
+
}
|
|
272
507
|
viewport.render();
|
|
273
508
|
this.highlightedFace = null;
|
|
274
509
|
return;
|
|
@@ -298,16 +533,25 @@ export class vtkOrientationControllerWidget {
|
|
|
298
533
|
}
|
|
299
534
|
setupMouseHandlers(viewportId, element, viewport, actors, callbacks) {
|
|
300
535
|
let isMouseDown = false;
|
|
536
|
+
let didDrag = false;
|
|
537
|
+
let pendingPickResult = null;
|
|
538
|
+
let mouseDownCanvas = null;
|
|
539
|
+
const clickTolerancePx = 3;
|
|
301
540
|
const hoverHandler = (evt) => {
|
|
302
541
|
if (isMouseDown) {
|
|
542
|
+
if (mouseDownCanvas) {
|
|
543
|
+
const dx = evt.clientX - mouseDownCanvas.x;
|
|
544
|
+
const dy = evt.clientY - mouseDownCanvas.y;
|
|
545
|
+
if (dx * dx + dy * dy > clickTolerancePx * clickTolerancePx) {
|
|
546
|
+
didDrag = true;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
303
549
|
return;
|
|
304
550
|
}
|
|
305
551
|
const pickResult = this.pickAtPosition(evt, viewportId, viewport, element, actors);
|
|
306
552
|
if (pickResult) {
|
|
307
553
|
const { pickedActor, cellId, actorIndex } = pickResult;
|
|
308
|
-
|
|
309
|
-
this.highlightFace(pickedActor, cellId, viewport, false);
|
|
310
|
-
}
|
|
554
|
+
this.highlightFace(pickedActor, cellId, viewport, actorIndex === 0);
|
|
311
555
|
if (callbacks.onFaceHover) {
|
|
312
556
|
callbacks.onFaceHover(pickResult);
|
|
313
557
|
}
|
|
@@ -328,22 +572,33 @@ export class vtkOrientationControllerWidget {
|
|
|
328
572
|
return;
|
|
329
573
|
}
|
|
330
574
|
isMouseDown = true;
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
else if (pickResult.actorIndex === 2) {
|
|
336
|
-
globalCellId = pickResult.cellId + 18;
|
|
337
|
-
}
|
|
338
|
-
callbacks.onFacePicked({
|
|
339
|
-
...pickResult,
|
|
340
|
-
cellId: globalCellId,
|
|
341
|
-
});
|
|
342
|
-
evt.preventDefault();
|
|
343
|
-
evt.stopPropagation();
|
|
575
|
+
didDrag = false;
|
|
576
|
+
pendingPickResult = pickResult;
|
|
577
|
+
mouseDownCanvas = { x: evt.clientX, y: evt.clientY };
|
|
578
|
+
beginOwnedDrag(viewportId, 'orientation-controller');
|
|
344
579
|
};
|
|
345
|
-
const mouseUpHandler = () => {
|
|
580
|
+
const mouseUpHandler = (evt) => {
|
|
581
|
+
if (isMouseDown && !didDrag && pendingPickResult) {
|
|
582
|
+
let globalCellId = pendingPickResult.cellId;
|
|
583
|
+
if (pendingPickResult.actorIndex === 1) {
|
|
584
|
+
globalCellId = pendingPickResult.cellId + 6;
|
|
585
|
+
}
|
|
586
|
+
else if (pendingPickResult.actorIndex === 2) {
|
|
587
|
+
globalCellId = pendingPickResult.cellId + 18;
|
|
588
|
+
}
|
|
589
|
+
callbacks.onFacePicked({
|
|
590
|
+
...pendingPickResult,
|
|
591
|
+
cellId: globalCellId,
|
|
592
|
+
});
|
|
593
|
+
evt.preventDefault();
|
|
594
|
+
evt.stopImmediatePropagation();
|
|
595
|
+
evt.stopPropagation();
|
|
596
|
+
}
|
|
346
597
|
isMouseDown = false;
|
|
598
|
+
didDrag = false;
|
|
599
|
+
pendingPickResult = null;
|
|
600
|
+
mouseDownCanvas = null;
|
|
601
|
+
endOwnedDrag(viewportId, 'orientation-controller');
|
|
347
602
|
this.clearHighlight();
|
|
348
603
|
};
|
|
349
604
|
const dblclickHandler = (evt) => {
|
|
@@ -353,17 +608,18 @@ export class vtkOrientationControllerWidget {
|
|
|
353
608
|
evt.stopImmediatePropagation();
|
|
354
609
|
}
|
|
355
610
|
};
|
|
356
|
-
element.addEventListener('mousemove', hoverHandler);
|
|
357
|
-
element.addEventListener('mousedown', clickHandler);
|
|
611
|
+
element.addEventListener('mousemove', hoverHandler, true);
|
|
612
|
+
element.addEventListener('mousedown', clickHandler, true);
|
|
358
613
|
element.addEventListener('mouseup', mouseUpHandler);
|
|
359
614
|
element.addEventListener('mouseleave', mouseUpHandler);
|
|
360
615
|
element.addEventListener('dblclick', dblclickHandler, true);
|
|
361
616
|
const cleanup = () => {
|
|
362
|
-
element.removeEventListener('mousemove', hoverHandler);
|
|
363
|
-
element.removeEventListener('mousedown', clickHandler);
|
|
617
|
+
element.removeEventListener('mousemove', hoverHandler, true);
|
|
618
|
+
element.removeEventListener('mousedown', clickHandler, true);
|
|
364
619
|
element.removeEventListener('mouseup', mouseUpHandler);
|
|
365
620
|
element.removeEventListener('mouseleave', mouseUpHandler);
|
|
366
621
|
element.removeEventListener('dblclick', dblclickHandler, true);
|
|
622
|
+
endOwnedDrag(viewportId, 'orientation-controller');
|
|
367
623
|
};
|
|
368
624
|
this.mouseHandlers.set(viewportId, { cleanup });
|
|
369
625
|
return { cleanup };
|
|
@@ -371,7 +627,19 @@ export class vtkOrientationControllerWidget {
|
|
|
371
627
|
getActors(viewportId) {
|
|
372
628
|
return this.actors.get(viewportId);
|
|
373
629
|
}
|
|
374
|
-
syncOverlayViewport(
|
|
630
|
+
syncOverlayViewport(viewportId, viewport) {
|
|
631
|
+
const overlayRenderer = this.overlayRenderers.get(viewportId);
|
|
632
|
+
if (!overlayRenderer) {
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
const mainRenderer = viewport
|
|
636
|
+
.getRenderingEngine()
|
|
637
|
+
?.getRenderer(viewportId) ?? viewport.getRenderer();
|
|
638
|
+
if (!mainRenderer) {
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
const mainVp = mainRenderer.getViewport();
|
|
642
|
+
overlayRenderer.setViewport(...mainVp);
|
|
375
643
|
}
|
|
376
644
|
getOrientationForFace(cellId) {
|
|
377
645
|
const orientations = new Map();
|
|
@@ -472,12 +740,31 @@ export class vtkOrientationControllerWidget {
|
|
|
472
740
|
handler.cleanup();
|
|
473
741
|
this.mouseHandlers.delete(viewportId);
|
|
474
742
|
}
|
|
743
|
+
const overlayRenderer = this.overlayRenderers.get(viewportId);
|
|
744
|
+
const renderWindow = this.renderWindows.get(viewportId);
|
|
745
|
+
if (overlayRenderer) {
|
|
746
|
+
if (renderWindow) {
|
|
747
|
+
renderWindow.removeRenderer(overlayRenderer);
|
|
748
|
+
}
|
|
749
|
+
overlayRenderer.delete();
|
|
750
|
+
}
|
|
475
751
|
this.actors.delete(viewportId);
|
|
476
752
|
this.pickers.delete(viewportId);
|
|
753
|
+
this.overlayRenderers.delete(viewportId);
|
|
754
|
+
this.renderWindows.delete(viewportId);
|
|
477
755
|
}
|
|
478
756
|
else {
|
|
479
757
|
this.mouseHandlers.forEach((handler) => handler.cleanup());
|
|
480
758
|
this.mouseHandlers.clear();
|
|
759
|
+
this.overlayRenderers.forEach((overlayRenderer, vpId) => {
|
|
760
|
+
const renderWindow = this.renderWindows.get(vpId);
|
|
761
|
+
if (renderWindow) {
|
|
762
|
+
renderWindow.removeRenderer(overlayRenderer);
|
|
763
|
+
}
|
|
764
|
+
overlayRenderer.delete();
|
|
765
|
+
});
|
|
766
|
+
this.overlayRenderers.clear();
|
|
767
|
+
this.renderWindows.clear();
|
|
481
768
|
this.actors.clear();
|
|
482
769
|
this.pickers.clear();
|
|
483
770
|
}
|
|
@@ -15,6 +15,7 @@ const EDGE_FACES = [
|
|
|
15
15
|
11, 4, 5, 6, 22, 21, 4, 6, 7, 15, 14, 4, 7, 4, 17, 18, 4, 8, 11, 17, 16, 4, 9,
|
|
16
16
|
20, 21, 10, 4, 13, 23, 22, 14, 4, 12, 19, 18, 15,
|
|
17
17
|
];
|
|
18
|
+
const FACE_HALF_SIZE = 0.792;
|
|
18
19
|
function vtkRhombicuboctahedronSource(publicAPI, model) {
|
|
19
20
|
model.classHierarchy.push('vtkRhombicuboctahedronSource');
|
|
20
21
|
publicAPI.requestData = (inData, outData) => {
|
|
@@ -30,32 +31,31 @@ function vtkRhombicuboctahedronSource(publicAPI, model) {
|
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
const phi = 1.4;
|
|
33
|
-
const faceSize = 0.88;
|
|
34
34
|
const vertices = [];
|
|
35
|
-
vertices.push(-
|
|
36
|
-
vertices.push(
|
|
37
|
-
vertices.push(
|
|
38
|
-
vertices.push(-
|
|
39
|
-
vertices.push(-
|
|
40
|
-
vertices.push(
|
|
41
|
-
vertices.push(
|
|
42
|
-
vertices.push(-
|
|
43
|
-
vertices.push(-
|
|
44
|
-
vertices.push(
|
|
45
|
-
vertices.push(
|
|
46
|
-
vertices.push(-
|
|
47
|
-
vertices.push(-
|
|
48
|
-
vertices.push(
|
|
49
|
-
vertices.push(
|
|
50
|
-
vertices.push(-
|
|
51
|
-
vertices.push(-phi, -
|
|
52
|
-
vertices.push(-phi, -
|
|
53
|
-
vertices.push(-phi,
|
|
54
|
-
vertices.push(-phi,
|
|
55
|
-
vertices.push(phi, -
|
|
56
|
-
vertices.push(phi, -
|
|
57
|
-
vertices.push(phi,
|
|
58
|
-
vertices.push(phi,
|
|
35
|
+
vertices.push(-FACE_HALF_SIZE, -FACE_HALF_SIZE, -phi);
|
|
36
|
+
vertices.push(FACE_HALF_SIZE, -FACE_HALF_SIZE, -phi);
|
|
37
|
+
vertices.push(FACE_HALF_SIZE, FACE_HALF_SIZE, -phi);
|
|
38
|
+
vertices.push(-FACE_HALF_SIZE, FACE_HALF_SIZE, -phi);
|
|
39
|
+
vertices.push(-FACE_HALF_SIZE, -FACE_HALF_SIZE, phi);
|
|
40
|
+
vertices.push(FACE_HALF_SIZE, -FACE_HALF_SIZE, phi);
|
|
41
|
+
vertices.push(FACE_HALF_SIZE, FACE_HALF_SIZE, phi);
|
|
42
|
+
vertices.push(-FACE_HALF_SIZE, FACE_HALF_SIZE, phi);
|
|
43
|
+
vertices.push(-FACE_HALF_SIZE, -phi, -FACE_HALF_SIZE);
|
|
44
|
+
vertices.push(FACE_HALF_SIZE, -phi, -FACE_HALF_SIZE);
|
|
45
|
+
vertices.push(FACE_HALF_SIZE, -phi, FACE_HALF_SIZE);
|
|
46
|
+
vertices.push(-FACE_HALF_SIZE, -phi, FACE_HALF_SIZE);
|
|
47
|
+
vertices.push(-FACE_HALF_SIZE, phi, -FACE_HALF_SIZE);
|
|
48
|
+
vertices.push(FACE_HALF_SIZE, phi, -FACE_HALF_SIZE);
|
|
49
|
+
vertices.push(FACE_HALF_SIZE, phi, FACE_HALF_SIZE);
|
|
50
|
+
vertices.push(-FACE_HALF_SIZE, phi, FACE_HALF_SIZE);
|
|
51
|
+
vertices.push(-phi, -FACE_HALF_SIZE, -FACE_HALF_SIZE);
|
|
52
|
+
vertices.push(-phi, -FACE_HALF_SIZE, FACE_HALF_SIZE);
|
|
53
|
+
vertices.push(-phi, FACE_HALF_SIZE, FACE_HALF_SIZE);
|
|
54
|
+
vertices.push(-phi, FACE_HALF_SIZE, -FACE_HALF_SIZE);
|
|
55
|
+
vertices.push(phi, -FACE_HALF_SIZE, -FACE_HALF_SIZE);
|
|
56
|
+
vertices.push(phi, -FACE_HALF_SIZE, FACE_HALF_SIZE);
|
|
57
|
+
vertices.push(phi, FACE_HALF_SIZE, FACE_HALF_SIZE);
|
|
58
|
+
vertices.push(phi, FACE_HALF_SIZE, -FACE_HALF_SIZE);
|
|
59
59
|
let textureCoords = null;
|
|
60
60
|
if (model.generate3DTextureCoordinates) {
|
|
61
61
|
textureCoords = new Float64Array(24 * 3);
|
package/dist/esm/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "4.21.
|
|
1
|
+
export declare const version = "4.21.8";
|
package/dist/esm/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '4.21.
|
|
1
|
+
export const version = '4.21.8';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cornerstonejs/tools",
|
|
3
|
-
"version": "4.21.
|
|
3
|
+
"version": "4.21.8",
|
|
4
4
|
"description": "Cornerstone3D Tools",
|
|
5
5
|
"types": "./dist/esm/index.d.ts",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -105,11 +105,11 @@
|
|
|
105
105
|
"lodash.get": "4.4.2"
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
|
-
"@cornerstonejs/core": "4.21.
|
|
108
|
+
"@cornerstonejs/core": "4.21.8",
|
|
109
109
|
"canvas": "3.2.0"
|
|
110
110
|
},
|
|
111
111
|
"peerDependencies": {
|
|
112
|
-
"@cornerstonejs/core": "4.21.
|
|
112
|
+
"@cornerstonejs/core": "4.21.8",
|
|
113
113
|
"@kitware/vtk.js": "34.15.1",
|
|
114
114
|
"@types/d3-array": "3.2.1",
|
|
115
115
|
"@types/d3-interpolate": "3.0.4",
|
|
@@ -128,5 +128,5 @@
|
|
|
128
128
|
"type": "individual",
|
|
129
129
|
"url": "https://ohif.org/donate"
|
|
130
130
|
},
|
|
131
|
-
"gitHead": "
|
|
131
|
+
"gitHead": "65b9f66a24ac61dfa72640089ffde095dfff9d90"
|
|
132
132
|
}
|