Picker

Introduction

vtkPicker is used to select instances of vtkProp3D by shooting
a ray into a graphics window and intersecting with the actor’s bounding box.
The ray is defined from a point defined in window (or pixel) coordinates,
and a point located from the camera’s position.

vtkPicker may return more than one vtkProp3D, since more than one bounding box may be intersected.
vtkPicker returns an unsorted list of props that were hit, and a list of the corresponding world points of the hits.
For the vtkProp3D that is closest to the camera, vtkPicker returns the pick coordinates in world and untransformed mapper space,
the prop itself, the data set, and the mapper.
For vtkPicker the closest prop is the one whose center point (i.e., center of bounding box) projected on the view ray is closest
to the camera. Subclasses of vtkPicker use other methods for computing the pick point.

vtkPicker is used for quick geometric picking. If you desire more precise
picking of points or cells based on the geometry of any vtkProp3D, use the
subclasses vtkPointPicker or vtkCellPicker. For hardware-accelerated
picking of any type of vtkProp, use vtkPropPicker or vtkWorldPointPicker.

Note that only vtkProp3D’s can be picked by vtkPicker.

Methods

extend

Method use to decorate a given object (publicAPI+model) with vtkRenderer characteristics.

Argument Type Required Description
publicAPI Yes object on which methods will be bounds (public)
model Yes object on which data structure will be bounds (protected)
initialValues IPickerInitialValues No

getActors

Get a collection of all the actors that were intersected.

getDataSet

Get the dataset that was picked (if any).

getMapper

Get mapper that was picked (if any)

getMapperPosition

Get position in mapper (i.e., non-transformed) coordinates of pick point.

getMapperPositionByReference

Get position in mapper (i.e., non-transformed) coordinates of pick point.

getPickedPositions

Get a list of the points the actors returned by getActors were intersected at.

getTolerance

Get tolerance for performing pick operation.

invokePickChange

Invoke a pick change event with the list of picked points.
This function is called internally by VTK.js and is not intended for public use.

Argument Type Required Description
pickedPositions Array. Yes

newInstance

Method use to create a new instance of vtkPicker with its focal point at the origin,
and position=(0,0,1). The view up is along the y-axis, view angle is 30 degrees,
and the clipping range is (.1,1000).

Argument Type Required Description
initialValues IPickerInitialValues No for pre-setting some of its content

onPickChange

Execute the given callback when the pickChange event is fired.
The callback receives an array of picked point positions.

pick

Perform pick operation with selection point provided.

Argument Type Required Description
selection Vector3 Yes First two values should be x-y pixel coordinate, the third is usually zero.
renderer vtkRenderer Yes The renderer on which you want to do picking.

pick3DPoint

Perform pick operation with the provided selection and focal points.
Both point are in world coordinates.

Argument Type Required Description
selectionPoint Vector3 Yes
focalPoint Vector3 Yes
renderer vtkRenderer Yes

setMapperPosition

Set position in mapper coordinates of pick point.

Argument Type Required Description
x Number Yes The x coordinate.
y Number Yes The y coordinate.
z Number Yes The z coordinate.

setMapperPositionFrom

Set position in mapper coordinates of pick point.

Argument Type Required Description
mapperPosition Vector3 Yes The mapper coordinates of pick point.

setTolerance

Specify tolerance for performing pick operation. Tolerance is specified
as fraction of rendering window size. (Rendering window size is measured
across diagonal.)

Argument Type Required Description
tolerance Number Yes The tolerance value.

Source

index.d.ts
import { Vector3, Nullable } from '../../../types';
import vtkAbstractPicker, {
IAbstractPickerInitialValues,
} from '../AbstractPicker';
import vtkActor from '../Actor';
import vtkMapper from '../Mapper';
import vtkProp3D from '../Prop3D';
import vtkRenderer from '../Renderer';
import { vtkSubscription } from '../../../interfaces';

export interface IPickerInitialValues extends IAbstractPickerInitialValues {
tolerance?: number;
mapperPosition?: number[];
actors?: vtkActor[];
pickedPositions?: Array<any>;
globalTMin?: number;
}

type OnPickChangeCallback = (pickedPositions: Vector3[]) => void;

