import macro from 'vtk.js/Sources/macros'; import vtkHardwareSelector from 'vtk.js/Sources/Rendering/Core/HardwareSelector'; import vtkWebGPUBuffer from 'vtk.js/Sources/Rendering/WebGPU/Buffer'; import vtkWebGPUHardwareSelectionPass from 'vtk.js/Sources/Rendering/WebGPU/HardwareSelectionPass'; import vtkSelectionNode from 'vtk.js/Sources/Common/DataModel/SelectionNode'; import vtkDataSet from 'vtk.js/Sources/Common/DataModel/DataSet';
const { SelectionContent, SelectionField } = vtkSelectionNode; const { FieldAssociations } = vtkDataSet; const { vtkErrorMacro } = macro;
function getInfoHash(info) { return `${info.propID} ${info.compositeID}`; }
function convert(xx, yy, buffdata, channel) { const offset = ((buffdata.height - yy - 1) * buffdata.colorBufferWidth + xx) * 4 + channel; return buffdata.colorValues[offset]; }
function getPixelInformationWithData( buffdata, inDisplayPosition, maxDistance, outSelectedPosition ) { const maxDist = maxDistance < 0 ? 0 : maxDistance; if (maxDist === 0) { outSelectedPosition[0] = inDisplayPosition[0]; outSelectedPosition[1] = inDisplayPosition[1]; if ( inDisplayPosition[0] < 0 || inDisplayPosition[0] >= buffdata.width || inDisplayPosition[1] < 0 || inDisplayPosition[1] >= buffdata.height ) { return null; }
const actorid = convert( inDisplayPosition[0], inDisplayPosition[1], buffdata, 0 );
if (actorid <= 0) { return null; }
const info = {};
info.propID = actorid;
let compositeID = convert( inDisplayPosition[0], inDisplayPosition[1], buffdata, 1 ); if (compositeID < 0 || compositeID > 0xffffff) { compositeID = 0; } info.compositeID = compositeID;
if (buffdata.captureZValues) { const offset = (buffdata.height - inDisplayPosition[1] - 1) * buffdata.zbufferBufferWidth + inDisplayPosition[0]; info.zValue = buffdata.depthValues[offset]; info.zValue = buffdata.webGPURenderer.convertToOpenGLDepth(info.zValue); info.displayPosition = inDisplayPosition; } return info; }
const dispPos = [inDisplayPosition[0], inDisplayPosition[1]]; const curPos = [0, 0]; let info = getPixelInformationWithData( buffdata, inDisplayPosition, 0, outSelectedPosition ); if (info) { return info; } for (let dist = 1; dist < maxDist; ++dist) { for ( let y = dispPos[1] > dist ? dispPos[1] - dist : 0; y <= dispPos[1] + dist; ++y ) { curPos[1] = y; if (dispPos[0] >= dist) { curPos[0] = dispPos[0] - dist; info = getPixelInformationWithData( buffdata, curPos, 0, outSelectedPosition ); if (info) { return info; } } curPos[0] = dispPos[0] + dist; info = getPixelInformationWithData( buffdata, curPos, 0, outSelectedPosition ); if (info) { return info; } } for ( let x = dispPos[0] >= dist ? dispPos[0] - (dist - 1) : 0; x <= dispPos[0] + (dist - 1); ++x ) { curPos[0] = x; if (dispPos[1] >= dist) { curPos[1] = dispPos[1] - dist; info = getPixelInformationWithData( buffdata, curPos, 0, outSelectedPosition ); if (info) { return info; } } curPos[1] = dispPos[1] + dist; info = getPixelInformationWithData( buffdata, curPos, 0, outSelectedPosition ); if (info) { return info; } } }
outSelectedPosition[0] = inDisplayPosition[0]; outSelectedPosition[1] = inDisplayPosition[1]; return null; }
function convertSelection(fieldassociation, dataMap, buffdata) { const sel = [];
let count = 0; dataMap.forEach((value, key) => { const child = vtkSelectionNode.newInstance(); child.setContentType(SelectionContent.INDICES); switch (fieldassociation) { case FieldAssociations.FIELD_ASSOCIATION_CELLS: child.setFieldType(SelectionField.CELL); break; case FieldAssociations.FIELD_ASSOCIATION_POINTS: child.setFieldType(SelectionField.POINT); break; default: vtkErrorMacro('Unknown field association'); } child.getProperties().propID = value.info.propID; const wprop = buffdata.webGPURenderer.getPropFromID(value.info.propID); child.getProperties().prop = wprop.getRenderable(); child.getProperties().compositeID = value.info.compositeID; child.getProperties().pixelCount = value.pixelCount; if (buffdata.captureZValues) { child.getProperties().displayPosition = [ value.info.displayPosition[0], value.info.displayPosition[1], value.info.zValue, ]; child.getProperties().worldPosition = buffdata.webGPURenderWindow.displayToWorld( value.info.displayPosition[0], value.info.displayPosition[1], value.info.zValue, buffdata.renderer ); }
child.setSelectionList(value.attributeIDs); sel[count] = child; count++; });
return sel; }
function generateSelectionWithData(buffdata, fx1, fy1, fx2, fy2) { const x1 = Math.floor(fx1); const y1 = Math.floor(fy1); const x2 = Math.floor(fx2); const y2 = Math.floor(fy2);
const dataMap = new Map();
const outSelectedPosition = [0, 0];
for (let yy = y1; yy <= y2; yy++) { for (let xx = x1; xx <= x2; xx++) { const pos = [xx, yy]; const info = getPixelInformationWithData( buffdata, pos, 0, outSelectedPosition ); if (info) { const hash = getInfoHash(info); if (!dataMap.has(hash)) { dataMap.set(hash, { info, pixelCount: 1, attributeIDs: [info.attributeID], }); } else { const dmv = dataMap.get(hash); dmv.pixelCount++; if (buffdata.captureZValues) { if (info.zValue < dmv.info.zValue) { dmv.info = info; } } if (dmv.attributeIDs.indexOf(info.attributeID) === -1) { dmv.attributeIDs.push(info.attributeID); } } } } } return convertSelection(buffdata.fieldAssociation, dataMap, buffdata); }
function vtkWebGPUHardwareSelector(publicAPI, model) { model.classHierarchy.push('vtkWebGPUHardwareSelector');
publicAPI.endSelection = () => { model.WebGPURenderer.setSelector(null); };
publicAPI.getSourceDataAsync = async (renderer) => { if (!renderer || !model._WebGPURenderWindow) { vtkErrorMacro('Renderer and view must be set before calling Select.'); return false; }
model._WebGPURenderWindow.getRenderable().preRender();
if (!model._WebGPURenderWindow.getInitialized()) { model._WebGPURenderWindow.initialize(); await new Promise((resolve) => { model._WebGPURenderWindow.onInitialized(resolve); }); }
const webGPURenderer = model._WebGPURenderWindow.getViewNodeFor(renderer);
if (!webGPURenderer) { return false; }
const originalSuppress = webGPURenderer.getSuppressClear(); webGPURenderer.setSuppressClear(true);
model._selectionPass.traverse(model._WebGPURenderWindow, webGPURenderer);
webGPURenderer.setSuppressClear(originalSuppress);
const device = model._WebGPURenderWindow.getDevice(); const texture = model._selectionPass.getColorTexture(); const depthTexture = model._selectionPass.getDepthTexture();
const result = { area: [0, 0, texture.getWidth() - 1, texture.getHeight() - 1], captureZValues: model.captureZValues, fieldAssociation: model.fieldAssociation, renderer, webGPURenderer, webGPURenderWindow: model._WebGPURenderWindow, width: texture.getWidth(), height: texture.getHeight(), };
result.colorBufferWidth = 16 * Math.floor((result.width + 15) / 16); result.colorBufferSizeInBytes = result.colorBufferWidth * result.height * 4 * 4; const colorBuffer = vtkWebGPUBuffer.newInstance({ label: 'hardwareSelectColorBuffer', }); colorBuffer.setDevice(device); colorBuffer.create( result.colorBufferSizeInBytes, GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST );
const cmdEnc = model._WebGPURenderWindow.getCommandEncoder(); cmdEnc.copyTextureToBuffer( { texture: texture.getHandle(), }, { buffer: colorBuffer.getHandle(), bytesPerRow: 16 * result.colorBufferWidth, rowsPerImage: result.height, }, { width: result.width, height: result.height, depthOrArrayLayers: 1, } );
let zbuffer; if (model.captureZValues) { result.zbufferBufferWidth = 64 * Math.floor((result.width + 63) / 64); zbuffer = vtkWebGPUBuffer.newInstance({ label: 'hardwareSelectDepthBuffer', }); zbuffer.setDevice(device); result.zbufferSizeInBytes = result.height * result.zbufferBufferWidth * 4; zbuffer.create( result.zbufferSizeInBytes, GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST );
cmdEnc.copyTextureToBuffer( { texture: depthTexture.getHandle(), aspect: 'depth-only', }, { buffer: zbuffer.getHandle(), bytesPerRow: 4 * result.zbufferBufferWidth, rowsPerImage: result.height, }, { width: result.width, height: result.height, depthOrArrayLayers: 1, } ); } device.submitCommandEncoder(cmdEnc);
const cLoad = colorBuffer.mapAsync(GPUMapMode.READ); if (model.captureZValues) { const zLoad = zbuffer.mapAsync(GPUMapMode.READ); await Promise.all([cLoad, zLoad]); result.depthValues = new Float32Array(zbuffer.getMappedRange().slice()); zbuffer.unmap(); } else { await cLoad; }
result.colorValues = new Uint32Array(colorBuffer.getMappedRange().slice()); colorBuffer.unmap();
result.generateSelection = (fx1, fy1, fx2, fy2) => generateSelectionWithData(result, fx1, fy1, fx2, fy2); return result; }; }
const DEFAULT_VALUES = { };
export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues);
vtkHardwareSelector.extend(publicAPI, model, initialValues);
model._selectionPass = vtkWebGPUHardwareSelectionPass.newInstance();
macro.setGet(publicAPI, model, ['_WebGPURenderWindow']); macro.moveToProtected(publicAPI, model, ['WebGPURenderWindow']);
vtkWebGPUHardwareSelector(publicAPI, model); }
export const newInstance = macro.newInstance( extend, 'vtkWebGPUHardwareSelector' );
export default { newInstance, extend };
|