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