Skip to content

Commit

Permalink
Merge pull request Kitware#2369 from boucaud/add-point-cell-support-t…
Browse files Browse the repository at this point in the history
…o-hardware-selector

feat(hardwareselector): add hardware picking for point and cell ids
  • Loading branch information
floryst authored May 21, 2022
2 parents e0df8f0 + 5b43bd7 commit 77ee125
Show file tree
Hide file tree
Showing 11 changed files with 608 additions and 110 deletions.
6 changes: 6 additions & 0 deletions Sources/Rendering/Core/Actor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ function vtkActor(publicAPI, model) {

publicAPI.getSupportsSelection = () =>
model.mapper ? model.mapper.getSupportsSelection() : false;

publicAPI.processSelectorPixelBuffers = (selector, pixelOffsets) => {
if (model.mapper && model.mapper.processSelectorPixelBuffers) {
model.mapper.processSelectorPixelBuffers(selector, pixelOffsets);
}
};
}

// ----------------------------------------------------------------------------
Expand Down
44 changes: 36 additions & 8 deletions Sources/Rendering/Core/HardwareSelector/example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import vtkFullScreenRenderWindow from 'vtk.js/Sources/Rendering/Misc/FullScreenR
import vtkGlyph3DMapper from 'vtk.js/Sources/Rendering/Core/Glyph3DMapper';
import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper';
import vtkSphereSource from 'vtk.js/Sources/Filters/Sources/SphereSource';

import vtkMath from 'vtk.js/Sources/Common/Core/Math';
import { FieldAssociations } from 'vtk.js/Sources/Common/DataModel/DataSet/Constants';
import { Representation } from 'vtk.js/Sources/Rendering/Core/Property/Constants';

Expand Down Expand Up @@ -56,6 +56,7 @@ const sphereSource = vtkSphereSource.newInstance({
phiResolution: 30,
thetaResolution: 30,
});

const sphereMapper = vtkMapper.newInstance();
const sphereActor = vtkActor.newInstance();
sphereActor.setMapper(sphereMapper);
Expand Down Expand Up @@ -155,9 +156,7 @@ renderWindow.render();

const hardwareSelector = apiSpecificRenderWindow.getSelector();
hardwareSelector.setCaptureZValues(true);
hardwareSelector.setFieldAssociation(
FieldAssociations.FIELD_ASSOCIATION_POINTS
);
hardwareSelector.setFieldAssociation(FieldAssociations.FIELD_ASSOCIATION_CELLS);

// ----------------------------------------------------------------------------
// Create Mouse listener for picking on mouse move
Expand Down Expand Up @@ -199,13 +198,42 @@ function processSelections(selections) {
return;
}

const { worldPosition, compositeID, prop } = selections[0].getProperties();

