@kitware/vtk.js 29.11.1 → 30.0.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/BREAKING_CHANGES.md +5 -0
- package/Rendering/Core/CellPicker.d.ts +2 -20
- package/Rendering/Core/CellPicker.js +8 -8
- package/Rendering/Core/Picker.d.ts +12 -10
- package/Rendering/Core/Picker.js +143 -134
- package/Rendering/Core/PointPicker.d.ts +2 -18
- package/Rendering/Core/PointPicker.js +5 -5
- package/Rendering/Core/VolumeMapper.d.ts +3 -3
- package/Rendering/OpenGL/ImageCPRMapper.js +5 -3
- package/Widgets/Widgets3D/ResliceCursorWidget/behavior.d.ts +0 -1
- package/Widgets/Widgets3D/ResliceCursorWidget/behavior.js +3 -12
- package/Widgets/Widgets3D/ResliceCursorWidget.d.ts +0 -1
- package/Widgets/Widgets3D/ResliceCursorWidget.js +2 -2
- package/package.json +1 -1
package/BREAKING_CHANGES.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
## From 29.x to 30
|
|
2
|
+
|
|
3
|
+
- **ResliceCursorWidget.interactionEvent**: no longer pass an object of computeFocalPointOffset, canUpdateFocalPoint but simply the type of the handle that triggers the event. Those values can easily be recomputed by the consumers of the event. Regarding `computeFocalPointOffset`, it is no longer adviced to compute focal point offset for each interaction, instead observing `startInteraction()` should be considered (see ResliceCursorWidget example).
|
|
4
|
+
- **ResliceCursorWidget.invokeInternalInteractionEvent(methodName)**: has been removed and should be replaced by `ResliceCursorWidget.invokeInteractionEvent(methodName)`.
|
|
5
|
+
|
|
1
6
|
## From 28.x to 29
|
|
2
7
|
|
|
3
8
|
- **getOpenGLRenderWindow**: `getOpenGLRenderWindow` has been renamed to `getApiSpecificRenderWindow` in `vtkFullScreenRenderWindow`, `vtkGenericRenderWindow` and `vtkViewProxy` to support WebGL and WebGPU backend. ([#2816](https://github.com/Kitware/vtk-js/pull/2816))
|
|
@@ -2,7 +2,9 @@ import vtkCell from './../../Common/DataModel/Cell';
|
|
|
2
2
|
import { Vector3 } from './../../types';
|
|
3
3
|
import vtkMapper from './Mapper';
|
|
4
4
|
import vtkPicker, { IPickerInitialValues } from './Picker';
|
|
5
|
+
import vtkProp3D from './Prop3D';
|
|
5
6
|
import vtkRenderer from './Renderer';
|
|
7
|
+
import { Nullable } from './../../types';
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
*
|
|
@@ -83,26 +85,6 @@ export interface vtkCellPicker extends vtkPicker {
|
|
|
83
85
|
* @param {vtkRenderer} renderer The vtkRenderer instance.
|
|
84
86
|
*/
|
|
85
87
|
pick(selection: any, renderer: vtkRenderer): void;
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
*
|
|
89
|
-
* @param {Vector3} p1
|
|
90
|
-
* @param {Vector3} p2
|
|
91
|
-
* @param {Number} tol
|
|
92
|
-
* @param {vtkMapper} mapper The vtkMapper instance.
|
|
93
|
-
*/
|
|
94
|
-
intersectWithLine(p1: Vector3, p2: Vector3, tol: number, mapper: vtkMapper): number;
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
*
|
|
98
|
-
* @param {Vector3} p1
|
|
99
|
-
* @param {Vector3} p2
|
|
100
|
-
* @param {Number} t1
|
|
101
|
-
* @param {Number} t2
|
|
102
|
-
* @param {Number} tol
|
|
103
|
-
* @param {vtkMapper} mapper The vtkMapper instance.
|
|
104
|
-
*/
|
|
105
|
-
intersectActorWithLine(p1: Vector3, p2: Vector3, t1: number, t2: number, tol: number, mapper: vtkMapper): number;
|
|
106
88
|
}
|
|
107
89
|
|
|
108
90
|
/**
|
|
@@ -158,7 +158,7 @@ function vtkCellPicker(publicAPI, model) {
|
|
|
158
158
|
}
|
|
159
159
|
return pickResult;
|
|
160
160
|
};
|
|
161
|
-
|
|
161
|
+
model.intersectWithLine = (p1, p2, tolerance, prop, mapper) => {
|
|
162
162
|
let tMin = Number.MAX_VALUE;
|
|
163
163
|
let t1 = 0.0;
|
|
164
164
|
let t2 = 1.0;
|
|
@@ -177,13 +177,13 @@ function vtkCellPicker(publicAPI, model) {
|
|
|
177
177
|
} else if (mapper.isA('vtkVolumeMapper')) {
|
|
178
178
|
// we calculate here the parametric intercept points between the ray and the bounding box, so
|
|
179
179
|
// if the application defines for some reason a too large ray length (1e6), it restrict the calculation
|
|
180
|
-
// to the vtkVolume
|
|
180
|
+
// to the vtkVolume prop bounding box
|
|
181
181
|
const interceptionObject = vtkBox.intersectWithLine(mapper.getBounds(), p1, p2);
|
|
182
182
|
t1 = interceptionObject?.t1 > clipLine.t1 ? interceptionObject.t1 : clipLine.t1;
|
|
183
183
|
t2 = interceptionObject?.t2 < clipLine.t2 ? interceptionObject.t2 : clipLine.t2;
|
|
184
|
-
tMin =
|
|
184
|
+
tMin = model.intersectVolumeWithLine(p1, p2, t1, t2, tolerance, prop);
|
|
185
185
|
} else if (mapper.isA('vtkMapper')) {
|
|
186
|
-
tMin =
|
|
186
|
+
tMin = model.intersectActorWithLine(p1, p2, t1, t2, tolerance, mapper);
|
|
187
187
|
}
|
|
188
188
|
if (tMin < model.globalTMin) {
|
|
189
189
|
model.globalTMin = tMin;
|
|
@@ -208,7 +208,7 @@ function vtkCellPicker(publicAPI, model) {
|
|
|
208
208
|
}
|
|
209
209
|
return tMin;
|
|
210
210
|
};
|
|
211
|
-
|
|
211
|
+
model.intersectVolumeWithLine = (p1, p2, t1, t2, tolerance, volume) => {
|
|
212
212
|
let tMin = Number.MAX_VALUE;
|
|
213
213
|
const mapper = volume.getMapper();
|
|
214
214
|
const imageData = mapper.getInputData();
|
|
@@ -315,7 +315,7 @@ function vtkCellPicker(publicAPI, model) {
|
|
|
315
315
|
}
|
|
316
316
|
return tMin;
|
|
317
317
|
};
|
|
318
|
-
|
|
318
|
+
model.intersectActorWithLine = (p1, p2, t1, t2, tolerance, mapper) => {
|
|
319
319
|
let tMin = Number.MAX_VALUE;
|
|
320
320
|
const minXYZ = [0, 0, 0];
|
|
321
321
|
let pDistMin = Number.MAX_VALUE;
|
|
@@ -368,9 +368,9 @@ function vtkCellPicker(publicAPI, model) {
|
|
|
368
368
|
let cellPicked;
|
|
369
369
|
{
|
|
370
370
|
if (vtkCellTypes.hasSubCells(minCellType)) {
|
|
371
|
-
cellPicked = cell.intersectWithLine(t1, t2, p1, p2,
|
|
371
|
+
cellPicked = cell.intersectWithLine(t1, t2, p1, p2, tolerance, x, pCoords);
|
|
372
372
|
} else {
|
|
373
|
-
cellPicked = cell.intersectWithLine(p1, p2,
|
|
373
|
+
cellPicked = cell.intersectWithLine(p1, p2, tolerance, x, pCoords);
|
|
374
374
|
}
|
|
375
375
|
}
|
|
376
376
|
if (cellPicked.intersect === 1 && cellPicked.t <= tMin + model.tolerance && cellPicked.t >= t1 && cellPicked.t <= t2) {
|
|
@@ -2,6 +2,7 @@ import { Vector3, Nullable } from './../../types';
|
|
|
2
2
|
import vtkAbstractPicker, { IAbstractPickerInitialValues } from './AbstractPicker';
|
|
3
3
|
import vtkActor from './Actor';
|
|
4
4
|
import vtkMapper from './Mapper';
|
|
5
|
+
import vtkProp3D from './Prop3D';
|
|
5
6
|
import vtkRenderer from './Renderer';
|
|
6
7
|
import { vtkSubscription } from './../../interfaces';
|
|
7
8
|
|
|
@@ -67,16 +68,6 @@ export interface vtkPicker extends vtkAbstractPicker {
|
|
|
67
68
|
*/
|
|
68
69
|
onPickChange(callback: OnPickChangeCallback): vtkSubscription;
|
|
69
70
|
|
|
70
|
-
/**
|
|
71
|
-
* Intersect data with specified ray.
|
|
72
|
-
* Project the center point of the mapper onto the ray and determine its parametric value
|
|
73
|
-
* @param {Vector3} p1
|
|
74
|
-
* @param {Vector3} p2
|
|
75
|
-
* @param {Number} tol
|
|
76
|
-
* @param {vtkMapper} mapper
|
|
77
|
-
*/
|
|
78
|
-
intersectWithLine(p1: Vector3, p2: Vector3, tol: number, mapper: vtkMapper): number;
|
|
79
|
-
|
|
80
71
|
/**
|
|
81
72
|
* Perform pick operation with selection point provided.
|
|
82
73
|
* @param {Vector3} selection First two values should be x-y pixel coordinate, the third is usually zero.
|
|
@@ -84,6 +75,15 @@ export interface vtkPicker extends vtkAbstractPicker {
|
|
|
84
75
|
*/
|
|
85
76
|
pick(selection: Vector3, renderer: vtkRenderer): void;
|
|
86
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Perform pick operation with the provided selection and focal points.
|
|
80
|
+
* Both point are in world coordinates.
|
|
81
|
+
* @param {Vector3} selectionPoint
|
|
82
|
+
* @param {Vector3} focalPoint
|
|
83
|
+
* @param {vtkRenderer} renderer
|
|
84
|
+
*/
|
|
85
|
+
pick3DPoint(selectionPoint: Vector3, focalPoint: Vector3, renderer: vtkRenderer): void;
|
|
86
|
+
|
|
87
87
|
/**
|
|
88
88
|
* Set position in mapper coordinates of pick point.
|
|
89
89
|
* @param {Number} x The x coordinate.
|
|
@@ -141,6 +141,8 @@ export function newInstance(initialValues?: IPickerInitialValues): vtkPicker;
|
|
|
141
141
|
* picking of points or cells based on the geometry of any vtkProp3D, use the
|
|
142
142
|
* subclasses vtkPointPicker or vtkCellPicker. For hardware-accelerated
|
|
143
143
|
* picking of any type of vtkProp, use vtkPropPicker or vtkWorldPointPicker.
|
|
144
|
+
*
|
|
145
|
+
* Note that only vtkProp3D's can be picked by vtkPicker.
|
|
144
146
|
*/
|
|
145
147
|
export declare const vtkPicker: {
|
|
146
148
|
newInstance: typeof newInstance,
|
package/Rendering/Core/Picker.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { m as macro } from '../../macros2.js';
|
|
2
2
|
import vtkAbstractPicker from './AbstractPicker.js';
|
|
3
3
|
import vtkBoundingBox from '../../Common/DataModel/BoundingBox.js';
|
|
4
|
-
import { d as dot, l as normalize,
|
|
5
|
-
import { mat4, vec4 } from 'gl-matrix';
|
|
4
|
+
import { d as dot, l as normalize, s as subtract, e as distance2BetweenPoints } from '../../Common/Core/Math/index.js';
|
|
5
|
+
import { vec3, mat4, vec4 } from 'gl-matrix';
|
|
6
6
|
|
|
7
7
|
const {
|
|
8
8
|
vtkErrorMacro
|
|
@@ -62,17 +62,114 @@ function vtkPicker(publicAPI, model) {
|
|
|
62
62
|
return Math.sqrt(tolerance);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Perform picking on the given renderer, given a ray defined in world coordinates.
|
|
67
|
+
* @param {*} renderer
|
|
68
|
+
* @param {*} tolerance
|
|
69
|
+
* @param {*} p1World
|
|
70
|
+
* @param {*} p2World
|
|
71
|
+
* @returns true if we picked something else false
|
|
72
|
+
*/
|
|
73
|
+
function pick3DInternal(renderer, tolerance, p1World, p2World) {
|
|
74
|
+
const p1Mapper = new Float64Array(4);
|
|
75
|
+
const p2Mapper = new Float64Array(4);
|
|
76
|
+
const ray = [];
|
|
77
|
+
const hitPosition = [];
|
|
78
|
+
const props = model.pickFromList ? model.pickList : renderer.getActors();
|
|
79
|
+
|
|
80
|
+
// pre-allocate some arrays.
|
|
81
|
+
const transformScale = new Float64Array(3);
|
|
82
|
+
const pickedPosition = new Float64Array(3);
|
|
83
|
+
|
|
84
|
+
// Loop over props.
|
|
85
|
+
// Transform ray (defined from position of camera to selection point) into coordinates of mapper (not
|
|
86
|
+
// transformed to actors coordinates! Reduces overall computation!!!).
|
|
87
|
+
// Note that only vtkProp3D's can be picked by vtkPicker.
|
|
88
|
+
props.forEach(prop => {
|
|
89
|
+
const mapper = prop.getMapper();
|
|
90
|
+
const propIsNotFullyTranslucent = prop.getProperty?.()?.getOpacity() > 0.0;
|
|
91
|
+
const pickable = prop.getNestedPickable() && prop.getNestedVisibility() && propIsNotFullyTranslucent;
|
|
92
|
+
if (!pickable) {
|
|
93
|
+
// prop cannot be picked
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The prop is candidate for picking:
|
|
98
|
+
// - get its composite matrix and invert it
|
|
99
|
+
// - use the inverted matrix to transform the ray points into mapper coordinates
|
|
100
|
+
model.transformMatrix = prop.getMatrix().slice(0);
|
|
101
|
+
mat4.transpose(model.transformMatrix, model.transformMatrix);
|
|
102
|
+
mat4.invert(model.transformMatrix, model.transformMatrix);
|
|
103
|
+
vec4.transformMat4(p1Mapper, p1World, model.transformMatrix);
|
|
104
|
+
vec4.transformMat4(p2Mapper, p2World, model.transformMatrix);
|
|
105
|
+
vec3.scale(p1Mapper, p1Mapper, 1 / p1Mapper[3]);
|
|
106
|
+
vec3.scale(p2Mapper, p2Mapper, 1 / p2Mapper[3]);
|
|
107
|
+
subtract(p2Mapper, p1Mapper, ray);
|
|
108
|
+
|
|
109
|
+
// We now have the ray endpoints in mapper coordinates.
|
|
110
|
+
// Compare it with the mapper bounds to check if intersection is possible.
|
|
111
|
+
|
|
112
|
+
// Get the bounding box of the mapper.
|
|
113
|
+
// Note that the tolerance is added to the bounding box to make sure things on the edge of the
|
|
114
|
+
// bounding box are picked correctly.
|
|
115
|
+
const bounds = mapper ? vtkBoundingBox.inflate(mapper.getBounds(), tolerance) : [...vtkBoundingBox.INIT_BOUNDS];
|
|
116
|
+
if (vtkBoundingBox.intersectBox(bounds, p1Mapper, ray, hitPosition, [])) {
|
|
117
|
+
mat4.getScaling(transformScale, model.transformMatrix);
|
|
118
|
+
const t = model.intersectWithLine(p1Mapper, p2Mapper, tolerance * 0.333 * (transformScale[0] + transformScale[1] + transformScale[2]), prop, mapper);
|
|
119
|
+
if (t < Number.MAX_VALUE) {
|
|
120
|
+
pickedPosition[0] = (1.0 - t) * p1World[0] + t * p2World[0];
|
|
121
|
+
pickedPosition[1] = (1.0 - t) * p1World[1] + t * p2World[1];
|
|
122
|
+
pickedPosition[2] = (1.0 - t) * p1World[2] + t * p2World[2];
|
|
123
|
+
const actorIndex = model.actors.indexOf(prop);
|
|
124
|
+
if (actorIndex !== -1) {
|
|
125
|
+
// If already in list, compare the previous picked position with the new one.
|
|
126
|
+
// Store the new one if it is closer from the ray endpoint.
|
|
127
|
+
const previousPickedPosition = model.pickedPositions[actorIndex];
|
|
128
|
+
if (distance2BetweenPoints(p1World, pickedPosition) < distance2BetweenPoints(p1World, previousPickedPosition)) {
|
|
129
|
+
model.pickedPositions[actorIndex] = pickedPosition.slice(0);
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
model.actors.push(prop);
|
|
133
|
+
model.pickedPositions.push(pickedPosition.slice(0));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// sort array by distance
|
|
140
|
+
const tempArray = [];
|
|
141
|
+
for (let i = 0; i < model.pickedPositions.length; i++) {
|
|
142
|
+
tempArray.push({
|
|
143
|
+
actor: model.actors[i],
|
|
144
|
+
pickedPosition: model.pickedPositions[i],
|
|
145
|
+
distance2: distance2BetweenPoints(p1World, model.pickedPositions[i])
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
tempArray.sort((a, b) => {
|
|
149
|
+
const keyA = a.distance2;
|
|
150
|
+
const keyB = b.distance2;
|
|
151
|
+
// order the actors based on the distance2 attribute, so the near actors comes
|
|
152
|
+
// first in the list
|
|
153
|
+
if (keyA < keyB) return -1;
|
|
154
|
+
if (keyA > keyB) return 1;
|
|
155
|
+
return 0;
|
|
156
|
+
});
|
|
157
|
+
model.pickedPositions = [];
|
|
158
|
+
model.actors = [];
|
|
159
|
+
tempArray.forEach(obj => {
|
|
160
|
+
model.pickedPositions.push(obj.pickedPosition);
|
|
161
|
+
model.actors.push(obj.actor);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
65
165
|
// Intersect data with specified ray.
|
|
66
166
|
// Project the center point of the mapper onto the ray and determine its parametric value
|
|
67
|
-
|
|
167
|
+
model.intersectWithLine = (p1, p2, tolerance, prop, mapper) => {
|
|
68
168
|
if (!mapper) {
|
|
69
169
|
return Number.MAX_VALUE;
|
|
70
170
|
}
|
|
71
171
|
const center = mapper.getCenter();
|
|
72
|
-
const ray =
|
|
73
|
-
for (let i = 0; i < 3; i++) {
|
|
74
|
-
ray[i] = p2[i] - p1[i];
|
|
75
|
-
}
|
|
172
|
+
const ray = vec3.subtract(new Float64Array(3), p2, p1);
|
|
76
173
|
const rayFactor = dot(ray, ray);
|
|
77
174
|
if (rayFactor === 0.0) {
|
|
78
175
|
return 2.0;
|
|
@@ -86,55 +183,43 @@ function vtkPicker(publicAPI, model) {
|
|
|
86
183
|
// To be overridden in subclasses
|
|
87
184
|
publicAPI.pick = (selection, renderer) => {
|
|
88
185
|
if (selection.length !== 3) {
|
|
89
|
-
vtkWarningMacro('vtkPicker
|
|
186
|
+
vtkWarningMacro('vtkPicker.pick - selection needs three components');
|
|
90
187
|
}
|
|
188
|
+
if (!renderer) {
|
|
189
|
+
vtkErrorMacro('vtkPicker.pick - renderer cannot be null');
|
|
190
|
+
throw new Error('renderer cannot be null');
|
|
191
|
+
}
|
|
192
|
+
initialize();
|
|
91
193
|
const selectionX = selection[0];
|
|
92
194
|
const selectionY = selection[1];
|
|
93
195
|
let selectionZ = selection[2];
|
|
94
|
-
let cameraPos = [];
|
|
95
|
-
let cameraFP = [];
|
|
96
|
-
let displayCoords = [];
|
|
97
|
-
let worldCoords = [];
|
|
98
|
-
const ray = [];
|
|
99
|
-
const cameraDOP = [];
|
|
100
|
-
let clipRange = [];
|
|
101
|
-
let tF;
|
|
102
|
-
let tB;
|
|
103
|
-
const p1World = [];
|
|
104
|
-
const p2World = [];
|
|
105
|
-
let props = [];
|
|
106
|
-
let pickable = false;
|
|
107
|
-
const p1Mapper = new Float64Array(4);
|
|
108
|
-
const p2Mapper = new Float64Array(4);
|
|
109
|
-
const bbox = vtkBoundingBox.newInstance();
|
|
110
|
-
const t = [];
|
|
111
|
-
const hitPosition = [];
|
|
112
|
-
const view = renderer.getRenderWindow().getViews()[0];
|
|
113
|
-
initialize();
|
|
114
196
|
model.renderer = renderer;
|
|
115
197
|
model.selectionPoint[0] = selectionX;
|
|
116
198
|
model.selectionPoint[1] = selectionY;
|
|
117
199
|
model.selectionPoint[2] = selectionZ;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
200
|
+
const p1World = new Float64Array(4);
|
|
201
|
+
const p2World = new Float64Array(4);
|
|
122
202
|
|
|
123
203
|
// Get camera focal point and position. Convert to display (screen)
|
|
124
204
|
// coordinates. We need a depth value for z-buffer.
|
|
125
205
|
const camera = renderer.getActiveCamera();
|
|
126
|
-
cameraPos = camera.getPosition();
|
|
127
|
-
cameraFP = camera.getFocalPoint();
|
|
206
|
+
const cameraPos = camera.getPosition();
|
|
207
|
+
const cameraFP = camera.getFocalPoint();
|
|
208
|
+
const view = renderer.getRenderWindow().getViews()[0];
|
|
128
209
|
const dims = view.getViewportSize(renderer);
|
|
210
|
+
if (dims[1] === 0) {
|
|
211
|
+
vtkWarningMacro('vtkPicker.pick - viewport area is 0');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
129
214
|
const aspect = dims[0] / dims[1];
|
|
215
|
+
let displayCoords = [];
|
|
130
216
|
displayCoords = renderer.worldToNormalizedDisplay(cameraFP[0], cameraFP[1], cameraFP[2], aspect);
|
|
131
217
|
displayCoords = view.normalizedDisplayToDisplay(displayCoords[0], displayCoords[1], displayCoords[2]);
|
|
132
218
|
selectionZ = displayCoords[2];
|
|
133
|
-
const tolerance = computeTolerance(selectionZ, aspect, renderer) * model.tolerance;
|
|
134
219
|
|
|
135
220
|
// Convert the selection point into world coordinates.
|
|
136
221
|
const normalizedDisplay = view.displayToNormalizedDisplay(selectionX, selectionY, selectionZ);
|
|
137
|
-
worldCoords = renderer.normalizedDisplayToWorld(normalizedDisplay[0], normalizedDisplay[1], normalizedDisplay[2], aspect);
|
|
222
|
+
const worldCoords = renderer.normalizedDisplayToWorld(normalizedDisplay[0], normalizedDisplay[1], normalizedDisplay[2], aspect);
|
|
138
223
|
for (let i = 0; i < 3; i++) {
|
|
139
224
|
model.pickPosition[i] = worldCoords[i];
|
|
140
225
|
}
|
|
@@ -143,9 +228,11 @@ function vtkPicker(publicAPI, model) {
|
|
|
143
228
|
// the camera position to the selection point, starting where this line
|
|
144
229
|
// intersects the front clipping plane, and terminating where this
|
|
145
230
|
// line intersects the back clipping plane.
|
|
231
|
+
const ray = [];
|
|
146
232
|
for (let i = 0; i < 3; i++) {
|
|
147
233
|
ray[i] = model.pickPosition[i] - cameraPos[i];
|
|
148
234
|
}
|
|
235
|
+
const cameraDOP = [];
|
|
149
236
|
for (let i = 0; i < 3; i++) {
|
|
150
237
|
cameraDOP[i] = cameraFP[i] - cameraPos[i];
|
|
151
238
|
}
|
|
@@ -155,7 +242,9 @@ function vtkPicker(publicAPI, model) {
|
|
|
155
242
|
vtkWarningMacro('Picker::Pick Cannot process points');
|
|
156
243
|
return;
|
|
157
244
|
}
|
|
158
|
-
clipRange = camera.getClippingRange();
|
|
245
|
+
const clipRange = camera.getClippingRange();
|
|
246
|
+
let tF;
|
|
247
|
+
let tB;
|
|
159
248
|
if (camera.getParallelProjection()) {
|
|
160
249
|
tF = clipRange[0] - rayLength;
|
|
161
250
|
tB = clipRange[1] - rayLength;
|
|
@@ -173,105 +262,25 @@ function vtkPicker(publicAPI, model) {
|
|
|
173
262
|
}
|
|
174
263
|
p1World[3] = 1.0;
|
|
175
264
|
p2World[3] = 1.0;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
265
|
+
const tolerance = computeTolerance(selectionZ, aspect, renderer) * model.tolerance;
|
|
266
|
+
pick3DInternal(model.renderer, tolerance, p1World, p2World);
|
|
267
|
+
};
|
|
268
|
+
publicAPI.pick3DPoint = (selectionPoint, focalPoint, renderer) => {
|
|
269
|
+
if (!renderer) {
|
|
270
|
+
throw new Error('renderer cannot be null');
|
|
180
271
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
model.transformMatrix = prop.getMatrix().slice(0);
|
|
190
|
-
// Webgl need a transpose matrix but we need the untransposed one to project world points
|
|
191
|
-
// into the right referential
|
|
192
|
-
mat4.transpose(model.transformMatrix, model.transformMatrix);
|
|
193
|
-
mat4.invert(model.transformMatrix, model.transformMatrix);
|
|
194
|
-
// Extract scale
|
|
195
|
-
const col1 = [model.transformMatrix[0], model.transformMatrix[1], model.transformMatrix[2]];
|
|
196
|
-
const col2 = [model.transformMatrix[4], model.transformMatrix[5], model.transformMatrix[6]];
|
|
197
|
-
const col3 = [model.transformMatrix[8], model.transformMatrix[9], model.transformMatrix[10]];
|
|
198
|
-
scale[0] = norm(col1);
|
|
199
|
-
scale[1] = norm(col2);
|
|
200
|
-
scale[2] = norm(col3);
|
|
201
|
-
vec4.transformMat4(p1Mapper, p1World, model.transformMatrix);
|
|
202
|
-
vec4.transformMat4(p2Mapper, p2World, model.transformMatrix);
|
|
203
|
-
p1Mapper[0] /= p1Mapper[3];
|
|
204
|
-
p1Mapper[1] /= p1Mapper[3];
|
|
205
|
-
p1Mapper[2] /= p1Mapper[3];
|
|
206
|
-
p2Mapper[0] /= p2Mapper[3];
|
|
207
|
-
p2Mapper[1] /= p2Mapper[3];
|
|
208
|
-
p2Mapper[2] /= p2Mapper[3];
|
|
209
|
-
for (let i = 0; i < 3; i++) {
|
|
210
|
-
ray[i] = p2Mapper[i] - p1Mapper[i];
|
|
211
|
-
}
|
|
212
|
-
if (mapper) {
|
|
213
|
-
bbox.setBounds(mapper.getBounds());
|
|
214
|
-
bbox.inflate(tolerance);
|
|
215
|
-
} else {
|
|
216
|
-
bbox.reset();
|
|
217
|
-
}
|
|
218
|
-
if (bbox.intersectBox(p1Mapper, ray, hitPosition, t)) {
|
|
219
|
-
t[0] = publicAPI.intersectWithLine(p1Mapper, p2Mapper, tolerance * 0.333 * (scale[0] + scale[1] + scale[2]), prop, mapper);
|
|
220
|
-
if (t[0] < Number.MAX_VALUE) {
|
|
221
|
-
const p = [];
|
|
222
|
-
p[0] = (1.0 - t[0]) * p1World[0] + t[0] * p2World[0];
|
|
223
|
-
p[1] = (1.0 - t[0]) * p1World[1] + t[0] * p2World[1];
|
|
224
|
-
p[2] = (1.0 - t[0]) * p1World[2] + t[0] * p2World[2];
|
|
225
|
-
|
|
226
|
-
// Check if the current actor is already in the list
|
|
227
|
-
let actorID = -1;
|
|
228
|
-
for (let i = 0; i < model.actors.length; i++) {
|
|
229
|
-
if (model.actors[i] === prop) {
|
|
230
|
-
actorID = i;
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
if (actorID === -1) {
|
|
235
|
-
model.actors.push(prop);
|
|
236
|
-
model.pickedPositions.push(p);
|
|
237
|
-
} else {
|
|
238
|
-
const oldPoint = model.pickedPositions[actorID];
|
|
239
|
-
const distOld = distance2BetweenPoints(p1World, oldPoint);
|
|
240
|
-
const distCurrent = distance2BetweenPoints(p1World, p);
|
|
241
|
-
if (distCurrent < distOld) {
|
|
242
|
-
model.pickedPositions[actorID] = p;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
publicAPI.invokePickChange(model.pickedPositions);
|
|
249
|
-
return 1;
|
|
250
|
-
});
|
|
251
|
-
// sort array by distance
|
|
252
|
-
const tempArray = [];
|
|
253
|
-
for (let i = 0; i < model.pickedPositions.length; i++) {
|
|
254
|
-
tempArray.push({
|
|
255
|
-
actor: model.actors[i],
|
|
256
|
-
pickedPosition: model.pickedPositions[i],
|
|
257
|
-
distance2: distance2BetweenPoints(p1World, model.pickedPositions[i])
|
|
258
|
-
});
|
|
272
|
+
initialize();
|
|
273
|
+
model.renderer = renderer;
|
|
274
|
+
vec3.copy(model.selectionPoint, selectionPoint);
|
|
275
|
+
const view = renderer.getRenderWindow().getViews()[0];
|
|
276
|
+
const dims = view.getViewportSize(renderer);
|
|
277
|
+
if (dims[1] === 0) {
|
|
278
|
+
vtkWarningMacro('vtkPicker.pick3DPoint - viewport area is 0');
|
|
279
|
+
return;
|
|
259
280
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
// order the actors based on the distance2 attribute, so the near actors comes
|
|
264
|
-
// first in the list
|
|
265
|
-
if (keyA < keyB) return -1;
|
|
266
|
-
if (keyA > keyB) return 1;
|
|
267
|
-
return 0;
|
|
268
|
-
});
|
|
269
|
-
model.pickedPositions = [];
|
|
270
|
-
model.actors = [];
|
|
271
|
-
tempArray.forEach(obj => {
|
|
272
|
-
model.pickedPositions.push(obj.pickedPosition);
|
|
273
|
-
model.actors.push(obj.actor);
|
|
274
|
-
});
|
|
281
|
+
const aspect = dims[0] / dims[1];
|
|
282
|
+
const tolerance = computeTolerance(model.selectionPoint[2], aspect, renderer) * model.tolerance;
|
|
283
|
+
pick3DInternal(renderer, tolerance, selectionPoint, focalPoint);
|
|
275
284
|
};
|
|
276
285
|
}
|
|
277
286
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import vtkPicker, { IPickerInitialValues } from './Picker';
|
|
2
2
|
import vtkMapper from './Mapper';
|
|
3
3
|
import { Vector3 } from './../../types';
|
|
4
|
+
import vtkProp3D from './Prop3D';
|
|
5
|
+
import { Nullable } from './../../types';
|
|
4
6
|
|
|
5
7
|
interface IPointPickerInitialValues extends IPickerInitialValues {
|
|
6
8
|
pointId?: number;
|
|
@@ -31,24 +33,6 @@ export interface vtkPointPicker extends vtkPicker {
|
|
|
31
33
|
*/
|
|
32
34
|
getUseCells(): boolean;
|
|
33
35
|
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
36
|
-
* @param {Vector3} p1
|
|
37
|
-
* @param {Vector3} p2
|
|
38
|
-
* @param {Number} tol
|
|
39
|
-
* @param {vtkMapper} mapper
|
|
40
|
-
*/
|
|
41
|
-
intersectWithLine(p1: Vector3, p2: Vector3, tol: number, mapper: vtkMapper): number;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
*
|
|
45
|
-
* @param {Vector3} p1
|
|
46
|
-
* @param {Vector3} p2
|
|
47
|
-
* @param {Number} tol
|
|
48
|
-
* @param {vtkMapper} mapper
|
|
49
|
-
*/
|
|
50
|
-
intersectActorWithLine(p1: Vector3, p2: Vector3, tol: number, mapper: vtkMapper): number;
|
|
51
|
-
|
|
52
36
|
/**
|
|
53
37
|
* Specify whether the point search should be based on cell points or directly on the point list.
|
|
54
38
|
* @param useCells
|
|
@@ -13,7 +13,7 @@ const {
|
|
|
13
13
|
function vtkPointPicker(publicAPI, model) {
|
|
14
14
|
// Set our className
|
|
15
15
|
model.classHierarchy.push('vtkPointPicker');
|
|
16
|
-
|
|
16
|
+
model.intersectWithLine = (p1, p2, tolerance, prop, mapper) => {
|
|
17
17
|
let tMin = Number.MAX_VALUE;
|
|
18
18
|
if (mapper.isA('vtkImageMapper') || mapper.isA('vtkImageArrayMapper')) {
|
|
19
19
|
const pickData = mapper.intersectWithLineForPointPicking(p1, p2);
|
|
@@ -22,11 +22,11 @@ function vtkPointPicker(publicAPI, model) {
|
|
|
22
22
|
model.pointIJK = pickData.ijk;
|
|
23
23
|
}
|
|
24
24
|
} else if (mapper.isA('vtkMapper')) {
|
|
25
|
-
tMin =
|
|
25
|
+
tMin = model.intersectActorWithLine(p1, p2, tolerance, mapper);
|
|
26
26
|
}
|
|
27
27
|
return tMin;
|
|
28
28
|
};
|
|
29
|
-
|
|
29
|
+
model.intersectActorWithLine = (p1, p2, tolerance, mapper) => {
|
|
30
30
|
// Get dataset
|
|
31
31
|
const input = mapper.getInputData();
|
|
32
32
|
|
|
@@ -77,7 +77,7 @@ function vtkPointPicker(publicAPI, model) {
|
|
|
77
77
|
maxDist = dist;
|
|
78
78
|
}
|
|
79
79
|
} // end for i
|
|
80
|
-
if (maxDist <=
|
|
80
|
+
if (maxDist <= tolerance && maxDist < minPtDist) {
|
|
81
81
|
// within tolerance
|
|
82
82
|
minPtId = ptId;
|
|
83
83
|
minPtDist = maxDist;
|
|
@@ -105,7 +105,7 @@ function vtkPointPicker(publicAPI, model) {
|
|
|
105
105
|
maxDist = dist;
|
|
106
106
|
}
|
|
107
107
|
} // end for i
|
|
108
|
-
if (maxDist <=
|
|
108
|
+
if (maxDist <= tolerance && maxDist < minPtDist) {
|
|
109
109
|
// within tolerance
|
|
110
110
|
minPtId = ptId;
|
|
111
111
|
minPtDist = maxDist;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import vtkPiecewiseFunction from './../../Common/DataModel/PiecewiseFunction';
|
|
2
2
|
import { Bounds, Range } from './../../types';
|
|
3
|
-
import
|
|
3
|
+
import vtkAbstractMapper3D, { IAbstractMapper3DInitialValues } from './AbstractMapper3D';
|
|
4
4
|
import { BlendMode, FilterMode } from './VolumeMapper/Constants';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
*
|
|
8
8
|
*/
|
|
9
|
-
export interface IVolumeMapperInitialValues extends
|
|
9
|
+
export interface IVolumeMapperInitialValues extends IAbstractMapper3DInitialValues {
|
|
10
10
|
anisotropy?: number;
|
|
11
11
|
autoAdjustSampleDistances?: boolean;
|
|
12
12
|
averageIPScalarRange?: Range;
|
|
@@ -23,7 +23,7 @@ export interface IVolumeMapperInitialValues extends IAbstractMapperInitialValues
|
|
|
23
23
|
LAOKernelSize?: number;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export interface vtkVolumeMapper extends
|
|
26
|
+
export interface vtkVolumeMapper extends vtkAbstractMapper3D {
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Get the bounds for this mapper as [xmin, xmax, ymin, ymax,zmin, zmax].
|
|
@@ -487,7 +487,7 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
|
|
|
487
487
|
// weighting shift and scale
|
|
488
488
|
`uniform float pwfshift0;`, `uniform float pwfscale0;`];
|
|
489
489
|
if (useProjection) {
|
|
490
|
-
tcoordFSDec.push('uniform vec3
|
|
490
|
+
tcoordFSDec.push('uniform vec3 volumeSizeMC;', 'uniform int projectionSlabNumberOfSamples;', 'uniform float projectionConstantOffset;', 'uniform float projectionStepLength;');
|
|
491
491
|
}
|
|
492
492
|
if (isDirectionUniform) {
|
|
493
493
|
tcoordFSDec.push('in vec3 samplingDirVSOutput;');
|
|
@@ -571,7 +571,7 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
|
|
|
571
571
|
}
|
|
572
572
|
|
|
573
573
|
// Loop on all the samples of the projection
|
|
574
|
-
tcoordFSImpl.push('vec3 projectionScaledDirection = projectionDirection /
|
|
574
|
+
tcoordFSImpl.push('vec3 projectionScaledDirection = projectionDirection / volumeSizeMC;', 'vec3 projectionStep = projectionStepLength * projectionScaledDirection;', 'vec3 projectionStartPosition = volumePosTC + projectionConstantOffset * projectionScaledDirection;', 'vec4 tvalue = initialProjectionTextureValue;', 'for (int projectionSampleIdx = 0; projectionSampleIdx < projectionSlabNumberOfSamples; ++projectionSampleIdx) {', ' vec3 projectionSamplePosition = projectionStartPosition + float(projectionSampleIdx) * projectionStep;', ' vec4 sampledTextureValue = texture(volumeTexture, projectionSamplePosition);');
|
|
575
575
|
switch (projectionMode) {
|
|
576
576
|
case ProjectionMode.MAX:
|
|
577
577
|
tcoordFSImpl.push(' tvalue = max(tvalue, sampledTextureValue);');
|
|
@@ -706,9 +706,11 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
|
|
|
706
706
|
if (model.renderable.isProjectionEnabled()) {
|
|
707
707
|
const image = model.currentImageDataInput;
|
|
708
708
|
const spacing = image.getSpacing();
|
|
709
|
+
const dimensions = image.getDimensions();
|
|
709
710
|
const projectionSlabThickness = model.renderable.getProjectionSlabThickness();
|
|
710
711
|
const projectionSlabNumberOfSamples = model.renderable.getProjectionSlabNumberOfSamples();
|
|
711
|
-
|
|
712
|
+
const volumeSize = vec3.mul([], spacing, dimensions);
|
|
713
|
+
program.setUniform3fArray('volumeSizeMC', volumeSize);
|
|
712
714
|
program.setUniformi('projectionSlabNumberOfSamples', projectionSlabNumberOfSamples);
|
|
713
715
|
const constantOffset = -0.5 * projectionSlabThickness;
|
|
714
716
|
program.setUniformf('projectionConstantOffset', constantOffset);
|
|
@@ -135,7 +135,7 @@ function widgetBehavior(publicAPI, model) {
|
|
|
135
135
|
const step = previousPosition.y - callData.position.y;
|
|
136
136
|
publicAPI.translateCenterOnPlaneDirection(step);
|
|
137
137
|
previousPosition = callData.position;
|
|
138
|
-
publicAPI.
|
|
138
|
+
publicAPI.invokeInteractionEvent(publicAPI.getActiveInteraction());
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
return macro.VOID;
|
|
@@ -164,7 +164,7 @@ function widgetBehavior(publicAPI, model) {
|
|
|
164
164
|
const step = calldata.spinY;
|
|
165
165
|
isScrolling = true;
|
|
166
166
|
publicAPI.translateCenterOnPlaneDirection(step);
|
|
167
|
-
publicAPI.
|
|
167
|
+
publicAPI.invokeInteractionEvent(
|
|
168
168
|
// Force interaction mode because mouse cursor could be above rotation handle
|
|
169
169
|
InteractionMethodsName.TranslateCenter);
|
|
170
170
|
isScrolling = false;
|
|
@@ -187,20 +187,11 @@ function widgetBehavior(publicAPI, model) {
|
|
|
187
187
|
if (model.activeState.getActive()) {
|
|
188
188
|
const methodName = publicAPI.getActiveInteraction();
|
|
189
189
|
publicAPI[methodName](callData);
|
|
190
|
-
publicAPI.
|
|
190
|
+
publicAPI.invokeInteractionEvent(methodName);
|
|
191
191
|
return macro.EVENT_ABORT;
|
|
192
192
|
}
|
|
193
193
|
return macro.VOID;
|
|
194
194
|
};
|
|
195
|
-
publicAPI.invokeInternalInteractionEvent = function () {
|
|
196
|
-
let methodName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : publicAPI.getActiveInteraction();
|
|
197
|
-
const computeFocalPointOffset = methodName !== InteractionMethodsName.RotateLine;
|
|
198
|
-
const canUpdateFocalPoint = methodName === InteractionMethodsName.RotateLine;
|
|
199
|
-
publicAPI.invokeInteractionEvent({
|
|
200
|
-
computeFocalPointOffset,
|
|
201
|
-
canUpdateFocalPoint
|
|
202
|
-
});
|
|
203
|
-
};
|
|
204
195
|
publicAPI.startInteraction = () => {
|
|
205
196
|
publicAPI.invokeStartInteractionEvent();
|
|
206
197
|
// When interacting, plane actor and lines must be re-rendered on other views
|
|
@@ -215,8 +215,8 @@ function vtkResliceCursorWidget(publicAPI, model) {
|
|
|
215
215
|
// Methods
|
|
216
216
|
// --------------------------------------------------------------------------
|
|
217
217
|
|
|
218
|
-
publicAPI.updateCameraPoints = (renderer, viewType, resetFocalPoint,
|
|
219
|
-
publicAPI.resetCamera(renderer, viewType, resetFocalPoint,
|
|
218
|
+
publicAPI.updateCameraPoints = (renderer, viewType, resetFocalPoint, computeFocalPointOffset) => {
|
|
219
|
+
publicAPI.resetCamera(renderer, viewType, resetFocalPoint, !computeFocalPointOffset);
|
|
220
220
|
if (computeFocalPointOffset) {
|
|
221
221
|
computeFocalPointOffsetFromResliceCursorCenter(viewType, renderer);
|
|
222
222
|
}
|