import macro from 'vtk.js/Sources/macros'; import Constants from 'vtk.js/Sources/Rendering/OpenGL/HardwareSelector/Constants'; import vtkHardwareSelector from 'vtk.js/Sources/Rendering/Core/HardwareSelector'; import vtkOpenGLFramebuffer from 'vtk.js/Sources/Rendering/OpenGL/Framebuffer'; import vtkSelectionNode from 'vtk.js/Sources/Common/DataModel/SelectionNode'; import vtkDataSet from 'vtk.js/Sources/Common/DataModel/DataSet';
const { PassTypes } = Constants; const { SelectionContent, SelectionField } = vtkSelectionNode; const { FieldAssociations } = vtkDataSet; const { vtkErrorMacro } = macro;
const idOffset = 1;
function getInfoHash(info) { return `${info.propID} ${info.compositeID}`; }
function getAlpha(xx, yy, pb, area) { if (!pb) { return 0; } const offset = (yy * (area[2] - area[0] + 1) + xx) * 4; return pb[offset + 3]; }
function convert(xx, yy, pb, area) { if (!pb) { return 0; } const offset = (yy * (area[2] - area[0] + 1) + xx) * 4; const r = pb[offset]; const g = pb[offset + 1]; const b = pb[offset + 2]; return (b * 256 + g) * 256 + r; }
function getID(low24, high8) { let val = high8; val <<= 24; val |= low24; return val; }
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] < buffdata.area[0] || inDisplayPosition[0] > buffdata.area[2] || inDisplayPosition[1] < buffdata.area[1] || inDisplayPosition[1] > buffdata.area[3] ) { return null; }
const displayPosition = [ inDisplayPosition[0] - buffdata.area[0], inDisplayPosition[1] - buffdata.area[1], ];
const actorid = convert( displayPosition[0], displayPosition[1], buffdata.pixBuffer[PassTypes.ACTOR_PASS], buffdata.area ); if (actorid <= 0 || actorid - idOffset >= buffdata.props.length) { return null; }
const info = {}; info.valid = true;
info.propID = actorid - idOffset; info.prop = buffdata.props[info.propID];
let compositeID = convert( displayPosition[0], displayPosition[1], buffdata.pixBuffer[PassTypes.COMPOSITE_INDEX_PASS], buffdata.area ); if (compositeID < 0 || compositeID > 0xffffff) { compositeID = 0; } info.compositeID = compositeID - idOffset; if (buffdata.captureZValues) { const offset = (displayPosition[1] * (buffdata.area[2] - buffdata.area[0] + 1) + displayPosition[0]) * 4; info.zValue = (256 * buffdata.zBuffer[offset] + buffdata.zBuffer[offset + 1]) / 65535.0; info.displayPosition = inDisplayPosition; }
if (buffdata.pixBuffer[PassTypes.ID_LOW24]) { if ( getAlpha( displayPosition[0], displayPosition[1], buffdata.pixBuffer[PassTypes.ID_LOW24], buffdata.area ) === 0.0 ) { return info; } }
const low24 = convert( displayPosition[0], displayPosition[1], buffdata.pixBuffer[PassTypes.ID_LOW24], buffdata.area ); const high24 = convert( displayPosition[0], displayPosition[1], buffdata.pixBuffer[PassTypes.ID_HIGH24], buffdata.area ); info.attributeID = getID(low24, high24, 0);
return info; }
const dispPos = [inDisplayPosition[0], inDisplayPosition[1]]; const curPos = [0, 0]; let info = getPixelInformationWithData( buffdata, inDisplayPosition, 0, outSelectedPosition ); if (info && info.valid) { 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 && info.valid) { return info; } } curPos[0] = dispPos[0] + dist; info = getPixelInformationWithData( buffdata, curPos, 0, outSelectedPosition ); if (info && info.valid) { 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 && info.valid) { return info; } } curPos[1] = dispPos[1] + dist; info = getPixelInformationWithData( buffdata, curPos, 0, outSelectedPosition ); if (info && info.valid) { return info; } } }
outSelectedPosition[0] = inDisplayPosition[0]; outSelectedPosition[1] = inDisplayPosition[1]; return null; }
function convertSelection( fieldassociation, dataMap, captureZValues, renderer, openGLRenderWindow ) { 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; child.getProperties().prop = value.info.prop; child.getProperties().compositeID = value.info.compositeID; child.getProperties().attributeID = value.info.attributeID; child.getProperties().pixelCount = value.pixelCount; if (captureZValues) { child.getProperties().displayPosition = [ value.info.displayPosition[0], value.info.displayPosition[1], value.info.zValue, ]; child.getProperties().worldPosition = openGLRenderWindow.displayToWorld( value.info.displayPosition[0], value.info.displayPosition[1], value.info.zValue, 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 && info.valid) { 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.captureZValues, buffdata.renderer, buffdata.openGLRenderWindow ); }
function vtkOpenGLHardwareSelector(publicAPI, model) { model.classHierarchy.push('vtkOpenGLHardwareSelector');
publicAPI.releasePixBuffers = () => { model.rawPixBuffer = []; model.pixBuffer = []; model.zBuffer = null; };
publicAPI.beginSelection = () => { model._openGLRenderer = model._openGLRenderWindow.getViewNodeFor( model._renderer ); model.maxAttributeId = 0;
const size = model._openGLRenderWindow.getSize(); if (!model.framebuffer) { model.framebuffer = vtkOpenGLFramebuffer.newInstance(); model.framebuffer.setOpenGLRenderWindow(model._openGLRenderWindow); model.framebuffer.saveCurrentBindingsAndBuffers(); model.framebuffer.create(size[0], size[1]); model.framebuffer.populateFramebuffer(); } else { model.framebuffer.setOpenGLRenderWindow(model._openGLRenderWindow); model.framebuffer.saveCurrentBindingsAndBuffers(); const fbSize = model.framebuffer.getSize(); if (!fbSize || fbSize[0] !== size[0] || fbSize[1] !== size[1]) { model.framebuffer.create(size[0], size[1]); model.framebuffer.populateFramebuffer(); } else { model.framebuffer.bind(); } }
model._openGLRenderer.clear(); model._openGLRenderer.setSelector(publicAPI); model.hitProps = {}; model.propPixels = {}; model.props = []; publicAPI.releasePixBuffers();
if (model.fieldAssociation === FieldAssociations.FIELD_ASSOCIATION_POINTS) { const gl = model._openGLRenderWindow.getContext(); const originalBlending = gl.isEnabled(gl.BLEND); gl.disable(gl.BLEND); model._openGLRenderWindow.traverseAllPasses(); if (originalBlending) { gl.enable(gl.BLEND); } } };
publicAPI.endSelection = () => { model.hitProps = {}; model._openGLRenderer.setSelector(null); model.framebuffer.restorePreviousBindingsAndBuffers(); };
publicAPI.preCapturePass = () => { const gl = model._openGLRenderWindow.getContext(); model.originalBlending = gl.isEnabled(gl.BLEND); gl.disable(gl.BLEND); };
publicAPI.postCapturePass = () => { const gl = model._openGLRenderWindow.getContext(); if (model.originalBlending) { gl.enable(gl.BLEND); } };
publicAPI.select = () => { let sel = null; if (publicAPI.captureBuffers()) { sel = publicAPI.generateSelection( model.area[0], model.area[1], model.area[2], model.area[3] ); publicAPI.releasePixBuffers(); } return sel; };
publicAPI.getSourceDataAsync = async (renderer, fx1, fy1, fx2, fy2) => { model._renderer = renderer;
if (fx1 === undefined) { const size = model._openGLRenderWindow.getSize(); publicAPI.setArea(0, 0, size[0] - 1, size[1] - 1); } else { publicAPI.setArea(fx1, fy1, fx2, fy2); } if (!publicAPI.captureBuffers()) { return false; } const result = { area: [...model.area], pixBuffer: [...model.pixBuffer], captureZValues: model.captureZValues, zBuffer: model.zBuffer, props: [...model.props], fieldAssociation: model.fieldAssociation, renderer, openGLRenderWindow: model._openGLRenderWindow, }; result.generateSelection = (...args) => generateSelectionWithData(result, ...args); return result; };
publicAPI.captureBuffers = () => { if (!model._renderer || !model._openGLRenderWindow) { vtkErrorMacro('Renderer and view must be set before calling Select.'); return false; }
model._openGLRenderer = model._openGLRenderWindow.getViewNodeFor( model._renderer );
model._openGLRenderWindow.getRenderable().preRender();
publicAPI.invokeEvent({ type: 'StartEvent' });
model.originalBackground = model._renderer.getBackgroundByReference(); model._renderer.setBackground(0.0, 0.0, 0.0, 0.0); const rpasses = model._openGLRenderWindow.getRenderPasses();
publicAPI.beginSelection(); const pixelBufferSavedPasses = []; for ( model.currentPass = PassTypes.MIN_KNOWN_PASS; model.currentPass <= PassTypes.MAX_KNOWN_PASS; model.currentPass++ ) { if (publicAPI.passRequired(model.currentPass)) { publicAPI.preCapturePass(model.currentPass); if ( model.captureZValues && model.currentPass === PassTypes.ACTOR_PASS && typeof rpasses[0].requestDepth === 'function' && typeof rpasses[0].getFramebuffer === 'function' ) { rpasses[0].requestDepth(); model._openGLRenderWindow.traverseAllPasses(); } else { model._openGLRenderWindow.traverseAllPasses(); } publicAPI.postCapturePass(model.currentPass);
publicAPI.savePixelBuffer(model.currentPass); pixelBufferSavedPasses.push(model.currentPass); } }
pixelBufferSavedPasses.forEach((pass) => { model.currentPass = pass; publicAPI.processPixelBuffers(); }); model.currentPass = PassTypes.MAX_KNOWN_PASS;
publicAPI.endSelection();
model._renderer.setBackground(model.originalBackground); publicAPI.invokeEvent({ type: 'EndEvent' });
return true; };
publicAPI.processPixelBuffers = () => { model.props.forEach((prop, index) => { if (publicAPI.isPropHit(index)) { prop.processSelectorPixelBuffers(publicAPI, model.propPixels[index]); } }); };
publicAPI.passRequired = (pass) => { if (pass === PassTypes.ID_HIGH24) { if ( model.fieldAssociation === FieldAssociations.FIELD_ASSOCIATION_POINTS ) { return model.maximumPointId > 0x00ffffff; } if ( model.fieldAssociation === FieldAssociations.FIELD_ASSOCIATION_CELLS ) { return model.maximumCellId > 0x00ffffff; } } return true; };
publicAPI.savePixelBuffer = (passNo) => { model.pixBuffer[passNo] = model._openGLRenderWindow.getPixelData( model.area[0], model.area[1], model.area[2], model.area[3] );
if (!model.rawPixBuffer[passNo]) { const size = (model.area[2] - model.area[0] + 1) * (model.area[3] - model.area[1] + 1) * 4;
model.rawPixBuffer[passNo] = new Uint8Array(size); model.rawPixBuffer[passNo].set(model.pixBuffer[passNo]); }
if (passNo === PassTypes.ACTOR_PASS) { if (model.captureZValues) { const rpasses = model._openGLRenderWindow.getRenderPasses(); if ( typeof rpasses[0].requestDepth === 'function' && typeof rpasses[0].getFramebuffer === 'function' ) { const fb = rpasses[0].getFramebuffer(); fb.saveCurrentBindingsAndBuffers(); fb.bind(); model.zBuffer = model._openGLRenderWindow.getPixelData( model.area[0], model.area[1], model.area[2], model.area[3] ); fb.restorePreviousBindingsAndBuffers(); } } publicAPI.buildPropHitList(model.rawPixBuffer[passNo]); } };
publicAPI.buildPropHitList = (pixelbuffer) => { let offset = 0; for (let yy = 0; yy <= model.area[3] - model.area[1]; yy++) { for (let xx = 0; xx <= model.area[2] - model.area[0]; xx++) { let val = convert(xx, yy, pixelbuffer, model.area); if (val > 0) { val--; if (!(val in model.hitProps)) { model.hitProps[val] = true; model.propPixels[val] = []; } model.propPixels[val].push(offset * 4); } ++offset; } } };
publicAPI.renderProp = (prop) => { if (model.currentPass === PassTypes.ACTOR_PASS) { publicAPI.setPropColorValueFromInt(model.props.length + idOffset); model.props.push(prop); } };
publicAPI.renderCompositeIndex = (index) => { if (model.currentPass === PassTypes.COMPOSITE_INDEX_PASS) { publicAPI.setPropColorValueFromInt(index + idOffset); } };
publicAPI.renderAttributeId = (attribid) => { if (attribid < 0) { return; }
model.maxAttributeId = attribid > model.maxAttributeId ? attribid : model.maxAttributeId;
};
publicAPI.passTypeToString = (type) => macro.enumToString(PassTypes, type);
publicAPI.isPropHit = (id) => Boolean(model.hitProps[id]);
publicAPI.setPropColorValueFromInt = (val) => { model.propColorValue[0] = (val % 256) / 255.0; model.propColorValue[1] = (Math.floor(val / 256) % 256) / 255.0; model.propColorValue[2] = (Math.floor(val / 65536) % 256) / 255.0; };
publicAPI.getPixelInformation = ( inDisplayPosition, maxDistance, outSelectedPosition ) => { const maxDist = maxDistance < 0 ? 0 : maxDistance; if (maxDist === 0) { outSelectedPosition[0] = inDisplayPosition[0]; outSelectedPosition[1] = inDisplayPosition[1]; if ( inDisplayPosition[0] < model.area[0] || inDisplayPosition[0] > model.area[2] || inDisplayPosition[1] < model.area[1] || inDisplayPosition[1] > model.area[3] ) { return null; }
const displayPosition = [ inDisplayPosition[0] - model.area[0], inDisplayPosition[1] - model.area[1], ];
const actorid = convert( displayPosition[0], displayPosition[1], model.pixBuffer[PassTypes.ACTOR_PASS], model.area ); if (actorid <= 0 || actorid - idOffset >= model.props.length) { return null; }
const info = {}; info.valid = true;
info.propID = actorid - idOffset; info.prop = model.props[info.propID]; let compositeID = convert( displayPosition[0], displayPosition[1], model.pixBuffer[PassTypes.COMPOSITE_INDEX_PASS], model.area ); if (compositeID < 0 || compositeID > 0xffffff) { compositeID = 0; } info.compositeID = compositeID - idOffset; if (model.captureZValues) { const offset = (displayPosition[1] * (model.area[2] - model.area[0] + 1) + displayPosition[0]) * 4; info.zValue = (256 * model.zBuffer[offset] + model.zBuffer[offset + 1]) / 65535.0; info.displayPosition = inDisplayPosition; }
if (model.pixBuffer[PassTypes.ID_LOW24]) { if ( getAlpha( displayPosition[0], displayPosition[1], model.pixBuffer[PassTypes.ID_LOW24], model.area ) === 0.0 ) { return info; } }
const low24 = convert( displayPosition[0], displayPosition[1], model.pixBuffer[PassTypes.ID_LOW24], model.area ); const high24 = convert( displayPosition[0], displayPosition[1], model.pixBuffer[PassTypes.ID_HIGH24], model.area ); info.attributeID = getID(low24, high24);
return info; }
const dispPos = [inDisplayPosition[0], inDisplayPosition[1]]; const curPos = [0, 0]; let info = publicAPI.getPixelInformation( inDisplayPosition, 0, outSelectedPosition ); if (info && info.valid) { 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 = publicAPI.getPixelInformation(curPos, 0, outSelectedPosition); if (info && info.valid) { return info; } } curPos[0] = dispPos[0] + dist; info = publicAPI.getPixelInformation(curPos, 0, outSelectedPosition); if (info && info.valid) { 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 = publicAPI.getPixelInformation(curPos, 0, outSelectedPosition); if (info && info.valid) { return info; } } curPos[1] = dispPos[1] + dist; info = publicAPI.getPixelInformation(curPos, 0, outSelectedPosition); if (info && info.valid) { return info; } } }
outSelectedPosition[0] = inDisplayPosition[0]; outSelectedPosition[1] = inDisplayPosition[1]; return null; };
publicAPI.generateSelection = (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 = publicAPI.getPixelInformation(pos, 0, outSelectedPosition); if (info && info.valid) { 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 (model.captureZValues) { if (info.zValue < dmv.info.zValue) { dmv.info = info; } } if (dmv.attributeIDs.indexOf(info.attributeID) === -1) { dmv.attributeIDs.push(info.attributeID); } } } } } return convertSelection( model.fieldAssociation, dataMap, model.captureZValues, model._renderer, model._openGLRenderWindow ); };
publicAPI.getRawPixelBuffer = (passNo) => model.rawPixBuffer[passNo]; publicAPI.getPixelBuffer = (passNo) => model.pixBuffer[passNo];
publicAPI.attach = (openGLRenderWindow, renderer) => { model._openGLRenderWindow = openGLRenderWindow; model._renderer = renderer; };
const superSetArea = publicAPI.setArea; publicAPI.setArea = (...args) => { if (superSetArea(...args)) { model.area[0] = Math.floor(model.area[0]); model.area[1] = Math.floor(model.area[1]); model.area[2] = Math.floor(model.area[2]); model.area[3] = Math.floor(model.area[3]); return true; } return false; }; }
const DEFAULT_VALUES = { area: undefined, currentPass: -1, propColorValue: null, props: null, maximumPointId: 0, maximumCellId: 0, idOffset: 1, };
export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues);
vtkHardwareSelector.extend(publicAPI, model, initialValues);
model.propColorValue = [0, 0, 0]; model.props = [];
if (!model.area) { model.area = [0, 0, 0, 0]; }
macro.setGetArray(publicAPI, model, ['area'], 4); macro.setGet(publicAPI, model, [ '_renderer', 'currentPass', '_openGLRenderWindow', 'maximumPointId', 'maximumCellId', ]);
macro.setGetArray(publicAPI, model, ['propColorValue'], 3); macro.moveToProtected(publicAPI, model, ['renderer', 'openGLRenderWindow']); macro.event(publicAPI, model, 'event');
vtkOpenGLHardwareSelector(publicAPI, model); }
export const newInstance = macro.newInstance( extend, 'vtkOpenGLHardwareSelector' );
export default { newInstance, extend, ...Constants };
|