Manually Create PolyData in vtk.js

(Donny Zimmerman) #1

I am struggling to create polydata manually with vtk.js. The methods I used in VTK are not working/supported in vtk.js. A basic example from VTK is as follows:

vtkSmartPointer points = vtkSmartPointer::New();
vtkSmartPointer triangles = vtkSmartPointer::New();
vtkSmartPointer textureCoordinates = vtkSmartPointer::New();
textureCoordinates->SetNumberOfComponents(2);
textureCoordinates->SetName(“TextureCoordinates”);

float xval, yval, x1, y1, x2, y2;

double sinangtop = Math::Sin(vtkMath::RadiansFromDegrees(product->ElevationAngle));
float ztop = sinangtop * 1000.0;

int ptIdIndex = 0, p1ind = 0, p2ind = 0, p3ind = 0, p4ind = 0, p5ind = 0;

for (int rrad = 0; rrad < 360; rrad++)
{

  	// Point 1
  	points->InsertNextPoint(1000.0, 1000.0, 0.0);			
  	textureCoordinates->InsertNextTuple2(0.5, 0.5);
  	p1ind = ptIdIndex++;			

  	// Point 2
  	xval = (float)(Math::Sin(((float)rrad * Math::PI) / 180.0));
  	yval = (float)(Math::Cos(((float)rrad * Math::PI) / 180.0));
  	x1 = 1000.0 + (1000.0 * xval);
  	y1 = 1000.0 + (1000.0 * yval);
  	points->InsertNextPoint(x1, y1, ztop);
  	textureCoordinates->InsertNextTuple2(x1 / 2000.0, y1 / 2000.0);
  	p2ind = ptIdIndex++;

  	// Point 3
  	xval = (float)(Math::Sin(((float)(rrad + 1) * Math::PI) / 180.0));
  	yval = (float)(Math::Cos(((float)(rrad + 1) * Math::PI) / 180.0));
  	x2 = 1000.0 + (1000.0 * xval);
  	y2 = 1000.0 + (1000.0 * yval);
  	points->InsertNextPoint(x2, y2, ztop);
  	textureCoordinates->InsertNextTuple2(x2 / 2000.0, y2 / 2000.0);
  	p3ind = ptIdIndex++;
  	
  	vtkSmartPointer<vtkTriangle> triangletop = vtkSmartPointer<vtkTriangle>::New();
  	triangletop->GetPointIds()->SetNumberOfIds(3);
  	triangletop->GetPointIds()->SetId(0, p1ind);
  	triangletop->GetPointIds()->SetId(1, p2ind);
  	triangletop->GetPointIds()->SetId(2, p3ind);

  	triangles->InsertNextCell(triangletop);				

  }

  vtkSmartPointer<vtkPolyData> polyData = vtkSmartPointer<vtkPolyData>::New();
  polyData->SetPoints(points);
  polyData->SetPolys(triangles);
  polyData->GetPointData()->SetTCoords(textureCoordinates);

  vtkSmartPointer<vtkTexture> texture = vtkSmartPointer<vtkTexture>::New();
  texture->MapColorScalarsThroughLookupTableOn();
  texture->SetLookupTable(lookuptable);
  texture->InterpolateOff();
  texture->SetInputConnection(imgdat->GetOutputPort());

I have found how to deal with the scalar lookup with the vtkScalarToRGBA example, but I can’t figure out why the vtkTriangle in vtk.js wants point coordinate information in it’s initialize function instead of just an index array.

Here is what I have so far in vtk.js:

