import '@kitware/vtk.js/favicon';
import '@kitware/vtk.js/Rendering/Profiles/Geometry'; import '@kitware/vtk.js/Rendering/Profiles/Molecule';
import { mat4, vec3 } from 'gl-matrix';
import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor'; import vtkSphereMapper from '@kitware/vtk.js/Rendering/Core/SphereMapper'; import vtkMapper from '@kitware/vtk.js/Rendering/Core/Mapper'; import vtkOpenGLRenderWindow from '@kitware/vtk.js/Rendering/OpenGL/RenderWindow'; import vtkPixelSpaceCallbackMapper from '@kitware/vtk.js/Rendering/Core/PixelSpaceCallbackMapper'; import vtkRenderWindow from '@kitware/vtk.js/Rendering/Core/RenderWindow'; import vtkRenderWindowInteractor from '@kitware/vtk.js/Rendering/Core/RenderWindowInteractor'; import vtkRenderer from '@kitware/vtk.js/Rendering/Core/Renderer'; import vtkInteractorStyleTrackballCamera from '@kitware/vtk.js/Interaction/Style/InteractorStyleTrackballCamera'; import vtk from '@kitware/vtk.js/vtk';
import '@kitware/vtk.js/Common/Core/Points'; import '@kitware/vtk.js/Common/Core/DataArray'; import '@kitware/vtk.js/Common/Core/StringArray'; import '@kitware/vtk.js/Common/DataModel/PolyData';
import style from './style.module.css';
function affine(val, inMin, inMax, outMin, outMax) { return ((val - inMin) / (inMax - inMin)) * (outMax - outMin) + outMin; }
const bodyElement = document.querySelector('body'); const container = document.createElement('div'); container.classList.add(style.container); bodyElement.appendChild(container);
let textCtx = null; let windowWidth = 0; let windowHeight = 0; const enableDebugCanvas = true; let debugHandler = null;
const renderWindow = vtkRenderWindow.newInstance(); const renderer = vtkRenderer.newInstance({ background: [0.2, 0.3, 0.4] }); renderWindow.addRenderer(renderer);
const pointPoly = vtk({ vtkClass: 'vtkPolyData', points: { vtkClass: 'vtkPoints', dataType: 'Float32Array', numberOfComponents: 3, values: [0, 0, -1], }, polys: { vtkClass: 'vtkCellArray', dataType: 'Uint16Array', values: [1, 0], }, pointData: { vtkClass: 'vtkDataSetAttributes', arrays: [ { data: { vtkClass: 'vtkStringArray', name: 'pointLabels', dataType: 'string', values: ['Neo'], }, }, ], }, });
const planePoly = vtk({ vtkClass: 'vtkPolyData', points: { vtkClass: 'vtkPoints', dataType: 'Float32Array', numberOfComponents: 3, values: [-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0], }, polys: { vtkClass: 'vtkCellArray', dataType: 'Uint16Array', values: [3, 0, 1, 2, 3, 0, 2, 3], }, });
function resetCameraPosition(doRender = false) { const activeCamera = renderWindow.getRenderers()[0].getActiveCamera(); activeCamera.setPosition(0, 0, 3); activeCamera.setFocalPoint(0, 0, 0); activeCamera.setViewUp(0, 1, 0); activeCamera.setClippingRange(3.49999, 4.50001);
if (doRender) { renderWindow.render(); } }
function initializeDebugHandler() { const debugCanvas = document.createElement('canvas'); const debugCtx = debugCanvas.getContext('2d');
const debugCanvasSize = 1 / 4; let dbgWidth = 0; let dbgHeight = 0; let lastDepthBuffer = null;
debugCanvas.addEventListener('click', (evt) => { evt.preventDefault(); evt.stopPropagation(); const x = (evt.pageX - debugCanvas.offsetLeft) / debugCanvasSize; const y = (evt.pageY - debugCanvas.offsetTop) / debugCanvasSize;
if (lastDepthBuffer && dbgWidth > 0 && dbgHeight > 0) { const dIdx = ((dbgHeight - 1 - Math.floor(y)) * dbgWidth + Math.floor(x)) * 4; const r = lastDepthBuffer[dIdx] / 255; const g = lastDepthBuffer[dIdx + 1] / 255; let z = (r * 256 + g) / 257; z = z * 2 - 1; console.log(`depth at (${x}, ${y}) is ${z}`);
const activeCamera = renderWindow.getRenderers()[0].getActiveCamera(); const crange = activeCamera.getClippingRange(); console.log(`current clipping range: [${crange[0]}, ${crange[1]}]`); } else { console.log(`click(${x}, ${y})`); } });
debugCanvas.classList.add(style.debugCanvas, 'debugCanvas'); bodyElement.appendChild(debugCanvas);
return { update: (coordsList, depthBuffer) => { debugCtx.fillStyle = 'rgba(255, 255, 255, 1.0)'; debugCtx.clearRect(0, 0, dbgWidth, dbgHeight);
if (!depthBuffer) { console.error('Expected a depthBuffer!'); return; }
lastDepthBuffer = depthBuffer;
if (dbgWidth === 0 || dbgHeight === 0) { console.log('No size yet, cannot draw debug canvas'); return; }
const depthRange = [10000000, -10000000];
const imageData = debugCtx.getImageData(0, 0, dbgWidth, dbgHeight); const data = imageData.data; for (let y = 0; y < imageData.height; y += 1) { for (let x = 0; x < imageData.width; x += 1) { const dIdx = ((imageData.height - 1 - y) * imageData.width + x) * 4;
const r = depthBuffer[dIdx] / 255; const g = depthBuffer[dIdx + 1] / 255; const z = (r * 256 + g) / 257; const zColor = affine(z, 0, 1, 0, 255); const pIdx = (y * imageData.width + x) * 4; data[pIdx] = zColor; data[pIdx + 1] = zColor; data[pIdx + 2] = zColor; data[pIdx + 3] = 255;
if (z < depthRange[0]) { depthRange[0] = z; }
if (z > depthRange[1]) { depthRange[1] = z; }
} } debugCtx.putImageData(imageData, 0, 0);
}, resize: (w, h) => { console.log(`Debug canvas resize: [${w}, ${h}]`); const aspect = w / h; const sw = w * debugCanvasSize; const sh = sw / aspect; debugCanvas.setAttribute('width', w); debugCanvas.setAttribute('height', h); debugCanvas.setAttribute('style', `width: ${sw}px; height: ${sh}px;`); dbgWidth = w; dbgHeight = h; }, }; }
const pointMapper = vtkSphereMapper.newInstance({ radius: 0.5 }); const pointActor = vtkActor.newInstance(); pointMapper.setInputData(pointPoly); pointActor.setMapper(pointMapper);
const planeMapper = vtkMapper.newInstance(); const planeActor = vtkActor.newInstance(); planeMapper.setInputData(planePoly); planeActor.setMapper(planeMapper);
const psMapper = vtkPixelSpaceCallbackMapper.newInstance(); psMapper.setInputData(pointPoly); psMapper.setUseZValues(true); psMapper.setCallback((coordsList, camera, aspect, depthBuffer) => { if (textCtx && windowWidth > 0 && windowHeight > 0) { const dataPoints = psMapper.getInputData().getPoints();
const viewMatrix = camera.getViewMatrix(); mat4.transpose(viewMatrix, viewMatrix); const projMatrix = camera.getProjectionMatrix(aspect, -1, 1); mat4.transpose(projMatrix, projMatrix);
textCtx.clearRect(0, 0, windowWidth, windowHeight); coordsList.forEach((xy, idx) => { const pdPoint = dataPoints.getPoint(idx); const vc = vec3.fromValues(pdPoint[0], pdPoint[1], pdPoint[2]); vec3.transformMat4(vc, vc, viewMatrix); vc[2] += 0.5; vec3.transformMat4(vc, vc, projMatrix);
console.log( `Distance to camera: point = ${xy[2]}, depth buffer = ${xy[3]}` ); if (vc[2] - 0.001 < xy[3]) { textCtx.font = '12px serif'; textCtx.textAlign = 'center'; textCtx.textBaseline = 'middle'; textCtx.fillText(`p ${idx}`, xy[0], windowHeight - xy[1]); } }); const activeCamera = renderWindow.getRenderers()[0].getActiveCamera(); const crange = activeCamera.getClippingRange(); console.log(`current clipping range: [${crange[0]}, ${crange[1]}]`); }
if (enableDebugCanvas && depthBuffer) { if (!debugHandler) { debugHandler = initializeDebugHandler(); } debugHandler.update(coordsList, depthBuffer); } });
const textActor = vtkActor.newInstance(); textActor.setMapper(psMapper);
renderer.addActor(pointActor); renderer.addActor(textActor); renderer.addActor(planeActor);
resetCameraPosition();
const openGLRenderWindow = vtkOpenGLRenderWindow.newInstance(); renderWindow.addView(openGLRenderWindow); openGLRenderWindow.setContainer(container);
const textCanvas = document.createElement('canvas'); textCanvas.classList.add(style.container, 'textCanvas'); container.appendChild(textCanvas);
textCtx = textCanvas.getContext('2d');
const interactor = vtkRenderWindowInteractor.newInstance(); interactor.setView(openGLRenderWindow); interactor.initialize(); interactor.bindEvents(container);
interactor.setInteractorStyle(vtkInteractorStyleTrackballCamera.newInstance());
function resize() { const dims = container.getBoundingClientRect(); windowWidth = Math.floor(dims.width); windowHeight = Math.floor(dims.height); openGLRenderWindow.setSize(windowWidth, windowHeight); textCanvas.setAttribute('width', windowWidth); textCanvas.setAttribute('height', windowHeight); if (debugHandler) { debugHandler.resize(windowWidth, windowHeight); } renderWindow.render(); }
window.addEventListener('resize', resize);
resize();
bodyElement.addEventListener('keypress', (e) => { if (String.fromCharCode(e.charCode) === 'm') { renderWindow.render(); } else if (String.fromCharCode(e.charCode) === 'n') { resetCameraPosition(true); } });
|