[vtk-developers] Class design for spline visualizations

David Thompson david.thompson at kitware.com
Thu Jun 18 10:47:41 EDT 2015


Hi Lin,

> From some examples I saw, usually we put some points in vtkPoints, store the connectivity in cellArray and add them to a dataset (vtkPolyData, vtkStructuredGrid or vtkUnstructureGrid). So is that we store the control point of multiple patches in the vtkPoints where we replace the vtkDataArray with vtkMappedDataArray now? And in a vtkMappedDataArray, we store all the points for all patches together but we overwrite how to iterate the array.

That is nearly correct. We will accept

    vtkDataObject* ControlPointData;

from the user. For the case we will implement first, ControlPointData's actual type will be the vtkStructuredGrid subclass of vtkDataObject. The vtkStructuredGrid instance can hold many user-supplied vtkDataArray instances:

    vtkStructuredGrid* structuredControlPoints =
      vtkStructuredGrid::SafeDownCast(ControlPointData);

    // Control points to map from parameter-space to world coordinates:
    vtkDataArray* geometricControlPoints =
      structuredControlPoints->GetPoints()->GetData();

    // Control points to map from paramter-space to scalar fields:
    vtkDataSetAttributes* scalarFields = structuredControlPoints->GetPointData();
    vtkIdType numScalarFields = scalarFields->GetNumberOfArrays();
    for (vtkIdType i = 0; i < numScalarFields; ++i)
      { // scalarFieldControlPoints will hold things like temperature, pressure, etc.:
      vtkDataArray* scalarFieldControlPoints = scalarFields->GetArray(i);
      }

Because the user supplies these arrays, we cannot force them to be vtkMappedDataArray. Instead, we can make a vtkMappedDataArray subclass that owns a reference to one of the arrays above and only returns one patch's values from the user-supplied array (which holds the values for all patches in the spline).

The BezierPatchAdaptor below would return instances of our vtkMappedDataArray when its GetPatchPoints method is called. Now that I've written some more out, it looks like the API should be changed a little bit so that users can ask for a particular array (geometricControlPoints or scalarFieldControlPoints):

class vtkBezierPatchAdaptor : public vtkObject
{
public:
  virtual void SetControlPointData(vtkDataObject* controlPointData);
  vtkGetObjectMacro(vtkDataObject,ControlPointData);

  // Methods to iterate over patches:
  // ... same as below ...

  // Methods to access the current patch:
  // ...
  virtual vtkSmartPointer<vtkMappedArray> GetPatchPoints(int scalarField) const = 0;
  // ...
};

This way, GetPatchPoints() could return the vtkMappedDataArray for geometricControlPoints when the "int scalarField" argument is negative value and a vtkMappedDataArray for one of the scalarFieldControlPoints arrays when scalarField is positive.

Having GetPatchPoints() return a smart pointer to a vtkMappedDataArray subclass would also get around the issue that vtkPoints expects its vtkDataArray to have 3 components per tuple (where ours will have 4 for geometricControlPoints or an arbitrary number for scalarFieldControlPoints).

The vtkMappedDataArray instances would own a weak reference back the vtkBezierPatchAdaptor which created them and a weak reference to the geometricControlPoints or scalarFieldControlPoints array it draws values from. When vtkBezierPatchAdaptor::Next() or vtkBezierPatchAdaptor::GoToPatch() is called, the adaptor would iterate over all of the vtkMappedDataArray instances it owns and change an internal variable so they would return values for the proper patch. That way the vtkMappedDataArray instances only have to be created once for each spline dataset, not once per patch.

Is that clear enough, or should I sketch out a header file for our vtkMappedDataArray subclass?

	David

