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...
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
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
TTK base package defining the standard types.
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)