TTK
Loading...
Searching...
No Matches
ContinuousScatterPlot.h
Go to the documentation of this file.
1
20
21#pragma once
22
23#include <array>
24#include <limits>
25
26// base code includes
27#include <Geometry.h>
28#include <Triangulation.h>
29
30namespace ttk {
31
32 class ContinuousScatterPlot : virtual public Debug {
33
34 public:
37
38 template <typename dataType1,
39 typename dataType2,
40 class triangulationType = AbstractTriangulation>
41 int execute(const dataType1 *,
42 const dataType2 *,
43 const triangulationType *) const;
44
45 inline int setVertexNumber(const SimplexId &vertexNumber) {
46 vertexNumber_ = vertexNumber;
47 return 0;
48 }
49
50 inline int setDummyValue(bool withDummyValue, double dummyValue) {
51 if(withDummyValue) {
52 withDummyValue_ = true;
53 dummyValue_ = dummyValue;
54 }
55 return 0;
56 }
57
58 inline int setResolutions(const SimplexId &resolutionX,
59 const SimplexId &resolutionY) {
60 resolutions_[0] = resolutionX;
61 resolutions_[1] = resolutionY;
62 return 0;
63 }
64
65 inline int setScalarMin(const std::array<double, 2> &scalarMin) {
66 scalarMin_ = scalarMin;
67 return 0;
68 }
69
70 inline int setScalarMax(const std::array<double, 2> &scalarMax) {
71 scalarMax_ = scalarMax;
72 return 0;
73 }
74
75 inline int setOutputDensity(std::vector<std::vector<double>> *density) {
76 density_ = density;
77 return 0;
78 }
79
80 inline int setOutputMask(std::vector<std::vector<char>> *mask) {
81 validPointMask_ = mask;
82 return 0;
83 }
84
85 protected:
90 std::array<double, 2> scalarMin_{0, 0};
91 std::array<double, 2> scalarMax_{0, 0};
92 std::vector<std::vector<double>> *density_;
93 std::vector<std::vector<char>> *validPointMask_;
94 };
95} // namespace ttk
96
97template <typename dataType1, typename dataType2, class triangulationType>
99 const dataType1 *scalars1,
100 const dataType2 *scalars2,
101 const triangulationType *triangulation) const {
102#ifndef TTK_ENABLE_KAMIKAZE
103 if(!scalars1)
104 return -1;
105 if(!scalars2)
106 return -2;
107 if(!triangulation)
108 return -3;
109 if(!density_)
110 return -4;
111
112 if(triangulation->getNumberOfCells() <= 0) {
113 this->printErr("no cells.");
114 return -5;
115 }
116
117 if(triangulation->getCellVertexNumber(0) != 4) {
118 this->printErr("no tetrahedra.");
119 return -6;
120 }
121#endif
122
123 Timer t;
124
125 // helpers:
126 const SimplexId numberOfCells = triangulation->getNumberOfCells();
127
128 // rendering helpers:
129 // constant ray direction (ortho)
130 const double d[3]{0, 0, -1};
131 const double delta[2]{
132 scalarMax_[0] - scalarMin_[0], scalarMax_[1] - scalarMin_[1]};
133 const double sampling[2]{
134 delta[0] / resolutions_[0], delta[1] / resolutions_[1]};
135 const double epsilon{0.000001};
136
137 std::vector<std::array<SimplexId, 3>> triangles{};
138
139#ifdef TTK_ENABLE_OPENMP
140#pragma omp parallel for num_threads(threadNumber_) firstprivate(triangles)
141#endif
142 for(SimplexId cell = 0; cell < numberOfCells; ++cell) {
143 bool isDummy{};
144
145 // get tetrahedron info
146 SimplexId vertex[4];
147 double data[4][3];
148 float position[4][3];
149 double localScalarMin[2]{};
150 double localScalarMax[2]{};
151 // for each triangle
152 for(int k = 0; k < 4; ++k) {
153 // get indices
154 triangulation->getCellVertex(cell, k, vertex[k]);
155
156 // get scalars
157 data[k][0] = scalars1[vertex[k]];
158 data[k][1] = scalars2[vertex[k]];
159 data[k][2] = 0;
160
162 and (data[k][0] == dummyValue_ or data[k][1] == dummyValue_)) {
163 isDummy = true;
164 break;
165 }
166
167 // get local stats
168 if(!k or localScalarMin[0] > data[k][0])
169 localScalarMin[0] = data[k][0];
170 if(!k or localScalarMin[1] > data[k][1])
171 localScalarMin[1] = data[k][1];
172 if(!k or localScalarMax[0] < data[k][0])
173 localScalarMax[0] = data[k][0];
174 if(!k or localScalarMax[1] < data[k][1])
175 localScalarMax[1] = data[k][1];
176
177 // get positions
178 triangulation->getVertexPoint(
179 vertex[k], position[k][0], position[k][1], position[k][2]);
180 }
181 if(isDummy)
182 continue;
183
184 // gradient:
185 double g0[3];
186 double g1[3];
187 {
188 double v12[3];
189 double v13[3];
190 double v14[3];
191 double s12[3];
192 double s13[3];
193 double s14[3];
194 for(int k = 0; k < 3; ++k) {
195 v12[k] = position[1][k] - position[0][k];
196 v13[k] = position[2][k] - position[0][k];
197 v14[k] = position[3][k] - position[0][k];
198
199 s12[k] = data[1][k] - data[0][k];
200 s13[k] = data[2][k] - data[0][k];
201 s14[k] = data[3][k] - data[0][k];
202 }
203
204 double a[3];
205 double b[3];
206 double c[3];
207 Geometry::crossProduct(v13, v12, a);
208 Geometry::crossProduct(v12, v14, b);
209 Geometry::crossProduct(v14, v13, c);
210 const double det = Geometry::dotProduct(v14, a);
211 if(det == 0.) {
212 for(int k = 0; k < 3; ++k) {
213 g0[k] = 0.0;
214 g1[k] = 0.0;
215 }
216 } else {
217 const double invDet = 1.0 / det;
218 for(int k = 0; k < 3; ++k) {
219 g0[k] = (s14[0] * a[k] + s13[0] * b[k] + s12[0] * c[k]) * invDet;
220 g1[k] = (s14[1] * a[k] + s13[1] * b[k] + s12[1] * c[k]) * invDet;
221 }
222 }
223 }
224
225 // volume:
226 double volume;
227 bool isLimit{};
228 {
229 double cp[3];
230 Geometry::crossProduct(g0, g1, cp);
231 volume = Geometry::magnitude(cp);
232 if(volume == 0.)
233 isLimit = true;
234 }
235
236 // Classify tetrahedron based on their projection in the data domain,
237 // following Shirley & Tuchman algorithm
238
239 // Class 0 tetras have either 1 or 3 visible faces, so geometrically one
240 // point is in the triangle made by the 3 other in the 2D data domain.
241 // Testing if one point is inside the triangle made by the others is
242 // equivalent to testing the quadrilateral convexity property. Thus, for
243 // class 0, we cannot find a convex quadrilateral in the 2D plane out of
244 // these 4 points. Using the signs of cross products along the Z axis of
245 // consecutive edge vectors, we can find whether or not a convex quad can be
246 // found.
247
248 int index[4]{0, 1, 2, 3};
249 bool isInTriangle{}; // True if the tetra is class 0
250
251 const bool zCrossProductsSigns[4]
252 = {(data[1][0] - data[0][0]) * (data[2][1] - data[0][1])
253 - (data[1][1] - data[0][1]) * (data[2][0] - data[0][0])
254 > 0,
255 (data[2][0] - data[1][0]) * (data[3][1] - data[1][1])
256 - (data[2][1] - data[1][1]) * (data[3][0] - data[1][0])
257 > 0,
258 (data[3][0] - data[2][0]) * (data[0][1] - data[2][1])
259 - (data[3][1] - data[2][1]) * (data[0][0] - data[2][0])
260 > 0,
261 (data[0][0] - data[3][0]) * (data[1][1] - data[3][1])
262 - (data[0][1] - data[3][1]) * (data[1][0] - data[3][0])
263 > 0};
264
265 // For class 0, the quad is not convex, which means
266 // all but one consecutive edge vector cross-product Z coordinate have the
267 // same sign.
268 if((zCrossProductsSigns[0] != zCrossProductsSigns[1])
269 != (zCrossProductsSigns[2] != zCrossProductsSigns[3])) {
270 isInTriangle = true;
271 if(zCrossProductsSigns[1] == zCrossProductsSigns[2]
272 && zCrossProductsSigns[2] == zCrossProductsSigns[3]) {
273 index[0] = 0;
274 index[1] = 2;
275 index[2] = 3;
276 index[3] = 1;
277 } else if(zCrossProductsSigns[0] == zCrossProductsSigns[2]
278 && zCrossProductsSigns[2] == zCrossProductsSigns[3]) {
279 index[0] = 0;
280 index[1] = 1;
281 index[2] = 3;
282 index[3] = 2;
283 } else if(zCrossProductsSigns[0] == zCrossProductsSigns[1]
284 && zCrossProductsSigns[1] == zCrossProductsSigns[2]) {
285 index[0] = 1;
286 index[1] = 2;
287 index[2] = 3;
288 index[3] = 0;
289 }
290 }
291
292 // projection:
293 double density{};
294 double imaginaryPosition[3]{0, 0, 0};
295 triangles.clear();
296 std::array<SimplexId, 3> triangle{};
297
298 // class 0 projection : 3 triangles
299 if(isInTriangle) {
300 // mass density
301 double massDensity{};
302 {
303 double fullArea{};
304
306 data[index[0]], data[index[1]], data[index[2]], fullArea);
307
308 double invArea{};
309 if(fullArea == 0.) {
310 invArea = 0.0;
311 isLimit = true;
312 } else {
313 invArea = 1.0 / fullArea;
314 }
315 double alpha, beta, gamma;
317 data[index[1]], data[index[2]], data[index[3]], alpha);
318
320 data[index[0]], data[index[2]], data[index[3]], beta);
321
323 data[index[0]], data[index[1]], data[index[3]], gamma);
324
325 alpha *= invArea;
326 beta *= invArea;
327 gamma *= invArea;
328
329 double centralPoint[3];
330 double interpolatedPoint[3]; // Coordinates of the point on the opposite
331 // face that has the same isovalue as the
332 // central point
333 for(int k = 0; k < 3; ++k) {
334 centralPoint[k] = position[index[3]][k];
335 interpolatedPoint[k] = alpha * position[index[0]][k]
336 + beta * position[index[1]][k]
337 + gamma * position[index[2]][k];
338 }
339 massDensity = Geometry::distance(centralPoint, interpolatedPoint);
340 }
341
342 if(isLimit)
343 density = std::numeric_limits<decltype(density)>::max();
344 else
345 density = massDensity / volume;
346
347 triangle[0] = vertex[index[3]];
348 triangle[1] = vertex[index[0]];
349 triangle[2] = vertex[index[1]];
350 triangles.push_back(triangle);
351
352 triangle[0] = vertex[index[3]];
353 triangle[1] = vertex[index[0]];
354 triangle[2] = vertex[index[2]];
355 triangles.push_back(triangle);
356
357 triangle[0] = vertex[index[3]];
358 triangle[1] = vertex[index[1]];
359 triangle[2] = vertex[index[2]];
360 triangles.push_back(triangle);
361 }
362 // class 1 projection : 4 triangles using an "imaginary point"
363 else {
364 double massDensity{};
365
366 // We know that a convex quad can be made out of the 4 points in the data
367 // domain Still using cross-product signs, we find a point order where the
368 // quad is not self-intersecting A non self-intersecting quad would have
369 // the same cross-product signs for all 4 consecutive edge pairs
370 if(zCrossProductsSigns[0] != zCrossProductsSigns[1]) {
371 index[0] = 0;
372 index[1] = 3;
373 index[2] = 1;
374 index[3] = 2;
376 data[0][0], data[0][1], data[3][0], data[3][1], data[1][0],
377 data[1][1], data[2][0], data[2][1], imaginaryPosition[0],
378 imaginaryPosition[1]);
379 } else if(zCrossProductsSigns[2] != zCrossProductsSigns[1]) {
380 index[0] = 0;
381 index[1] = 1;
382 index[2] = 2;
383 index[3] = 3;
385 data[0][0], data[0][1], data[1][0], data[1][1], data[2][0],
386 data[2][1], data[3][0], data[3][1], imaginaryPosition[0],
387 imaginaryPosition[1]);
388 } else {
389 index[0] = 0;
390 index[1] = 2;
391 index[2] = 1;
392 index[3] = 3;
394 data[0][0], data[0][1], data[2][0], data[2][1], data[1][0],
395 data[1][1], data[3][0], data[3][1], imaginaryPosition[0],
396 imaginaryPosition[1]);
397 }
398
399 double distanceToIntersection
400 = Geometry::distance(data[index[0]], imaginaryPosition);
401 double diagonalLength
402 = Geometry::distance(data[index[0]], data[index[1]]);
403 const double r0 = distanceToIntersection / diagonalLength;
404
405 distanceToIntersection
406 = Geometry::distance(data[index[2]], imaginaryPosition);
407 diagonalLength = Geometry::distance(data[index[2]], data[index[3]]);
408 const double r1 = distanceToIntersection / diagonalLength;
409
410 double p0[3];
411 double p1[3];
412 for(int k = 0; k < 3; ++k) {
413 p0[k] = position[index[0]][k]
414 + r0 * (position[index[1]][k] - position[index[0]][k]);
415
416 p1[k] = position[index[2]][k]
417 + r1 * (position[index[3]][k] - position[index[2]][k]);
418 }
419 massDensity = Geometry::distance(p0, p1);
420
421 if(isLimit)
422 density = std::numeric_limits<decltype(density)>::max();
423 else
424 density = massDensity / volume;
425
426 // four triangles projection
427 triangle[0] = -1; // new geometry
428 triangle[1] = vertex[index[0]];
429 triangle[2] = vertex[index[2]];
430 triangles.push_back(triangle);
431
432 triangle[1] = vertex[index[2]];
433 triangle[2] = vertex[index[1]];
434 triangles.push_back(triangle);
435
436 triangle[1] = vertex[index[1]];
437 triangle[2] = vertex[index[3]];
438 triangles.push_back(triangle);
439
440 triangle[1] = vertex[index[3]];
441 triangle[2] = vertex[index[0]];
442 triangles.push_back(triangle);
443 }
444
445 // rendering:
446 // "Fast, Minimum Storage Ray/Triangle Intersection", Tomas Moller & Ben
447 // Trumbore
448 {
449 const SimplexId minI
450 = floor((localScalarMin[0] - scalarMin_[0]) / sampling[0]);
451 const SimplexId minJ
452 = floor((localScalarMin[1] - scalarMin_[1]) / sampling[1]);
453 const SimplexId maxI
454 = ceil((localScalarMax[0] - scalarMin_[0]) / sampling[0]);
455 const SimplexId maxJ
456 = ceil((localScalarMax[1] - scalarMin_[1]) / sampling[1]);
457
458 for(SimplexId i = minI; i < maxI; ++i) {
459 for(SimplexId j = minJ; j < maxJ; ++j) {
460 // set ray origin
461 const double o[3]{scalarMin_[0] + i * sampling[0],
462 scalarMin_[1] + j * sampling[1], 1};
463 for(unsigned int k = 0; k < triangles.size(); ++k) {
464 const auto &tr = triangles[k];
465
466 // get triangle info
467 double p0[3];
468 if(isInTriangle) {
469 p0[0] = scalars1[tr[0]];
470 p0[1] = scalars2[tr[0]];
471 } else {
472 p0[0] = imaginaryPosition[0];
473 p0[1] = imaginaryPosition[1];
474 }
475 p0[2] = 0;
476
477 const double p1[3]{
478 (double)scalars1[tr[1]], (double)scalars2[tr[1]], 0};
479 const double p2[3]{
480 (double)scalars1[tr[2]], (double)scalars2[tr[2]], 0};
481 const double e1[3]{p1[0] - p0[0], p1[1] - p0[1], 0};
482 const double e2[3]{p2[0] - p0[0], p2[1] - p0[1], 0};
483
484 double q[3];
485 Geometry::crossProduct(d, e2, q);
486 const double a = Geometry::dotProduct(e1, q);
487 if(a > -epsilon and a < epsilon)
488 continue;
489
490 const double f = 1.0 / a;
491 const double s[3]{o[0] - p0[0], o[1] - p0[1], 1};
492 const double u = f * Geometry::dotProduct(s, q);
493 if(u < 0.0)
494 continue;
495
496 double r[3];
497 Geometry::crossProduct(s, e1, r);
498 const double v = f * Geometry::dotProduct(d, r);
499 if(v < 0.0 or (u + v) > 1.0)
500 continue;
501
502 // triangle/ray intersection below
503#ifdef TTK_ENABLE_OPENMP
504#pragma omp atomic update
505#endif
506 (*density_)[i][j] += (1.0 - u - v) * density;
507
508#ifdef TTK_ENABLE_OPENMP
509#pragma omp atomic write
510#endif
511 (*validPointMask_)[i][j] = 1;
512 break;
513 }
514 }
515 }
516 }
517 }
518
519 {
520 std::stringstream msg;
521 msg << "Processed " << numberOfCells << " tetrahedra";
522 this->printMsg(msg.str(), 1, t.getElapsedTime(), threadNumber_);
523 }
524
525 return 0;
526}
AbstractTriangulation is an interface class that defines an interface for efficient traversal methods...
TTK processing package that computes the continuous scatterplot of bivariate volumetric data.
int setDummyValue(bool withDummyValue, double dummyValue)
int setOutputDensity(std::vector< std::vector< double > > *density)
std::array< double, 2 > scalarMax_
int setOutputMask(std::vector< std::vector< char > > *mask)
int setResolutions(const SimplexId &resolutionX, const SimplexId &resolutionY)
int setVertexNumber(const SimplexId &vertexNumber)
std::vector< std::vector< char > > * validPointMask_
int setScalarMax(const std::array< double, 2 > &scalarMax)
int setScalarMin(const std::array< double, 2 > &scalarMin)
std::array< double, 2 > scalarMin_
std::vector< std::vector< double > > * density_
int execute(const dataType1 *, const dataType2 *, const triangulationType *) const
Minimalist debugging class.
Definition Debug.h:88
int printErr(const std::string &msg, const debug::LineMode &lineMode=debug::LineMode::NEW, std::ostream &stream=std::cerr) const
Definition Debug.h:149
double getElapsedTime()
Definition Timer.h:15
int computeTriangleArea(const T *p0, const T *p1, const T *p2, T &area)
Definition Geometry.cpp:281
T dotProduct(const T *vA0, const T *vA1, const T *vB0, const T *vB1)
Definition Geometry.cpp:388
bool computeSegmentIntersection(const T &xA, const T &yA, const T &xB, const T &yB, const T &xC, const T &yC, const T &xD, const T &yD, T &x, T &y)
Definition Geometry.cpp:248
int crossProduct(const T *vA0, const T *vA1, const T *vB0, const T *vB1, std::array< T, 3 > &crossProduct)
Definition Geometry.cpp:332
T magnitude(const T *v, const int &dimension=3)
Definition Geometry.cpp:509
T distance(const T *p0, const T *p1, const int &dimension=3)
Definition Geometry.cpp:362
The Topology ToolKit.
int SimplexId
Identifier type for simplices of any dimension.
Definition DataTypes.h:22
printMsg(debug::output::BOLD+" | | | | | . \\ | | (__| | / __/| |_| / __/|__ _|"+debug::output::ENDCOLOR, debug::Priority::PERFORMANCE, debug::LineMode::NEW, stream)