export interface vtkPicker extends vtkAbstractPicker {
/**
* Get a collection of all the actors that were intersected.
*/
getActors(): vtkActor[];

/**
* Get the dataset that was picked (if any).
*/
getDataSet(): any;

/**
* Get mapper that was picked (if any)
*/
getMapper(): Nullable<vtkMapper>;

/**
* Get position in mapper (i.e., non-transformed) coordinates of pick point.
*/
getMapperPosition(): Vector3;

/**
* Get position in mapper (i.e., non-transformed) coordinates of pick point.
*/
getMapperPositionByReference(): Vector3;

/**
* Get a list of the points the actors returned by getActors were intersected at.
*/
getPickedPositions(): Vector3[];

/**
* Get tolerance for performing pick operation.
*/
getTolerance(): number;

/**
* Invoke a pick change event with the list of picked points.
* This function is called internally by VTK.js and is not intended for public use.
* @param {Vector3[]} pickedPositions
*/
invokePickChange(pickedPositions: Vector3[]): void;

/**
* Execute the given callback when the pickChange event is fired.
* The callback receives an array of picked point positions.
* @param {OnPickChangeCallback}
*/
onPickChange(callback: OnPickChangeCallback): vtkSubscription;

/**
* Perform pick operation with selection point provided.
* @param {Vector3} selection First two values should be x-y pixel coordinate, the third is usually zero.
* @param {vtkRenderer} renderer The renderer on which you want to do picking.
*/
pick(selection: Vector3, renderer: vtkRenderer): void;

/**
* Perform pick operation with the provided selection and focal points.
* Both point are in world coordinates.
* @param {Vector3} selectionPoint
* @param {Vector3} focalPoint
* @param {vtkRenderer} renderer
*/
pick3DPoint(
selectionPoint: Vector3,
focalPoint: Vector3,
renderer: vtkRenderer
): void;

/**
* Set position in mapper coordinates of pick point.
* @param {Number} x The x coordinate.
* @param {Number} y The y coordinate.
* @param {Number} z The z coordinate.
*/
setMapperPosition(x: number, y: number, z: number): boolean;

/**
* Set position in mapper coordinates of pick point.
* @param {Vector3} mapperPosition The mapper coordinates of pick point.
*/
setMapperPositionFrom(mapperPosition: Vector3): boolean;

/**
* Specify tolerance for performing pick operation. Tolerance is specified
* as fraction of rendering window size. (Rendering window size is measured
* across diagonal.)
* @param {Number} tolerance The tolerance value.
*/
setTolerance(tolerance: number): boolean;
}

/**
* Method use to decorate a given object (publicAPI+model) with vtkRenderer characteristics.
*
* @param publicAPI object on which methods will be bounds (public)
* @param model object on which data structure will be bounds (protected)
* @param {IPickerInitialValues} [initialValues]
*/
export function extend(
publicAPI: object,
model: object,
initialValues?: IPickerInitialValues
): void;

/**
* Method use to create a new instance of vtkPicker with its focal point at the origin,
* and position=(0,0,1). The view up is along the y-axis, view angle is 30 degrees,
* and the clipping range is (.1,1000).
* @param {IPickerInitialValues} [initialValues] for pre-setting some of its content
*/
export function newInstance(initialValues?: IPickerInitialValues): vtkPicker;

/**
* vtkPicker is used to select instances of vtkProp3D by shooting
* a ray into a graphics window and intersecting with the actor's bounding box.
* The ray is defined from a point defined in window (or pixel) coordinates,
* and a point located from the camera's position.
*
* vtkPicker may return more than one vtkProp3D, since more than one bounding box may be intersected.
* vtkPicker returns an unsorted list of props that were hit, and a list of the corresponding world points of the hits.
* For the vtkProp3D that is closest to the camera, vtkPicker returns the pick coordinates in world and untransformed mapper space,
* the prop itself, the data set, and the mapper.
* For vtkPicker the closest prop is the one whose center point (i.e., center of bounding box) projected on the view ray is closest
* to the camera. Subclasses of vtkPicker use other methods for computing the pick point.
*
* vtkPicker is used for quick geometric picking. If you desire more precise
* picking of points or cells based on the geometry of any vtkProp3D, use the
* subclasses vtkPointPicker or vtkCellPicker. For hardware-accelerated
* picking of any type of vtkProp, use vtkPropPicker or vtkWorldPointPicker.
*
* Note that only vtkProp3D's can be picked by vtkPicker.
*/
export declare const vtkPicker: {
newInstance: typeof newInstance;
extend: typeof extend;
};
export default vtkPicker;
index.js
import macro from 'vtk.js/Sources/macros';
import vtkAbstractPicker from 'vtk.js/Sources/Rendering/Core/AbstractPicker';
import vtkBoundingBox from 'vtk.js/Sources/Common/DataModel/BoundingBox';
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
import { mat4, vec3, vec4 } from 'gl-matrix';

