Manually Create PolyData in vtk.js

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);

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.

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);

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.

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!

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.

Relative to this topic ‘Create polydata in vtk.js’, I am struggling with coding a polyData from a JSON file that represents continents.

Easy to read with d3.js, I cannot create a vtk.vtkAppendPolyData correctly.
Any help would be appreciated.





  
d3.queue()
        .defer(d3.json, "https://unpkg.com/world-atlas@1/world/50m.json")
	.awaitAll(function(error, data) {
        	if (error) throw error;
        	console.log('continents ---> Read');
        	console.log('################### finished');

               multilinestring = topojson.mesh(data[0], data[0].objects.land);

              var polydata = vtk({
		  vtkClass: 'vtkPolyData',
		  points: {
		    vtkClass: 'vtkPoints',
		    dataType: 'Float32Array',
		    numberOfComponents: 3
		  },
		  lines: {
		    vtkClass: 'vtkCellArray',
		    dataType: 'Uint16Array'
		  }
	       });

               appendPolydata = vtk.Filters.General.vtkAppendPolyData.newInstance();
	       for (var p=0; p < multilinestring.coordinates.length; p++) {
        	       line = multilinestring.coordinates[p];
  		       polygonpoints = [];
  		       polygonlines = [];
                       for (var i=0; i<line.length; i++) {
			    xyz = vertex(line[i], 1.001);    // calculate a projection (return xyz)
        		    polygonpoints.push(xyz[0], xyz[1], xyz[2]);
        		    polygonlines.push(i);
		     }
                     polydata.getPoints().setData(polygonpoints);
  		     polydata.getLines().setData(polygonlines);
                     appendPolydata.addInputConnection(polydata);	
                }

         // add mapper
        // add actor

         );  //await

I have done this in python and this was much easier. What am I missing ?

Instead of converting a JSON to an empty polydata, it might be easier to just do:

var polyData = vtk.Common.DataModel.vtkPolyData.newInstance()

Then I’m not sure why you are using vtkAppendPolyData as you just need to keep adding to your points and cells…

But the piece that is actually missing from your code, is the “cell size”. The layout of the array that compose the cellArray is [nbPoints, pIdx0, pIdx1, …, nbPoints, pIdx0, pIdx1, …]

Here is what I would do:

const polydata = vtkPolyData.newInstance();
const vertexArray = [];
const vertexCount = 0;
const cellArray = [];

for (let p=0; p < multilinestring.coordinates.length; p++) {
    line = multilinestring.coordinates[p];
    cellArray.push(line.length);
    for (let i = 0; i < line.length; i++) {
        const xyz = vertex(line[i], 1.001); // calculate a projection (return xyz)
        cellArray.push(vertexCount++);
        vertexArray.push(xyz[0], xyz[1], xyz[2]);
    }
}

polydata.getPoints().setData(Float32Array.from(vertexArray), 3);
polydata.getLines().setData(Uint16Array.from(cellArray));
1 Like

It is running in a CodePen here

Thank you very much for helping on this.
Yes no need of a vtkAppendPolyData.

What about setting scalar for cell data.

In python, it is

polydata.GetCellData().SetScalars(valuesArray)

I cannot do the same thing with vtk.js

polydata.getCellData().getScalars().setData(Float32Array.from(valuesArray));

gives me
TypeError: polydata.getCellData(...).getScalars(...) is null

polydata.getCellData().SetScalars(
  vtkDataArray.newInstance({ name: 'countries', values: Float32Array.from(valuesArray) })
);

Great ! Thank you very much. I get sometimes lost in vtk data models. A good example anyway for polydata creation (both lines and scalars).

image

Great!

If you can share your vertex function, I could update my codePen and have a real 3D model for it.

Here is the jsfiddle on reading a JSON (topojson mesh) and converting it to a vtk polydata lines structure : https://jsfiddle.net/PBrockmann/r57m6pun/

and the complete ongoing example with celldata, continents and graticules
https://jsfiddle.net/PBrockmann/m49jxgwf/

1 Like