Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VTK - How to render multiple 3d text objects with single actor

I am trying to render text using the vtk object vtkVectorText. It works fine for a single 3d location. What I need is to place a 3d text above some 3d (vtkPoint) points. I tried doing it with the vtkTextActor3d but each text object requires a separate actor and I ended up with 10k actors, which is pretty bad and laggy when I try for example to rotate the scene.

I also tried to apply some appendFilter to create a vtkVectorText array, get the unstructured grid from appendFilter's object and then convert it to polydata in order to render the unstructured grid with my desired point locations. It shows nothing as I can't figure out what is the best way to do this.

Can someone help me with this?

Here is my code from the last part:

vtkSmartPointer<vtkAppendFilter> appendFilter = vtkSmartPointer<vtkAppendFilter>::New();
//for each point
for (int i = 0; i < N;i++) {
    vtkSmartPointer<vtkVectorText> vecText = vtkSmartPointer<vtkVectorText>::New();
    vecText->SetText("My text, needs to appear multiple times");
    vecText->Update();
    appendFilter->AddInputData(vecText->GetOutput());
    appendFilter->Update();
}
vtkSmartPointer<vtkUnstructuredGrid> unstructuredGrid = appendFilter->GetOutput();
unstructuredGrid->Allocate(N);
unstructuredGrid->SetPoints(points);
vtkSmartPointer<vtkGeometryFilter> geometryFilter = vtkSmartPointer<vtkGeometryFilter>::New();
geometryFilter->SetInputData(unstructuredGrid);
geometryFilter->Update();
vtkSmartPointer<vtkPolyDataMapper> textMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
textMapper->SetInputConnection(geometryFilter->GetOutputPort());
vtkSmartPointer<vtkActor> textActor = vtkSmartPointer<vtkActor>::New();
textActor->SetMapper(textMapper);
textActor->GetProperty()->SetColor(0, 1, 0);

renderer->AddActor(textActor);
like image 318
PeGiannOS Avatar asked Nov 24 '16 14:11

PeGiannOS


1 Answers

If the text is the same for each point, I would propose using it as a glyph:

vtkSmartPointer<vtkPolyData> pointsHolder = vtkSmartPointer<vtkPolyData>::New();
pointsHolder->SetPoints(points); // I assume that these are the points where you want the object to be rendered
vtkSmartPointer<vtkGlyph3DMapper> glyphMapper = vtkSmartPointer<vtkGlyph3DMapper>::New();
glyphMapper->SetSourceConnection(vecText->GetOutputPort()); // this says WHAT should be rendered
glyphMapper->SetInputData(pointsHolder); // this says WHERE
textActor->SetMapper(glyphMapper);

This will render the text at all the points in the "points" array (which I assume is a vtkPoints array with the positions where you want the text to be rendered). This way you can do all kinds of things that you can't when you append it into one mesh, like setting a different size for each glyph by providing a scale array or turn them on/off interactively by providing a mask array etc., see http://www.vtk.org/doc/nightly/html/classvtkGlyph3DMapper.html

If the text can be different for each point, you will probably have to do it the way of appending. I can see a few bugs in your appending code:

1) First a small performance one - call the appendFilter->Update(); only once, after you have set all the inputs.

2)

unstructuredGrid->Allocate(N);
unstructuredGrid->SetPoints(points);

By calling Allocate, you just resetted everything the appendfilter has done for you. Even without it, the second line would rewrite positions for all the points that were generated for the text. The output of the append filter is simply what you should directly assign to your mapper, these two lines have to be deleted. Just as well as the vtkGeometryFilter, I don't see any reason for it (I guess you used it to get vtkPolyData instead of vtkUnstructuredGrid - just use vtkAppendPolydata instead of vtkAppendFilter, it will produce polydata directly).

3) However, now the "points" array is not used, i.e. your text will not be in the correct position. You would have to transform the polydata for each of the text instance before you assign it to the append filter. The simplest would be to use the points from your "points" array as translation vectors, so you would add the point's coordinates to each points of the vecText->GetOutput() before sending it to the append filter, so something like this:

vtkSmartPointer<vtkAppendPolyData> appendFilter = vtkSmartPointer<vtkAppendPolyData>::New();
//for each point
for (int i = 0; i < N;i++) {
    vtkSmartPointer<vtkVectorText> vecText = vtkSmartPointer<vtkVectorText>::New();
    vecText->SetText("My text, needs to appear multiple times");
    vecText->Update();
    vtkPolyData *output = vecText->GetOutput();
    double *location = points->GetPoint(i);
    for (int j = 0; j < output->GetNumberOfPoints(); j++) {
        double *point = output->GetPoint(j);
        output->GetPoints()->SetPoint(j, point[0] + location[0], point[1] + location[1], points[2] + location[2]);
    }
    appendFilter->AddInputData(output);
}
appendFilter->Update();

Something more clever would be needed if you want to for example center the text there.

like image 187
tomj Avatar answered Nov 15 '22 11:11

tomj