const dataRange = [0, 255];

        const lookuptable = vtk.Rendering.Core.vtkColorTransferFunction.newInstance();
        lookuptable.setMappingRange(...dataRange);            

        const piecewiseFunction = vtk.Common.DataModel.vtkPiecewiseFunction.newInstance();
        piecewiseFunction.removeAllPoints();
        piecewiseFunction.addPoint(0, 0);
        piecewiseFunction.addPoint(20, 0);
        piecewiseFunction.addPoint(21, 1);
        piecewiseFunction.addPoint(255, 1);

        var index = 0;

        colors.forEach(function (item) {
            var r, g, b, a;
            r = item.R / 255.0;
            g = item.G / 255.0;
            b = item.B / 255.0

            if (index <= 20) {
                a = 0;
            }
            else {
                a = item.A / 255.0;
            }

            //lookuptable.setTableValue(index++, r, g, b, a);
            lookuptable.addRGBPoint(index++, r, g, b);

        });

        lookuptable.updateRange();

        //lookuptable.build();

        const numPts = (360 * 3) * 2;

        const points = vtk.Common.Core.vtkPoints.newInstance();
        points.setNumberOfPoints(numPts);
        const triangles = vtk.Common.Core.vtkCellArray.newInstance();
        const tcData = new Float32Array(numPts * 2);

        var xval, yval, x1, y1, x2, y2;

        var sinangtop = Math.sin(vtk.Common.Core.vtkMath.radiansFromDegrees(0.5));
        var ztop = sinangtop * 1000.0;

        var sinangbot = Math.sin(vtk.Common.Core.vtkMath.radiansFromDegrees(0));
        var zbot = sinangbot * 1000.0;

        var ptIdIndex = 0, p1ind = 0, p2ind = 0, p3ind = 0, p4ind = 0, p5ind = 0;

        var pind = 0;

        for (var rrad = 0; rrad < 360; rrad++) {
            //Point 1
            points.setPoint(ptIdIndex, 1000.0, 1000.0, 0);
            tcData[pind++] = 0.5;
            tcData[pind++] = 0.5;
            p1ind = ptIdIndex++;

            // Point 2
            xval = Math.sin((rrad * Math.PI) / 180.0);
            yval = Math.cos((rrad * Math.PI) / 180.0);
            x1 = 1000.0 + (1000.0 * xval);
            y1 = 1000.0 + (1000.0 * yval);
            points.setPoint(ptIdIndex,x1, y1, ztop);
            tcData[pind++] = x1 / 2000.0;
            tcData[pind++] = y1 / 2000.0;
            p2ind = ptIdIndex++;

            // Point 3
            xval = Math.sin(((rrad + 1) * Math.PI) / 180.0);
            yval = Math.cos(((rrad + 1) * Math.PI) / 180.0);
            x2 = 1000.0 + (1000.0 * xval);
            y2 = 1000.0 + (1000.0 * yval);
            points.setPoint(ptIdIndex, x2, y2, ztop);
            tcData[pind++] = x2 / 2000.0;
            tcData[pind++] = y2 / 2000.0;
            p3ind = ptIdIndex++;

            const triangletop = vtk.Common.DataModel.vtkTriangle.newInstance();
            triangletop.getPointIds().setNumberOfIds(3);
            triangletop.getPointIds().setId(0, p1ind);
            triangletop.getPointIds().setId(1, p2ind);
            triangletop.getPointIds().setId(2, p3ind);

            triangles.insertNextCell(triangletop);

        }

        const tcoords = vtkDataArray.newInstance({
            numberOfComponents: 2,
            values: tcData,
            name: 'TextureCoordinates',
        });

        const polydata = vtk.Common.DataModel.vtkPolyData.newInstance();
        polydata.setPoints(points);
        polydata.setPolys(triangles);
        polydata.getPointData().setTCoords(tcoords);

        const rgbaFilter = vtk.Filters.General.vtkScalarToRGBA.newInstance();
        rgbaFilter.setLookupTable(lookuptable);
        rgbaFilter.setPiecewiseFunction(piecewiseFunction);
        rgbaFilter.setInputData(imagedata);

        const texture = vtk.Rendering.Core.vtkTexture.newInstance();
        texture.interpolate = false;
        texture.setInputConnection(rgbaFilter.getOutputPort());

        mapper.setInputData(polydata);
        actor.setTexture(texture);
(Donny Zimmerman) #2

I have looked at how the polydata is created from the vtkConeSource javascript source code. I will try to use that approach and see if it works. It is quite a bit different than the way I am used to doing it in standard VTK.

(Sebastien Jourdain) #3

The structure in memory remain the same but what we try to do in vtk.js is to prevent bad habits that will lead to lot of memory allocation / gc which are way more costly than in C++.

And for example the way you could create your cell array in vtk.js if you don’t know its size before hand could be as follow (The same can be used for the points or any other arrays…):

const cellArray = [];

for (let rrad = 0; rrad < 360; rrad++) {
    // [...]
    cellArray.push(3);
    cellArray.push(p1ind);
    cellArray.push(p2ind);
    cellArray.push(p3ind);
}

const triangles = vtk.Common.Core.vtkCellArray.newInstance({ values: Uint16Array.from(cellArray) });
polydata.setPolys(triangles);

(Sebastien Jourdain) #4

One thing that could be worth adding to the vtkDataArray could be the setTuple{1,2,3,6} methods.
That way you could have a similar feel. But we were also trying not to wrap any functionality that could be naturally achieved on the native JS arrays.

(Donny Zimmerman) #5

Thanks Sebastien! I really like the idea of using the push functions. This will elegantly solve my issue. I would love to see more examples showing this kind of thing. I know the library of examples will grow over time.

Thanks again!

(Sebastien Jourdain) #6

You are welcome. Switching from C++ to JS is not trivial especially since vtk.js is not just compiling the C++ code so you can write JS like you would do in C++. (Which I think is a good thing)

It is also trying to leverage the flexibility and capability of JS itself which leads to slightly different way to achieve the same thing even if the concept remain similar.

Anyway adding more examples or documentation in how to transition is indeed good but not always easy to do. Maybe we could create an issue that could list the topics that the community would like to see described or extended. Feel free to initiate it.