Hello,
I’m trying to adapt the vtk.js hardware selector example to work with vtkSphereMapper as below, instead of vtkGlyph3DMapper. Due to a high number of vertices (more than 300 000) in my model, vtkGlyph3DMapper with spheres runs too slowly. So I changed the code like this, but this raises an error : “Uncaught (in promise) TypeError: model.comntext is undefined”
I’m quite stuck here.
Thanks in advance for your help.
Best regards
/* eslint-disable import/prefer-default-export */
/* eslint-disable import/no-extraneous-dependencies */
import ‘@kitware/vtk.js/favicon’;
// Load the rendering pieces we want to use (for both WebGL and WebGPU)
import ‘@kitware/vtk.js/Rendering/Profiles/Geometry’;
import ‘@kitware/vtk.js/Rendering/Profiles/Molecule’; // necessary for Sphere Mapper
import { throttle } from ‘@kitware/vtk.js/macros’;
import vtkActor from ‘@kitware/vtk.js/Rendering/Core/Actor’;
import vtkCylinderSource from ‘@kitware/vtk.js/Filters/Sources/CylinderSource’;
import vtkDataArray from ‘@kitware/vtk.js/Common/Core/DataArray’;
import vtkFullScreenRenderWindow from ‘@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow’;
import vtkMapper from ‘@kitware/vtk.js/Rendering/Core/Mapper’;
import vtkSphereSource from ‘@kitware/vtk.js/Filters/Sources/SphereSource’;
import vtkMatrixBuilder from ‘@kitware/vtk.js/Common/Core/MatrixBuilder’;
import { mat4 } from ‘gl-matrix’;
import vtkMath from ‘@kitware/vtk.js/Common/Core/Math’;
import { FieldAssociations } from ‘@kitware/vtk.js/Common/DataModel/DataSet/Constants’;
import vtkSphereMapper from ‘@kitware/vtk.js/Rendering/Core/SphereMapper’;
import vtkPolyData from ‘@kitware/vtk.js/Common/DataModel/PolyData’;
// Cylinder -----------------------------------------------
const cylinderSource = vtkCylinderSource.newInstance({
resolution: 10,
radius: 0.4,
height: 0.6,
direction: [1.0, 0.0, 0.0],
});
const cylinderMapper = vtkSphereMapper.newInstance();
const polydata = vtkPolyData.newInstance();
polydata.setPoints(cylinderSource.getOutputData().getPoints());
const gcns_vertices = new Uint32Array(polydata.getNumberOfPoints() + 1);
gcns_vertices[0] = polydata.getNumberOfPoints();
for (let i = 0; i < polydata.getNumberOfPoints(); i++) {
gcns_vertices[i+1] = i;
}
polydata.getVerts().setData(gcns_vertices);
// Add fields to cylinderPointSet
const scalarArray = new Float32Array(polydata.getNumberOfPoints());
scalarArray.fill(0.1);
const scalars = vtkDataArray.newInstance({
name: ‘scalars’,
values: scalarArray,
numberOfComponents: 1
});
polydata.getPointData().setScalars(scalars);
cylinderMapper.setInputData(polydata);
cylinderMapper.setScaleArray(‘scalarArray’);
cylinderMapper.setScaleFactor(1);
const cylinderActor = vtkActor.newInstance({ position: [0, 1, 0] });
cylinderActor.setMapper(cylinderMapper);
// ----------------------------------------------------------------------------
// Create Picking pointer
// ----------------------------------------------------------------------------
const pointerSource = vtkSphereSource.newInstance({
phiResolution: 15,
thetaResolution: 15,
radius: 0.01,
});
const pointerMapper = vtkMapper.newInstance();
const pointerActor = vtkActor.newInstance();
pointerActor.setMapper(pointerMapper);
pointerMapper.setInputConnection(pointerSource.getOutputPort());
// ----------------------------------------------------------------------------
// Create rendering infrastructure
// ----------------------------------------------------------------------------
const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({
background: [0, 0, 0],
});
const renderer = fullScreenRenderer.getRenderer();
const renderWindow = renderer.getRenderWindow();
const interactor = renderWindow.getInteractor();
const apiSpecificRenderWindow = interactor.getView();
renderer.addActor(cylinderActor);
renderer.addActor(pointerActor);
renderer.resetCamera();
renderWindow.render();
// ----------------------------------------------------------------------------
// Create hardware selector
// ----------------------------------------------------------------------------
const hardwareSelector = apiSpecificRenderWindow.getSelector();
hardwareSelector.setCaptureZValues(true);
// TODO: bug in FIELD_ASSOCIATION_POINTS mode
// hardwareSelector.setFieldAssociation(
// FieldAssociations.FIELD_ASSOCIATION_POINTS
// );
hardwareSelector.setFieldAssociation(FieldAssociations.FIELD_ASSOCIATION_CELLS);
// ----------------------------------------------------------------------------
// Create Mouse listener for picking on mouse move
// ----------------------------------------------------------------------------
function eventToWindowXY(event) {
// We know we are full screen => window.innerXXX
// Otherwise we can use pixel device ratio or else…
const { clientX, clientY } = event;
const [width, height] = apiSpecificRenderWindow.getSize();
const x = Math.round((width * clientX) / window.innerWidth);
const y = Math.round(height * (1 - clientY / window.innerHeight)); // Need to flip Y
return [x, y];
}
// ----------------------------------------------------------------------------
let needGlyphCleanup = false;
let lastProcessedActor = null;
const updateCursor = (worldPosition) => {
if (lastProcessedActor) {
pointerActor.setVisibility(true);
pointerActor.setPosition(worldPosition);
} else {
pointerActor.setVisibility(false);
}
renderWindow.render();
};
function processSelections(selections) {
renderer.getActors().forEach((a) => a.getProperty().setColor(1,0,0));
if (!selections || selections.length === 0) {
lastProcessedActor = null;
updateCursor();
return;
}
const {
worldPosition: rayHitWorldPosition,
compositeID,
prop,
propID,
attributeID,
} = selections[0].getProperties();
let closestCellPointWorldPosition = […rayHitWorldPosition];
if (attributeID || attributeID === 0) {
const input = prop.getMapper().getInputData();
if (!input.getCells()) {
input.buildCells();
}
// Get matrices to convert coordinates: (prop coordinates) <-> (world coordinates)
const glTempMat = mat4.fromValues(...prop.getMatrix());
mat4.transpose(glTempMat, glTempMat);
const propToWorld = vtkMatrixBuilder.buildFromDegree().setMatrix(glTempMat);
mat4.invert(glTempMat, glTempMat);
const worldToProp = vtkMatrixBuilder.buildFromDegree().setMatrix(glTempMat);
// Compute the position of the cursor in prop coordinates
const propPosition = [...rayHitWorldPosition];
worldToProp.apply(propPosition);
if (
hardwareSelector.getFieldAssociation() ===
FieldAssociations.FIELD_ASSOCIATION_POINTS
) {
// Selecting points
closestCellPointWorldPosition = [
...input.getPoints().getTuple(attributeID),
];
propToWorld.apply(closestCellPointWorldPosition);
} else {
// Selecting cells
const cellPoints = input.getCellPoints(attributeID);
if (cellPoints) {
const pointIds = cellPoints.cellPointIds;
// Find the closest cell point, and use that as cursor position
const points = Array.from(pointIds).map((pointId) =>
input.getPoints().getPoint(pointId)
);
const distance = (pA, pB) =>
vtkMath.distance2BetweenPoints(pA, propPosition) -
vtkMath.distance2BetweenPoints(pB, propPosition);
const sorted = points.sort(distance);
closestCellPointWorldPosition = [...sorted[0]];
propToWorld.apply(closestCellPointWorldPosition);
}
}
}
lastProcessedActor = prop;
// Use closestCellPointWorldPosition or rayHitWorldPosition
updateCursor(closestCellPointWorldPosition);
// Make the picked actor green
prop.getProperty().setColor(0,1,0);
// We hit the glyph, let’s scale the picked glyph
if (prop === cylinderActor) {
scaleArray.fill(0.5);
scaleArray[compositeID] = 0.7;
cylinderPointSet.modified();
needGlyphCleanup = true;
} else if (needGlyphCleanup) {
needGlyphCleanup = false;
scaleArray.fill(0.5);
cylinderPointSet.modified();
}
renderWindow.render();
}
// ----------------------------------------------------------------------------
function pickOnMouseEvent(event) {
if (interactor.isAnimating()) {
// We should not do picking when interacting with the scene
return;
}
const [x, y] = eventToWindowXY(event);
pointerActor.setVisibility(false);
hardwareSelector.getSourceDataAsync(renderer, x, y, x, y).then((result) => {
if (result) {
processSelections(result.generateSelection(x, y, x, y));
} else {
processSelections(null);
}
});
}
const throttleMouseHandler = throttle(pickOnMouseEvent, 20);
document.addEventListener(‘mousemove’, throttleMouseHandler);