if (lastProcessedActor === prop) {
const { worldPosition, compositeID, prop, attributeID } =
selections[0].getProperties();
let cursorPosition = [...worldPosition];
if (attributeID || attributeID === 0) {
const input = prop.getMapper().getInputData();
if (!input.getCells()) {
input.buildCells();
}
if (
hardwareSelector.getFieldAssociation() ===
FieldAssociations.FIELD_ASSOCIATION_POINTS
) {
const points = input.getPoints();
const point = points.getTuple(attributeID);
cursorPosition = [...point];
} else {
const cellPoints = input.getCellPoints(attributeID);
if (cellPoints) {
const pointIds = cellPoints.cellPointIds;
const points = [];
// Find the closest cell point, and use that as cursor position
pointIds.forEach((pointId) => {
points.push(input.getPoints().getPoint(pointId, []));
});
const distance = (pA, pB) =>
vtkMath.distance2BetweenPoints(pA, worldPosition) -
vtkMath.distance2BetweenPoints(pB, worldPosition);
const sorted = points.sort(distance);
cursorPosition = [...sorted[0]];
}
}
} else if (lastProcessedActor === prop) {
// Skip render call when nothing change
updateWorldPosition(worldPosition);
return;
}
updateWorldPosition(cursorPosition);
lastProcessedActor = prop;

// Make the picked actor green
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,62 @@ test('Test HardwareSelector', (tapeContext) => {
sel.setFieldAssociation(FieldAssociations.FIELD_ASSOCIATION_POINTS);
sel.setCaptureZValues(true);

sel.selectAsync(renderer, 200, 200, 300, 300).then((res) => {
// console.log(JSON.stringify(res[0].getProperties()));
// console.log(JSON.stringify(res[1].getProperties()));
// res[1] should be at 1.0, 1.0, 1.5 in world coords
const allGood =
res.length === 2 &&
res[0].getProperties().prop === actor &&
res[1].getProperties().prop === actor3 &&
Math.abs(res[1].getProperties().worldPosition[0] - 1.0) < 0.02 &&
Math.abs(res[1].getProperties().worldPosition[1] - 1.0) < 0.02 &&
Math.abs(res[1].getProperties().worldPosition[2] - 1.5) < 0.02;

tapeContext.ok(res.length === 2, 'Two props selected');
tapeContext.ok(allGood, 'Correct props were selected');
const promises = [];
promises.push(
sel.selectAsync(renderer, 200, 200, 300, 300).then((res) => {
// res[1] should be at 1.0, 1.0, 1.5 in world coords
const allGood =
res.length === 2 &&
res[0].getProperties().prop === actor &&
res[1].getProperties().prop === actor3 &&
Math.abs(res[1].getProperties().worldPosition[0] - 1.0) < 0.02 &&
Math.abs(res[1].getProperties().worldPosition[1] - 1.0) < 0.02 &&
Math.abs(res[1].getProperties().worldPosition[2] - 1.5) < 0.02;

tapeContext.ok(res.length === 2, 'Two props selected');
tapeContext.ok(allGood, 'Correct props were selected');
})
);

// Test picking points
promises.push(
sel.selectAsync(renderer, 210, 199, 211, 200).then((res) => {
tapeContext.ok(res[0].getProperties().propID === 3);
tapeContext.ok(res[0].getProperties().attributeID === 33);
})
);

promises.push(
sel.selectAsync(renderer, 145, 140, 146, 141).then((res) => {
tapeContext.ok(res[0].getProperties().propID === 4);
tapeContext.ok(res[0].getProperties().attributeID === 0);
})
);

promises.push(
sel.selectAsync(renderer, 294, 264, 295, 265).then((res) => {
tapeContext.ok(res[0].getProperties().propID === 5);
tapeContext.ok(res[0].getProperties().attributeID === 0);
})
);

sel.setFieldAssociation(FieldAssociations.FIELD_ASSOCIATION_CELLS);

// Test picking cells
promises.push(
sel.selectAsync(renderer, 200, 200, 201, 201).then((res) => {
tapeContext.ok(res[0].getProperties().propID === 3);
tapeContext.ok(res[0].getProperties().attributeID === 27);
})
);

promises.push(
sel.selectAsync(renderer, 265, 265, 266, 266).then((res) => {
tapeContext.ok(res[0].getProperties().propID === 5);
tapeContext.ok(res[0].getProperties().attributeID === 0);
})
);
Promise.all(promises).then(() => {
gc.releaseResources();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ test('Test HardwareSelector', (tapeContext) => {
console.timeEnd('hardware render');

tapeContext.ok(
// should take about 3 normal renders but we give it some wiggle room
tcTime < tbTime * 6,
`Hardware selector takes less than six normal renders (${taTime}, ${tbTime}, ${tcTime})`
// should take about 5 normal renders but we give it some wiggle room
tcTime < tbTime * 10,
`Hardware selector takes less than ten normal renders (${taTime}, ${tbTime}, ${tcTime})`
);

gc.releaseResources();
Expand Down
73 changes: 73 additions & 0 deletions Sources/Rendering/Core/Mapper/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import vtkScalarsToColors from 'vtk.js/Sources/Common/Core/ScalarsToColors/Const
import CoincidentTopologyHelper from 'vtk.js/Sources/Rendering/Core/Mapper/CoincidentTopologyHelper';
import Constants from 'vtk.js/Sources/Rendering/Core/Mapper/Constants';

import vtkDataSet from 'vtk.js/Sources/Common/DataModel/DataSet';

import { PassTypes } from 'vtk.js/Sources/Rendering/OpenGL/HardwareSelector/Constants';

const { FieldAssociations } = vtkDataSet;

const { staticOffsetAPI, otherStaticMethods } = CoincidentTopologyHelper;

const { ColorMode, ScalarMode, GetArray } = Constants;
Expand Down Expand Up @@ -493,6 +499,68 @@ function vtkMapper(publicAPI, model) {
publicAPI.colorToValue = notImplemented('ColorToValue');
publicAPI.useInvertibleColorFor = notImplemented('UseInvertibleColorFor');
publicAPI.clearInvertibleColor = notImplemented('ClearInvertibleColor');

publicAPI.processSelectorPixelBuffers = (selector, pixelOffsets) => {
/* eslint-disable no-bitwise */
if (
!selector ||
!model.selectionWebGLIdsToVTKIds ||
!model.populateSelectionSettings
) {
return;
}

const rawLowData = selector.getRawPixelBuffer(PassTypes.ID_LOW24);
const rawHighData = selector.getRawPixelBuffer(PassTypes.ID_HIGH24);
const currentPass = selector.getCurrentPass();
const fieldAssociation = selector.getFieldAssociation();

let idMap = null;
if (fieldAssociation === FieldAssociations.FIELD_ASSOCIATION_POINTS) {
idMap = model.selectionWebGLIdsToVTKIds.points;
} else if (fieldAssociation === FieldAssociations.FIELD_ASSOCIATION_CELLS) {
idMap = model.selectionWebGLIdsToVTKIds.cells;
}

if (!idMap) {
return;
}

pixelOffsets.forEach((pos) => {
if (currentPass === PassTypes.ID_LOW24) {
let inValue = 0;
if (rawHighData) {
inValue += rawHighData[pos];
inValue *= 256;
}
inValue += rawLowData[pos + 2];
inValue *= 256;
inValue += rawLowData[pos + 1];
inValue *= 256;
inValue += rawLowData[pos];

const outValue = idMap[inValue];
const lowData = selector.getPixelBuffer(PassTypes.ID_LOW24);
lowData[pos] = outValue & 0xff;
lowData[pos + 1] = (outValue & 0xff00) >> 8;
lowData[pos + 2] = (outValue & 0xff0000) >> 16;
} else if (currentPass === PassTypes.ID_HIGH24 && rawHighData) {
let inValue = 0;
inValue += rawHighData[pos];
inValue *= 256;
inValue += rawLowData[pos];
inValue *= 256;
inValue += rawLowData[pos + 1];
inValue *= 256;
inValue += rawLowData[pos + 2];

const outValue = idMap[inValue];
const highData = selector.getPixelBuffer(PassTypes.ID_HIGH24);
highData[pos] = (outValue & 0xff000000) >> 24;
}
});
/* eslint-enable no-bitwise */
};
}

// ----------------------------------------------------------------------------
Expand All @@ -519,6 +587,9 @@ const DEFAULT_VALUES = {

fieldDataTupleId: -1,

populateSelectionSettings: true,
selectionWebGLIdsToVTKIds: null,

interpolateScalarsBeforeMapping: false,
colorCoordinates: null,
colorTextureMap: null,
Expand Down Expand Up @@ -553,9 +624,11 @@ export function extend(publicAPI, model, initialValues = {}) {
'fieldDataTupleId',
'interpolateScalarsBeforeMapping',
'lookupTable',
'populateSelectionSettings',
'renderTime',
'scalarMode',
'scalarVisibility',
'selectionWebGLIdsToVTKIds',
'static',
'useLookupTableScalarRange',
'viewSpecificProperties',
Expand Down
2 changes: 2 additions & 0 deletions Sources/Rendering/Core/Prop/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ function vtkProp(publicAPI, model) {
return m1;
};

publicAPI.processSelectorPixelBuffers = (selector, pixeloffsets) => {};

publicAPI.getNestedProps = () => null;
publicAPI.getActors = () => [];
publicAPI.getActors2D = () => [];
Expand Down
35 changes: 34 additions & 1 deletion Sources/Rendering/OpenGL/CellArrayBufferObject/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ function vtkOpenGLCellArrayBufferObject(publicAPI, model) {

publicAPI.setType(ObjectType.ARRAY_BUFFER);

publicAPI.createVBO = (cellArray, inRep, outRep, options) => {
publicAPI.createVBO = (
cellArray,
inRep,
outRep,
options,
selectionMaps = null
) => {
if (!cellArray.getData() || !cellArray.getData().length) {
model.elementCount = 0;
return 0;
Expand Down Expand Up @@ -280,7 +286,34 @@ function vtkOpenGLCellArrayBufferObject(publicAPI, model) {
publicAPI.setCoordShiftAndScale(null, null);
}

// Initialize the structures used to keep track of point ids and cell ids for selectors
if (selectionMaps) {
if (!selectionMaps.points && !selectionMaps.cells) {
selectionMaps.points = new Int32Array(caboCount);
selectionMaps.cells = new Int32Array(caboCount);
} else {
const newPoints = new Int32Array(
caboCount + selectionMaps.points.length
);
newPoints.set(selectionMaps.points);
selectionMaps.points = newPoints;
const newCells = new Int32Array(
caboCount + selectionMaps.points.length
);
newCells.set(selectionMaps.cells);
selectionMaps.cells = newCells;
}
}

let pointCount = options.vertexOffset;
addAPoint = function addAPointFunc(i) {
// Keep track of original point and cell ids, for selection
if (selectionMaps) {
selectionMaps.points[pointCount] = i;
selectionMaps.cells[pointCount] = cellCount;
}
++pointCount;

// Vertices
pointIdx = i * 3;

Expand Down
3 changes: 2 additions & 1 deletion Sources/Rendering/OpenGL/HardwareSelector/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ export const PassTypes = {
ACTOR_PASS: 0,
COMPOSITE_INDEX_PASS: 1,
ID_LOW24: 2,
MAX_KNOWN_PASS: 2,
ID_HIGH24: 3,
MAX_KNOWN_PASS: 3,
};

export default {
Expand Down
Loading

0 comments on commit 77ee125

Please sign in to comment.