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.

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 a pointer to 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

getPickedPositions

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

getTolerance

Get tolerance for performing pick operation.

intersectWithLine

Intersect data with specified ray.
Project the center point of the mapper onto the ray and determine its parametric value

Argument Type Required Description
p1 Vector3 Yes
p2 Vector3 Yes
tol Number Yes
mapper vtkMapper 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

pick

Perform pick operation with selection point provided.

Argument Type Required Description
selection Yes
renderer vtkRenderer Yes The vtkRenderer instance.

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.

Argument Type Required Description
tolerance Number Yes The tolerance value.

Source

index.d.ts
import { Vector3 } from "../../../types";
import vtkAbstractPicker, { IAbstractPickerInitialValues } from "../AbstractPicker";
import vtkActor from "../Actor";
import vtkMapper from '../Mapper';
import vtkRenderer from '../Renderer';


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

export interface vtkPicker extends vtkAbstractPicker {

/**
* Get a collection of all the actors that were intersected.
*/
getActors(): vtkActor[];

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

/**
* Get mapper that was picked (if any)
*/
getMapper(): null | vtkMapper;

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

/**
*
*/
getMapperPositionByReference(): number[];

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

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

/**
* FIXME: this needs to be check again
*/
//invokePickChange(pickedPositions: number[]): any;

/**
* FIXME: this needs to be check again
*/
//onPickChange(pickedPositions: number[]): any;

/**
* Intersect data with specified ray.
* Project the center point of the mapper onto the ray and determine its parametric value
* @param {Vector3} p1
* @param {Vector3} p2
* @param {Number} tol
* @param {vtkMapper} mapper
*/
intersectWithLine(p1: Vector3, p2: Vector3, tol: number, mapper: vtkMapper): number;

/**
* Perform pick operation with selection point provided.
* @param selection
* @param {vtkRenderer} renderer The vtkRenderer instance.
*/
pick(selection: any, renderer: vtkRenderer): any;

/**
* 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.
* @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.
*/
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, 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;
}

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

const center = mapper.getCenter();

const ray = [];
for (let i = 0; i < 3; i++) {
ray[i] = p2[i] - p1[i];
}

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: selectionPt needs three components');
}
const selectionX = selection[0];
const selectionY = selection[1];
let selectionZ = selection[2];
let cameraPos = [];
let cameraFP = [];
let displayCoords = [];
let worldCoords = [];
const ray = [];
const cameraDOP = [];
let clipRange = [];
let tF;
let tB;
const p1World = [];
const p2World = [];
let viewport = [];
let winSize = [];
let x;
let y;
let windowLowerLeft = [];
let windowUpperRight = [];
let tol = 0.0;
let props = [];
let pickable = false;
const p1Mapper = new Float64Array(4);
const p2Mapper = new Float64Array(4);
const bbox = vtkBoundingBox.newInstance();
const t = [];
const hitPosition = [];
const view = renderer.getRenderWindow().getViews()[0];

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

if (!renderer) {
vtkErrorMacro('Picker::Pick Must specify renderer');
return;
}

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

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
);
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.
for (let i = 0; i < 3; i++) {
ray[i] = model.pickPosition[i] - cameraPos[i];
}
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;
}

clipRange = camera.getClippingRange();

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;

// 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.
viewport = renderer.getViewport();
if (renderer.getRenderWindow()) {
winSize = renderer.getRenderWindow().getViews()[0].getSize();
}
x = winSize[0] * viewport[0];
y = winSize[1] * viewport[1];
const normalizedLeftDisplay = view.displayToNormalizedDisplay(
x,
y,
selectionZ
);
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
);
windowUpperRight = renderer.normalizedDisplayToWorld(
normalizedRightDisplay[0],
normalizedRightDisplay[1],
normalizedRightDisplay[2],
aspect
);

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

tol = Math.sqrt(tol) * model.tolerance;
if (model.pickFromList) {
props = model.pickList;
} else {
props = renderer.getActors();
}
const scale = [];
props.forEach((prop) => {
const mapper = prop.getMapper();
pickable = prop.getNestedPickable() && prop.getNestedVisibility();

if (prop.getProperty().getOpacity?.() <= 0.0) {
pickable = false;
}

if (pickable) {
model.transformMatrix = prop.getMatrix().slice(0);
// Webgl need a transpose matrix but we need the untransposed one to project world points
// into the right referential
mat4.transpose(model.transformMatrix, model.transformMatrix);
mat4.invert(model.transformMatrix, model.transformMatrix);
// Extract scale
const col1 = [
model.transformMatrix[0],
model.transformMatrix[1],
model.transformMatrix[2],
];
const col2 = [
model.transformMatrix[4],
model.transformMatrix[5],
model.transformMatrix[6],
];
const col3 = [
model.transformMatrix[8],
model.transformMatrix[9],
model.transformMatrix[10],
];
scale[0] = vtkMath.norm(col1);
scale[1] = vtkMath.norm(col2);
scale[2] = vtkMath.norm(col3);

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

p1Mapper[0] /= p1Mapper[3];
p1Mapper[1] /= p1Mapper[3];
p1Mapper[2] /= p1Mapper[3];

p2Mapper[0] /= p2Mapper[3];
p2Mapper[1] /= p2Mapper[3];
p2Mapper[2] /= p2Mapper[3];

for (let i = 0; i < 3; i++) {
ray[i] = p2Mapper[i] - p1Mapper[i];
}

if (mapper) {
bbox.setBounds(mapper.getBounds());
bbox.inflate(tol);
} else {
bbox.reset();
}

if (bbox.intersectBox(p1Mapper, ray, hitPosition, t)) {
t[0] = publicAPI.intersectWithLine(
p1Mapper,
p2Mapper,
tol * 0.333 * (scale[0] + scale[1] + scale[2]),
prop,
mapper
);
if (t[0] < Number.MAX_VALUE) {
const p = [];
p[0] = (1.0 - t[0]) * p1World[0] + t[0] * p2World[0];
p[1] = (1.0 - t[0]) * p1World[1] + t[0] * p2World[1];
p[2] = (1.0 - t[0]) * p1World[2] + t[0] * p2World[2];

// Check if the current actor is already in the list
let actorID = -1;
for (let i = 0; i < model.actors.length; i++) {
if (model.actors[i] === prop) {
actorID = i;
break;
}
}
if (actorID === -1) {
model.actors.push(prop);
model.pickedPositions.push(p);
} else {
const oldPoint = model.pickedPositions[actorID];
const distOld = vtkMath.distance2BetweenPoints(p1World, oldPoint);
const distCurrent = vtkMath.distance2BetweenPoints(p1World, p);
if (distCurrent < distOld) {
model.pickedPositions[actorID] = p;
}
}
}
}
}
publicAPI.invokePickChange(model.pickedPositions);
return 1;
});
// 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);
});
};
}

// ----------------------------------------------------------------------------
// 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 };