You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

228 lines
12 KiB
C++

/*=========================================================================
Program: Visualization Toolkit
Module: vtkPolyDataAlgorithm.h
Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
All rights reserved.
See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notice for more information.
=========================================================================*/
/**
* @class vtkContinuousScatterplot
* @brief Given a 3D domain space represented by an
* unstructured grid composed of tetrahedral cells with bivariate fields, this filter
* tessellates each cell in the domain to polyhedral fragments by intersecting the
* projection of the cell into 2-D range space against two sets of cutting planes, one set
* is defined along the first field, the second set is defined along the second field. The
* volume of these subdivided polyhedral fragments can be computed and aggregated over
* cells to depict the density distribution of the data projection in the bivariate range
* space.
*
* @section Introduction
* Given a bivariate field (f1,f2) defined on an unstructured grid which is
* composed of tetrahedral cells, we can initially subdivide each cell based on its
* projection in the range into a number of fragments along the first field f1, we refer
* to these polyhedral fragments as Frag(f1) = {frag(f1)_1, frag(f1)_2, ... , frag(f1)_n},
* where frag(f1)_n refers to the nth fragment along the first field subdivision. Each
* fragment has a range value and the value difference between the neighbouring fragments
* is represented as fragment width fw_f1, which is uniformly distributed across the
* range.
* Based on the structure of Frag(f1), for each of its cell "frag(f1)_n", we
* can further subdivide this cell based on the second field f2 using fragment width
* fw_f2. The tessellation along the second field results in an even finer fragment
* collection which we refer to as Frag(f1,f2) = {frag(f1,f2)_1, frag(f1,f2)_2, ... ,
* frag(f1,f2)_m}. We can observe that Frag(f1,f2) is a finer tessellation of the domain
* than Frag(f1) and will be used to compute the density distribution in the bivariate
* range space. The algorithm for fragment computation is similar to the first stage of
* the work in [0].
* Each fragment "s" in Frag(f1,f2) has range values (f1(s), f2(s)) in the bivariate
* fields. These values can be further mapped to a 2-D bin with a resolution rexX * resY.
* The mapped bin index (binIndexX, binIndexY) of the fragment can be computed by linear
* interpolation on its range values :
* binIndexX = (int) resX * (f1(s) - f1_min) / (f1_max - f1_min)
* binIndexY = (int) resY * (f2(s) - f2_min) / (f2_max - f2_min),
* where (f1_min, f1_max) is the range in first field.
* Once we know which bin a fragment coincides, the density value in each bin equals to
* the total geometric volume of the fragments in this bin. This volume distribution
* over the bins will be exported as a point data array in the output data structure.
* If we map this 2-D bin to a 2-D image with each bin corresponding to a pixel and
* bin density to pixel transparency, then the image can be displayed as a continuous
* scatterplot.
* @section Algorithm
* The algorithm of this filter can be described as:
* Require: R.1 The domain space is an unstructured grid data set composed of
* tetrahedral cells;
* R.2 The range space contains two scalar fields, say f1 and f2.
*
* The most important step is to compute the fragments. The implementation processes
* the input grid one cell at a time, explicitly computing the intersection of the cell
* with the cutting planes defined by the fragment boundaries in each scalar field.
* In order to subdivide the cell, we need to define a list of cutting planes in each
* field. The interval between neighbouring cutting planes is related to the output 2-D
* bin resolution (resX, resY) and can be computed as :
* fw_f1 = (f1_max - f1_min) / resX
* fw_f2 = (f2_max - f2_min) / resY,
* where (f1_max,f1_min) is the scalar range of first field.
*
* 1. For each tetrahedron T in the input grid:
*
* 1.1 Subdivide the cell T based on the first field f1, we will obtain a list
* of fragments: Frag(f1) = {frag(f1)_1, frag(f1)_2, ... , frag(f1)_n}. The
* steps for subdivision can be described as:
*
* 1.1.1 For each cutting plane s with respect to the first field f1,
* its field value f1(s) = f1_min + n * fw_f1, where n refers to the n-th
* cutting plane:
*
* 1.1.2. Traverse each edge e starting from point a to b in the cell, we
* will maintain three data classes, namely fragmentFace,
* residualFace and cutSet:
* A. fragmentFace contains vertices in the current fragment.
* B. cutSet contains vertices whose range values equal to f1(s).
* This set contains the current cutting plane.
* C. residualFace contains the rest of the vertices in the cell.
* In order to classify edge vertices into these classes, the
* following case table is used for each vertex "a" :
* case 0 : f1(a)------ f1(s) ------f1(b)
* condition: f1(a) < f1(s) , f1(b) > f1(s)
* class: p(s,e), a -> fragmentFace
* p(s,e) -> cutSet
* p(s,e) -> residualFace
*
* case 1 : f1(b)------ f1(s) ------f1(a)
* condition: f1(a) > f1(s) , f1(b) < f1(s)
* class: p(s,e) -> fragmentFace
* p(s,e) -> cutSet
* a -> residualFace
*
* case 2 : f1(s),f1(a)-------------------f1(b)
* condition: f1(s) == f1(a), f1(s) <= f1(b)
* class: a -> fragmentFace
* a -> residualFace
* a -> cutSet
*
* case 3 : f1(a)-------------------f1(b), f1(s)
* condition: f1(s) > f1(a), f1(s) == f1(b)
* class: a -> fragmentFace
*
* case 4 : f1(s),f1(b)-------------------f1(a)
* condition: f1(s) < f1(a), f1(s) == f1(b)
* class: a -> residualFace
* Remark: 1. we use "->" to indicate "belongs to" relation.
* 2. p(s,e) refers to the interpolated point of range value
* f1(s) on the edge e.
*
* 1.1.3. After we have traversed every edge in a cell for the cutting plane
* s, three classes for storing fragment, cutting plane and residual
* faces are updated. The faces of the current fragment frag(f1)
* are the union of all elements in fragmentFace and cutSet.
*
* 1.2 Take the output of step 1.1, traverse each fragment in Frag(f1), define a list
* of cutting planes with respect to field f2, further subdivide the fragments in
* Frag(f1) following steps from 1.1.2 to 1.1.3. The output of this step will be
* the fragment collection Frag(f1,f2). Each fragment in Frag(f1,f2) can be further
* mapped to a 2-D bin based on its range values. The density value in each bin
* equals to the total geometric volume of the fragments in this bin. This volume
* distribution over the bins will be exported as a point data array in the output
* data structure.
*
* @section VTK Filter Design
* The input and output ports of the filter:
* Input port : the input data set should be a vtkUnstructuredGrid, with each of its
* cell defined as a tetrahedron. At least two scalar fields are
* associated with the data. The user needs to specify the name of the
* two scalar arrays beforehand.
* Output port: the output data set is a 2D image stored as a vtkImageData.
* The resolution of the output image can be set by the user.
* The volume distribution of fragments in each pixel or bin
* is stored in an point data array named "volume" in the output
* vtkImageData.
*
* @section How To Use This Filter
* Suppose we have a tetrahedral mesh stored in a vtkUnstructuredGrid, we call this
* data set "inputData". This data set has two scalar arrays whose names are "f1"
* and "f2" respectively. We would like the resolution of output image set to (resX,resY).
* Given these input, this filter can be called as follows in c++ sample code:
*
* vtkSmartPointer<vtkContinuousScatterplot> csp =
* vtkSmartPointer<vtkContinuousScatterplot>::New();
* csp->SetInputData(inputData);
* csp->SetField1("f1",resX);
* csp->SetField2("f2",resY);
* csp->Update();
*
* Then the output, "csp->GetOutput()", will be a vtkImageData containing a scalar
* array whose name is "volume". This array contains the volume distribution of the
* fragments.
*
* [0] H.Carr and D.Duke, Joint contour nets: Topological analysis of multivariate data.
* IEEE Transactions on Visualization and Computer Graphics, volume 20,
* issue 08, pages 1100-1113, 2014
*/
#ifndef vtkContinuousScatterplot_h
#define vtkContinuousScatterplot_h
#include "vtkImageAlgorithm.h"
#include "vtkInfovisCoreModule.h" // For export macro
class VTKINFOVISCORE_EXPORT vtkContinuousScatterplot : public vtkImageAlgorithm
{
public:
static vtkContinuousScatterplot* New();
vtkTypeMacro(vtkContinuousScatterplot, vtkImageAlgorithm);
void PrintSelf(ostream& os, vtkIndent indent) override;
/**
* Get the tolerance used when comparing floating point numbers for equality.
*/
vtkGetMacro(Epsilon, double);
/**
* Set the tolerance used when comparing floating point numbers for equality.
*/
vtkSetMacro(Epsilon, double);
/**
* Specify the name of the first field to be used in subdividing the dataset.
* Specify the resolution along x axis of the output image.
*/
void SetField1(const char* fieldName, vtkIdType ResX);
/**
* Specify the name of the second field to be used in subdividing the dataset.
* Specify the resolution along y axis of the output image.
*/
void SetField2(const char* fieldName, vtkIdType ResY);
protected:
vtkContinuousScatterplot();
// Configure input port to accept only vtkUnstructuredGrid.
int FillInputPortInformation(int port, vtkInformation* info) override;
// Configure out port to be a vtkImageData data set.
int FillOutputPortInformation(int port, vtkInformation* info) override;
int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override;
// Set the tolerance used when comparing floating numbers for equality.
double Epsilon;
// Names of the scalar fields to be used in the filter.
const char* Fields[2];
// Resolution of the output image.
vtkIdType ResX, ResY;
private:
vtkContinuousScatterplot(const vtkContinuousScatterplot&) = delete;
void operator=(const vtkContinuousScatterplot&) = delete;
};
#endif