> On Wed, Jun 17, 2015 at 10:22 PM, David Thompson <david.thompson at kitware.com> wrote:
> Hi Lin,
> 
>> Sorry for late reply. Since the midterm is coming, I'll definitely speed up the development from now on.
> 
> We both need to do a little more work. I have been looking at how to read data from PetIGA so we can show some actual simulation data and have some 3-D volumetric examples.
> 
> It would be nice to have a patch ready to merge into VTK's master branch at the midterm that provides spline interpolation and tests it for all 3 parametric dimensions (curves, surfaces, and volumes). The means producing triangles and tetrahedra, not just polylines.
> 
>> I have a question about the class design in your last email. To my understanding, control points for one patch is stored in a vtkDataArray.
> 
> Close. I think that we should use the vtkMappedDataArray class to hold control points for multiple patches but program it to appear as if points for each individual patch are stored in patch-order.
> 
>> Do you mean store multiple vtkDataArray regarding to multiple patches in the vtkDataObject?
> 
> There would be one data array to hold point coordinates, but other arrays would hold simulation variables like temperature, pressure, velocity, and so on.
> 
>> If so, what class do I need to use for vtkDataObject?
> 
> The class outline below is an abstract class. A concrete implementation for NURBs would require   the base class's ControlPointData to be an instance of vtkStructuredGrid. A T-spline implementation might expect ControlPointData to be a vtkUnstructuredGrid. I think all we should do this summer is the NURBs case.
> 
>     David
> 
>> 
>> On Wed, Jun 10, 2015 at 2:39 PM, David Thompson <david.thompson at kitware.com> wrote:
>> Hi Lin,
>> 
>> > I have made it support 3D ellipse with function ...
>> 
>> Great!
>> 
>> > As you can see in the python test, I call this function 4 times for each quadrant to generate the entire ellipse. Since we need to support multiple patches, I think it's better to define a new dataset class for bezier patches as you mentioned before about  creating a new vtkControlPoints class that inherits from vtkPoints and allows for an extra coordinate.
>> 
>> Rather than create new dataset types, I would like to use existing datatypes to store the data and make an adaptor to iterate over them. That way existing pipelines could modify scalar values defined on control points (and perhaps even patches). For instance, B-splines could be represented as a vtkStructuredGrid whose FieldData contains a special knot array (special in the sense that the array must be of the proper size and with a fixed name like "_vtkKnotVector").
>> 
>> The adaptor would take a vtkDataObject as input and provide iterator-style access to each patch defined by the dataset. Specific subclasses of the adaptor would be B-splines (which expect the vtkDataObject to be a vtkStructuredGrid) or T-splines (which might expect a vtkUniformGridAMR) or even point-based splines (PB-splines as defined in Sederberg's T-spline paper, which could expect any dataset derived vtkPointSet).
>> 
>> class vtkBezierPatchAdaptor : public vtkObject
>> {
>> public:
>>   virtual void SetControlPointData(vtkDataObject* controlPointData);
>>   vtkGetObjectMacro(vtkDataObject,ControlPointData);
>> 
>>   // Methods to iterate over patches:
>>   virtual void Begin() = 0;
>>   virtual bool IsAtEnd() = 0;
>>   virtual bool GoToPatch(vtkIdType patchId) = 0; // random access iteration
>>   virtual bool Next() = 0;
>> 
>>   // Methods to access the current patch:
>>   virtual int GetPatchDimension() const = 0;
>>   virtual void GetPatchShape() const = 0;
>>     // returns VTK_TRIANGLE/VTK_QUAD when PatchDimension==2,
>>     // returns VTK_HEXAHEDRON/VTK_TETRA when PatchDimension==3
>>   virtual void GetPatchDegree(int* degreeOut) const = 0;
>>   virtual void GetPatchParameterRange(double* paramsOut) const = 0;
>>   virtual void GetPatchPoints(vtkPoints* pointsOut) const = 0;
>> 
>>   // Some helper methods that would make Python wrappings usable:
>>   int GetPatchDegree(int dim) const
>>     {
>>     std::vector<int> degree(this->GetPatchDimension());
>>     this->GetPatchDegree(&degree[0]);
>>     return degree[dim];
>>     }
>>   void GetPatchParameterRange(int dim, double paramRange[2])
>>     {
>>     std::vector<double> range(2 * this->GetPatchDimension());
>>     this->GetPatchDegree(&range[0]);
>>     paramRange[0] = [2 * dim];
>>     paramRange[1] = [2 * dim + 1];
>>     }
>> 
>> protected:
>>   vtkDataObject* ControlPointData;
>> };
>> 
>> The B-spline subclass could then iterate over patches by keeping a current "cell ID," which would be incremented by calling Next(). When asked for the *patch* points (not the input points), the adaptor would create a vtkMappedDataArray (see http://www.vtk.org/Wiki/VTK/InSituDataStructure for more information) that did not copy control point coordinates from its ControlPointData->GetPoints() but instead used the implicit connectivity to provide access to underlying B-spline points.
>> 
>> For example, consider the simple case of a rectangular B-spline with degree 1 (bi-linear). We might be given a 5x6 grid of control points as input and asked to iterate over all the patches. Each patch is simply a quadrilateral, and there are (5-1)*(6-1) = 20 of them.
>> 
>>   adaptor->Begin(); // would initialize an internal "cell ID" to 0
>>   adaptor->IsAtEnd(); // would return false for "cell ID" values in [0,19] and true otherwise.
>>   adaptor->GetPatchDimension(); // would return 2
>>   adaptor->GetPatchShape(); // would return VTK_QUAD
>>   adaptor->GetPatchDegree(degreeOut); // would populate degreeOut with [1,1]
>>   adaptor->GetPatchPoints(pointsOut); // would populate pointsOut with a vtkMappedDataArray.
>> 
>> The vtkMappedDataArray would report having 4 tuples (one for each corner of the quad... when the degree is higher, is would report the product of (degreeOut[i]+1) for all i in GetPatchDimension()). The actual points reported would depend on the "cell ID" in the adaptor.
>> 
>> I have not figured out yet how the "w" homogeneous coordinate would work with vtkPoints. A different adaptor API might work better (keeping the "w" coordinate separate from the other points).
>> 
>> > Another question is that currently the domain of parameter coordinate is [0, 1] for each patch. Do you think it's better to make the domain continuously, namely [0, 0.25] for the first quadrant, [0.25, 0.75] for the second quadrant, etc?
>> 
>> If we focus on the B-spline adaptor above, we get that for free since the returned knot vector could be normalized to the range [0,1].
>> 
>>         David
>> 
> 



More information about the vtk-developers mailing list