const { vtkErrorMacro } = macro;
const { vtkWarningMacro } = macro;

// ----------------------------------------------------------------------------
// vtkPicker methods
// ----------------------------------------------------------------------------

function vtkPicker(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkPicker');

const superClass = { ...publicAPI };

function initialize() {
superClass.initialize();

model.actors = [];
model.pickedPositions = [];

model.mapperPosition[0] = 0.0;
model.mapperPosition[1] = 0.0;
model.mapperPosition[2] = 0.0;

model.mapper = null;
model.dataSet = null;

model.globalTMin = Number.MAX_VALUE;
}

/**
* Compute the tolerance in world coordinates.
* Do this by determining the world coordinates of the diagonal points of the
* window, computing the width of the window in world coordinates, and
* multiplying by the tolerance.
* @param {Number} selectionZ
* @param {Number} aspect
* @param {vtkRenderer} renderer
* @returns {Number} the computed tolerance
*/
function computeTolerance(selectionZ, aspect, renderer) {
let tolerance = 0.0;

const view = renderer.getRenderWindow().getViews()[0];
const viewport = renderer.getViewport();
const winSize = view.getSize();

let x = winSize[0] * viewport[0];
let y = winSize[1] * viewport[1];

const normalizedLeftDisplay = view.displayToNormalizedDisplay(
x,
y,
selectionZ
);
const windowLowerLeft = renderer.normalizedDisplayToWorld(
normalizedLeftDisplay[0],
normalizedLeftDisplay[1],
normalizedLeftDisplay[2],
aspect
);

x = winSize[0] * viewport[2];
y = winSize[1] * viewport[3];

const normalizedRightDisplay = view.displayToNormalizedDisplay(
x,
y,
selectionZ
);
const windowUpperRight = renderer.normalizedDisplayToWorld(
normalizedRightDisplay[0],
normalizedRightDisplay[1],
normalizedRightDisplay[2],
aspect
);

for (let i = 0; i < 3; i++) {
tolerance +=
(windowUpperRight[i] - windowLowerLeft[i]) *
(windowUpperRight[i] - windowLowerLeft[i]);
}

return Math.sqrt(tolerance);
}

/**
* Perform picking on the given renderer, given a ray defined in world coordinates.
* @param {*} renderer
* @param {*} tolerance
* @param {*} p1World
* @param {*} p2World
* @returns true if we picked something else false
*/
function pick3DInternal(renderer, tolerance, p1World, p2World) {
const p1Mapper = new Float64Array(4);
const p2Mapper = new Float64Array(4);

const ray = [];
const hitPosition = [];

const props = model.pickFromList ? model.pickList : renderer.getActors();

// pre-allocate some arrays.
const transformScale = new Float64Array(3);
const pickedPosition = new Float64Array(3);

// Loop over props.
// Transform ray (defined from position of camera to selection point) into coordinates of mapper (not
// transformed to actors coordinates! Reduces overall computation!!!).
// Note that only vtkProp3D's can be picked by vtkPicker.
props.forEach((prop) => {
const mapper = prop.getMapper();

const propIsFullyTranslucent =
prop.getProperty?.().getOpacity?.() === 0.0;

const pickable =
prop.getNestedPickable() &&
prop.getNestedVisibility() &&
!propIsFullyTranslucent;

if (!pickable) {
// prop cannot be picked
return;
}

// The prop is candidate for picking:
// - get its composite matrix and invert it
// - use the inverted matrix to transform the ray points into mapper coordinates
model.transformMatrix = prop.getMatrix().slice(0);
mat4.transpose(model.transformMatrix, model.transformMatrix);

mat4.invert(model.transformMatrix, model.transformMatrix);

vec4.transformMat4(p1Mapper, p1World, model.transformMatrix);
vec4.transformMat4(p2Mapper, p2World, model.transformMatrix);

vec3.scale(p1Mapper, p1Mapper, 1 / p1Mapper[3]);
vec3.scale(p2Mapper, p2Mapper, 1 / p2Mapper[3]);

vtkMath.subtract(p2Mapper, p1Mapper, ray);

// We now have the ray endpoints in mapper coordinates.
// Compare it with the mapper bounds to check if intersection is possible.

// Get the bounding box of the mapper.
// Note that the tolerance is added to the bounding box to make sure things on the edge of the
// bounding box are picked correctly.
const bounds = mapper
? vtkBoundingBox.inflate(mapper.getBounds(), tolerance)
: [...vtkBoundingBox.INIT_BOUNDS];

if (vtkBoundingBox.intersectBox(bounds, p1Mapper, ray, hitPosition, [])) {
mat4.getScaling(transformScale, model.transformMatrix);

const t = model.intersectWithLine(
p1Mapper,
p2Mapper,
tolerance *
0.333 *
(transformScale[0] + transformScale[1] + transformScale[2]),
prop,
mapper
);

if (t < Number.MAX_VALUE) {
pickedPosition[0] = (1.0 - t) * p1World[0] + t * p2World[0];
pickedPosition[1] = (1.0 - t) * p1World[1] + t * p2World[1];
pickedPosition[2] = (1.0 - t) * p1World[2] + t * p2World[2];

const actorIndex = model.actors.indexOf(prop);

if (actorIndex !== -1) {
// If already in list, compare the previous picked position with the new one.
// Store the new one if it is closer from the ray endpoint.
const previousPickedPosition = model.pickedPositions[actorIndex];
if (
vtkMath.distance2BetweenPoints(p1World, pickedPosition) <
vtkMath.distance2BetweenPoints(p1World, previousPickedPosition)
) {
model.pickedPositions[actorIndex] = pickedPosition.slice(0);
}
} else {
model.actors.push(prop);
model.pickedPositions.push(pickedPosition.slice(0));
}
}
}
});

// sort array by distance
const tempArray = [];
for (let i = 0; i < model.pickedPositions.length; i++) {
tempArray.push({
actor: model.actors[i],
pickedPosition: model.pickedPositions[i],
distance2: vtkMath.distance2BetweenPoints(
p1World,
model.pickedPositions[i]
),
});
}
tempArray.sort((a, b) => {
const keyA = a.distance2;
const keyB = b.distance2;
// order the actors based on the distance2 attribute, so the near actors comes
// first in the list
if (keyA < keyB) return -1;
if (keyA > keyB) return 1;
return 0;
});
model.pickedPositions = [];
model.actors = [];
tempArray.forEach((obj) => {
model.pickedPositions.push(obj.pickedPosition);
model.actors.push(obj.actor);
});
}

// Intersect data with specified ray.
// Project the center point of the mapper onto the ray and determine its parametric value
model.intersectWithLine = (p1, p2, tolerance, prop, mapper) => {
if (!mapper) {
return Number.MAX_VALUE;
}

const center = mapper.getCenter();
const ray = vec3.subtract(new Float64Array(3), p2, p1);

const rayFactor = vtkMath.dot(ray, ray);
if (rayFactor === 0.0) {
return 2.0;
}

// Project the center point onto the ray and determine its parametric value
const t =
(ray[0] * (center[0] - p1[0]) +
ray[1] * (center[1] - p1[1]) +
ray[2] * (center[2] - p1[2])) /
rayFactor;
return t;
};

// To be overridden in subclasses
publicAPI.pick = (selection, renderer) => {
if (selection.length !== 3) {
vtkWarningMacro('vtkPicker.pick - selection needs three components');
}

if (!renderer) {
vtkErrorMacro('vtkPicker.pick - renderer cannot be null');
throw new Error('renderer cannot be null');
}

initialize();

const selectionX = selection[0];
const selectionY = selection[1];
let selectionZ = selection[2];

model.renderer = renderer;
model.selectionPoint[0] = selectionX;
model.selectionPoint[1] = selectionY;
model.selectionPoint[2] = selectionZ;

const p1World = new Float64Array(4);
const p2World = new Float64Array(4);

// Get camera focal point and position. Convert to display (screen)
// coordinates. We need a depth value for z-buffer.
const camera = renderer.getActiveCamera();
const cameraPos = camera.getPosition();
const cameraFP = camera.getFocalPoint();

const view = renderer.getRenderWindow().getViews()[0];
const dims = view.getViewportSize(renderer);

if (dims[1] === 0) {
vtkWarningMacro('vtkPicker.pick - viewport area is 0');
return;
}

const aspect = dims[0] / dims[1];

let displayCoords = [];
displayCoords = renderer.worldToNormalizedDisplay(
cameraFP[0],
cameraFP[1],
cameraFP[2],
aspect
);
displayCoords = view.normalizedDisplayToDisplay(
displayCoords[0],
displayCoords[1],
displayCoords[2]
);
selectionZ = displayCoords[2];

// Convert the selection point into world coordinates.
const normalizedDisplay = view.displayToNormalizedDisplay(
selectionX,
selectionY,
selectionZ
);
const worldCoords = renderer.normalizedDisplayToWorld(
normalizedDisplay[0],
normalizedDisplay[1],
normalizedDisplay[2],
aspect
);

for (let i = 0; i < 3; i++) {
model.pickPosition[i] = worldCoords[i];
}

// Compute the ray endpoints. The ray is along the line running from
// the camera position to the selection point, starting where this line
// intersects the front clipping plane, and terminating where this
// line intersects the back clipping plane.
const ray = [];
for (let i = 0; i < 3; i++) {
ray[i] = model.pickPosition[i] - cameraPos[i];
}

const cameraDOP = [];
for (let i = 0; i < 3; i++) {
cameraDOP[i] = cameraFP[i] - cameraPos[i];
}

vtkMath.normalize(cameraDOP);

const rayLength = vtkMath.dot(cameraDOP, ray);

if (rayLength === 0.0) {
vtkWarningMacro('Picker::Pick Cannot process points');
return;
}

const clipRange = camera.getClippingRange();

let tF;
let tB;
if (camera.getParallelProjection()) {
tF = clipRange[0] - rayLength;
tB = clipRange[1] - rayLength;
for (let i = 0; i < 3; i++) {
p1World[i] = model.pickPosition[i] + tF * cameraDOP[i];
p2World[i] = model.pickPosition[i] + tB * cameraDOP[i];
}
} else {
tF = clipRange[0] / rayLength;
tB = clipRange[1] / rayLength;
for (let i = 0; i < 3; i++) {
p1World[i] = cameraPos[i] + tF * ray[i];
p2World[i] = cameraPos[i] + tB * ray[i];
}
}
p1World[3] = 1.0;
p2World[3] = 1.0;

const tolerance =
computeTolerance(selectionZ, aspect, renderer) * model.tolerance;

pick3DInternal(model.renderer, tolerance, p1World, p2World);
};

publicAPI.pick3DPoint = (selectionPoint, focalPoint, renderer) => {
if (!renderer) {
throw new Error('renderer cannot be null');
}

initialize();
model.renderer = renderer;

vec3.copy(model.selectionPoint, selectionPoint);

const view = renderer.getRenderWindow().getViews()[0];
const dims = view.getViewportSize(renderer);

if (dims[1] === 0) {
vtkWarningMacro('vtkPicker.pick3DPoint - viewport area is 0');
return;
}

const aspect = dims[0] / dims[1];

const tolerance =
computeTolerance(model.selectionPoint[2], aspect, renderer) *
model.tolerance;

pick3DInternal(renderer, tolerance, selectionPoint, focalPoint);
};
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
tolerance: 0.025,
mapperPosition: [0.0, 0.0, 0.0],
mapper: null,
dataSet: null,
actors: [],
pickedPositions: [],
transformMatrix: null,
globalTMin: Number.MAX_VALUE,
};

// ----------------------------------------------------------------------------
export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);

// Inheritance
vtkAbstractPicker.extend(publicAPI, model, initialValues);

macro.setGet(publicAPI, model, ['tolerance']);
macro.setGetArray(publicAPI, model, ['mapperPosition'], 3);
macro.get(publicAPI, model, [
'mapper',
'dataSet',
'actors',
'pickedPositions',
]);
macro.event(publicAPI, model, 'pickChange');

vtkPicker(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(extend, 'vtkPicker');

// ----------------------------------------------------------------------------

export default { newInstance, extend };