Here’s my code (works, needs cleanup) in case it’s useful to anybody.
import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor'
import vtkRenderer from "vtk.js/Sources/Rendering/Core/Renderer"
import vtkOpenGLRenderWindow from "vtk.js/Sources/Rendering/OpenGL/RenderWindow"
import vtkPixelSpaceCallbackMapper from "vtk.js/Sources/Rendering/Core/PixelSpaceCallbackMapper"
import vtk from "vtk.js/Sources/vtk"
export class RenderTextData {
polydata: Object
labels: string[] = []
positions: number[] = []
constructor(pd : Object) {
this.polydata = pd
}
}
export function addRenderText(data: RenderTextData, pos: [number, number, number], text: string) {
data.polydata['getPoints']()['insertNextPoint'](pos[0], pos[1], pos[2])
data.labels.push(text)
data.positions.push(pos[0], pos[1], pos[2])
}
export function setupRenderText(renderer: vtkRenderer, openglRenderWindow: vtkOpenGLRenderWindow,
textCtx: CanvasRenderingContext2D): RenderTextData {
const pointPoly = vtk({vtkClass: 'vtkPolyData'}) as Object
let data = new RenderTextData(pointPoly)
// (1) create a vtkPixelSpaceCallbackMapper and an actor,
// (2) have the mapper’s callback apply the current view transform to the 3d position to get a 2d screen position,
// (3) create html text directly on the canvas used by the renderer.
let oglCtx = openglRenderWindow.get3DContext({})!
const psMapper = vtkPixelSpaceCallbackMapper.newInstance();
psMapper.setInputData(pointPoly);
// @ts-ignore
psMapper.setCallback((coords, camera, aspect, depthBuffer) => {
textCtx.clearRect(0, 0, oglCtx.drawingBufferWidth, oglCtx.drawingBufferHeight);
coords.forEach((xy, idx) => {
let label = data.labels[idx]
textCtx.fillStyle = "white";
textCtx.font = "12px Arial";
textCtx.fillText(label, xy[0], oglCtx.drawingBufferHeight - xy[1]);
})
})
const textActor = vtkActor.newInstance();
textActor.setMapper(psMapper);
renderer.addActor(textActor);
renderer.resetCamera();
return data
}
// example of how to call it
function testSetup() {
const container: HTMLElement = ... (the div that encloses both opengl canvas and text canvas, with position: relative)
const { width, height } = container.getBoundingClientRect()
// Set up the text canvas overlay for the labels
const textCanvas = document.createElement('canvas')
textCanvas.setAttribute('style', 'position: absolute; top: 0; left: 0');
textCanvas.setAttribute('width', width.toString());
textCanvas.setAttribute('height', height.toString());
container.appendChild(textCanvas)
const textCtx = textCanvas.getContext('2d')!
let renderTextData = setupRenderText(renderer, openglRenderWindow, textCtx!
addRenderText(renderTextData, [0, 0, 0], "the text")
}