Mesh Oriented datABase  (version 5.6.0)
An array-based unstructured mesh library
IntxUtils.cpp
Go to the documentation of this file.
1 /*
2  * IntxUtils.cpp
3  *
4  * Created on: Oct 3, 2012
5  */
6 #if defined( _MSC_VER ) || defined( WIN32 ) /* windows */
7 #define _USE_MATH_DEFINES // For M_PI
8 #endif
9 
10 #include <cmath>
11 #include <cassert>
12 #include <algorithm>
13 #include <string>
14 #include <iostream>
15 #include <iomanip>
16 #include <limits>
17 
19 
20 #include "moab/MergeMesh.hpp"
21 #include "moab/ReadUtilIface.hpp"
22 #include "MBTagConventions.hpp"
23 //
24 // CHECKNEGATIVEAREA gates positive_orientation()'s per-cell "nonconvex problem"
25 // detail. That diagnostic fires on any locally concave sub-triangle of a valid,
26 // correctly oriented cell, so on a fine mesh it is noise, not a defect signal
27 // (see the summary line in positive_orientation() for the always-on version).
28 // area_spherical_element()'s "negative area" report is not gated: a negative
29 // whole-cell area is always a genuine inversion, rare, and worth surfacing
30 // unconditionally.
31 // #ifdef CHECKNEGATIVEAREA
32 //
33 #include <queue>
34 #include <map>
35 
36 #ifdef MOAB_HAVE_TEMPESTREMAP
37 #include "GridElements.h"
38 #endif
39 
40 #ifdef MOAB_HAVE_EIGEN3
41 #define EIGEN_NO_DEBUG
42 #include "Eigen/Dense"
43 #endif
44 
45 namespace moab
46 {
47 /**
48  * This code defines several utility functions for computing edge intersections and performing geometric operations.
49  *
50  * - `borderPointsOfXinY2`: Computes the border points of a set of points `X` inside another set of points `Y`.
51  * - `SortAndRemoveDoubles2`: Sorts a set of points `P` according to their angles and removes duplicate points.
52  * - `EdgeIntersections2`: Computes the intersections between the edges of two sets of points `blue` and `red`.
53  * - `EdgeIntxRllCs`: Computes the intersections between the edges of a set of points `blue` and a set of points `red` on a specific plane.
54  *
55  * The code also defines some helper structs and functions used by these utility functions.
56  */
57 
58 /**
59  * Computes the border points of X in Y2.
60  *
61  * @param X The array of points representing X.
62  * @param nX The number of points in X.
63  * @param Y The array of points representing Y.
64  * @param nY The number of points in Y.
65  * @param P The array to store the border points of X in Y2.
66  * @param side The array to store the side information for each point in X.
67  * @param epsilon_area The epsilon value for area comparison.
68  * @return The number of extra points found.
69  */
70 int IntxUtils::borderPointsOfXinY2( double* X, int nX, double* Y, int nY, double* P, int* side, double epsilon_area )
71 {
72  // 2 triangles, 3 corners, is the corner of X in Y?
73  // Y must have a positive area
74  /*
75  */
76  int extraPoint = 0;
77  for( int i = 0; i < nX; i++ )
78  {
79  // compute double the area of all nY triangles formed by a side of Y and a corner of X; if
80  // one is negative, stop (negative means it is outside; X and Y are all oriented such that
81  // they are positive oriented;
82  // if one area is negative, it means it is outside the convex region, for sure)
83  double* A = X + 2 * i;
84 
85  int inside = 1;
86  for( int j = 0; j < nY; j++ )
87  {
88  const double* B = Y + 2 * j;
89  int j1 = ( j + 1 ) % nY;
90  const double* C = Y + 2 * j1; // no copy of data
91 
92  double area2 = ( B[0] - A[0] ) * ( C[1] - A[1] ) - ( C[0] - A[0] ) * ( B[1] - A[1] );
93  if( area2 < -epsilon_area )
94  {
95  inside = 0;
96  break;
97  }
98  }
99  if( inside )
100  {
101  side[i] = 1; // so vertex i of X is inside the convex region formed by Y
102  // so side has nX dimension (first array)
103  P[extraPoint * 2] = A[0];
104  P[extraPoint * 2 + 1] = A[1];
105  extraPoint++;
106  }
107  }
108  return extraPoint;
109 }
110 
111 // used to order according to angle, so it can remove double easily
113 {
114  double angle;
115  int index;
116 };
117 
119 {
120  return lhs.angle < rhs.angle;
121 }
122 
123 /**
124  * Sorts and removes duplicate points in the given array.
125  *
126  * Note: nP might be modified too, we will remove duplicates if found
127  *
128  * @param P The array of points to be sorted and checked for duplicates.
129  * @param nP The number of points in P.
130  * @param epsilon_1 The epsilon value for distance comparison.
131  * @return 0 if successful.
132  */
133 int IntxUtils::SortAndRemoveDoubles2( double* P, int& nP, double epsilon_1 )
134 {
135  if( nP < 2 ) return 0; // nothing to do
136 
137  // center of gravity for the points
138  double c[2] = { 0., 0. };
139  int k = 0;
140  for( k = 0; k < nP; k++ )
141  {
142  c[0] += P[2 * k];
143  c[1] += P[2 * k + 1];
144  }
145  c[0] /= nP;
146  c[1] /= nP;
147 
148  // how many? we dimensioned P at MAXEDGES*10; so we imply we could have at most 5*MAXEDGES
149  // intersection points
150  struct angleAndIndex pairAngleIndex[5 * MAXEDGES];
151 
152  for( k = 0; k < nP; k++ )
153  {
154  double x = P[2 * k] - c[0], y = P[2 * k + 1] - c[1];
155  if( x != 0. || y != 0. )
156  {
157  pairAngleIndex[k].angle = atan2( y, x );
158  }
159  else
160  {
161  pairAngleIndex[k].angle = 0;
162  // it would mean that the cells are touching at a vertex
163  }
164  pairAngleIndex[k].index = k;
165  }
166 
167  // this should be faster than the bubble sort we had before
168  std::sort( pairAngleIndex, pairAngleIndex + nP, angleCompare );
169  // copy now to a new double array
170  double PCopy[10 * MAXEDGES]; // the same dimension as P; very conservative, but faster than
171  // reallocate for a vector
172  for( k = 0; k < nP; k++ ) // index will show where it should go now;
173  {
174  int ck = pairAngleIndex[k].index;
175  PCopy[2 * k] = P[2 * ck];
176  PCopy[2 * k + 1] = P[2 * ck + 1];
177  }
178  // now copy from PCopy over original P location
179  std::copy( PCopy, PCopy + 2 * nP, P );
180 
181  // eliminate duplicates, finally
182 
183  int i = 0, j = 1; // the next one; j may advance faster than i
184  // check the unit
185  // double epsilon_1 = 1.e-5; // these are cm; 2 points are the same if the distance is less
186  // than 1.e-5 cm
187  while( j < nP )
188  {
189  double d2 = dist2( &P[2 * i], &P[2 * j] );
190  if( d2 > epsilon_1 )
191  {
192  i++;
193  P[2 * i] = P[2 * j];
194  P[2 * i + 1] = P[2 * j + 1];
195  }
196  j++;
197  }
198  // test also the last point with the first one (index 0)
199  // the first one could be at -PI; last one could be at +PI, according to atan2 span
200 
201  double d2 = dist2( P, &P[2 * i] ); // check the first and last points (ordered from -pi to +pi)
202  if( d2 > epsilon_1 )
203  {
204  nP = i + 1;
205  }
206  else
207  nP = i; // effectively delete the last point (that would have been the same with first)
208  if( nP == 0 ) nP = 1; // we should be left with at least one point we already tested if nP is 0 originally
209  return 0;
210 }
211 
212 /**
213  * Computes the edge intersections of two elements.
214  *
215  * @param blue The array of points representing the blue element.
216  * @param nsBlue The number of points in the blue element.
217  * @param red The array of points representing the red element.
218  * @param nsRed The number of points in the red element.
219  * @param markb The array to mark the intersecting edges of the blue element.
220  * @param markr The array to mark the intersecting edges of the red element.
221  * @param points The array to store the intersection points.
222  * @param nPoints The number of intersection points found.
223  * @return The error code.
224  */
226  int nsBlue,
227  double* red,
228  int nsRed,
229  int* markb,
230  int* markr,
231  double* points,
232  int& nPoints )
233 {
234  /* EDGEINTERSECTIONS computes edge intersections of two elements
235  [P,n]=EdgeIntersections(X,Y) computes for the two given elements * red
236  and blue ( stored column wise )
237  (point coordinates are stored column-wise, in counter clock
238  order) the points P where their edges intersect. In addition,
239  in n the indices of which neighbors of red are also intersecting
240  with blue are given.
241  */
242 
243  // points is an array with enough slots (24 * 2 doubles)
244  nPoints = 0;
245  for( int i = 0; i < MAXEDGES; i++ )
246  {
247  markb[i] = markr[i] = 0;
248  }
249 
250  for( int i = 0; i < nsBlue; i++ )
251  {
252  for( int j = 0; j < nsRed; j++ )
253  {
254  double b[2];
255  double a[2][2]; // 2*2
256  int iPlus1 = ( i + 1 ) % nsBlue;
257  int jPlus1 = ( j + 1 ) % nsRed;
258  for( int k = 0; k < 2; k++ )
259  {
260  b[k] = red[2 * j + k] - blue[2 * i + k];
261  // row k of a: a(k, 0), a(k, 1)
262  a[k][0] = blue[2 * iPlus1 + k] - blue[2 * i + k];
263  a[k][1] = red[2 * j + k] - red[2 * jPlus1 + k];
264  }
265  double delta = a[0][0] * a[1][1] - a[0][1] * a[1][0];
266  if( fabs( delta ) > 1.e-14 ) // this is close to machine epsilon
267  {
268  // not parallel
269  double alfa = ( b[0] * a[1][1] - a[0][1] * b[1] ) / delta;
270  double beta = ( -b[0] * a[1][0] + b[1] * a[0][0] ) / delta;
271  if( 0 <= alfa && alfa <= 1. && 0 <= beta && beta <= 1. )
272  {
273  // the intersection is good
274  for( int k = 0; k < 2; k++ )
275  {
276  points[2 * nPoints + k] = blue[2 * i + k] + alfa * ( blue[2 * iPlus1 + k] - blue[2 * i + k] );
277  }
278  markb[i] = 1; // so neighbor number i of blue will be considered too.
279  markr[j] = 1; // this will be used in advancing red around blue quad
280  nPoints++;
281  }
282  }
283  // the case delta ~ 0. will be considered by the interior points logic
284  }
285  }
286  return MB_SUCCESS;
287 }
288 
289 /**
290  * Computes the edge intersections between a RLL and CS quad.
291  *
292  * Note: Special function.
293  *
294  * @param blue The array of points representing the blue element.
295  * @param bluec The array of Cartesian coordinates for the blue element.
296  * @param blueEdgeType The array of edge types for the blue element.
297  * @param nsBlue The number of points in the blue element.
298  * @param red The array of points representing the red element.
299  * @param redc The array of Cartesian coordinates for the red element.
300  * @param nsRed The number of points in the red element.
301  * @param markb The array to mark the intersecting edges of the blue element.
302  * @param markr The array to mark the intersecting edges of the red element.
303  * @param plane The plane of intersection.
304  * @param R The radius of the sphere.
305  * @param points The array to store the intersection points.
306  * @param nPoints The number of intersection points found.
307  * @return The error code.
308  */
310  CartVect* bluec,
311  int* blueEdgeType,
312  int nsBlue,
313  double* red,
314  CartVect* redc,
315  int nsRed,
316  int* markb,
317  int* markr,
318  int plane,
319  double R,
320  double* points,
321  int& nPoints )
322 {
323  // if blue edge type is 1, intersect in 3d then project to 2d by gnomonic projection
324  // everything else the same (except if there are 2 points resulting, which is rare)
325  for( int i = 0; i < 4; i++ )
326  { // always at most 4 , so maybe don't bother
327  markb[i] = markr[i] = 0;
328  }
329 
330  for( int i = 0; i < nsBlue; i++ )
331  {
332  int iPlus1 = ( i + 1 ) % nsBlue;
333  if( blueEdgeType[i] == 0 ) // old style, just 2d
334  {
335  for( int j = 0; j < nsRed; j++ )
336  {
337  double b[2];
338  double a[2][2]; // 2*2
339 
340  int jPlus1 = ( j + 1 ) % nsRed;
341  for( int k = 0; k < 2; k++ )
342  {
343  b[k] = red[2 * j + k] - blue[2 * i + k];
344  // row k of a: a(k, 0), a(k, 1)
345  a[k][0] = blue[2 * iPlus1 + k] - blue[2 * i + k];
346  a[k][1] = red[2 * j + k] - red[2 * jPlus1 + k];
347  }
348  double delta = a[0][0] * a[1][1] - a[0][1] * a[1][0];
349  if( fabs( delta ) > 1.e-14 )
350  {
351  // not parallel
352  double alfa = ( b[0] * a[1][1] - a[0][1] * b[1] ) / delta;
353  double beta = ( -b[0] * a[1][0] + b[1] * a[0][0] ) / delta;
354  if( 0 <= alfa && alfa <= 1. && 0 <= beta && beta <= 1. )
355  {
356  // the intersection is good
357  for( int k = 0; k < 2; k++ )
358  {
359  points[2 * nPoints + k] =
360  blue[2 * i + k] + alfa * ( blue[2 * iPlus1 + k] - blue[2 * i + k] );
361  }
362  markb[i] = 1; // so neighbor number i of blue will be considered too.
363  markr[j] = 1; // this will be used in advancing red around blue quad
364  nPoints++;
365  }
366  } // if the edges are too "parallel", skip them
367  }
368  }
369  else // edge type is 1, so use 3d intersection
370  {
371  CartVect& C = bluec[i];
372  CartVect& D = bluec[iPlus1];
373  for( int j = 0; j < nsRed; j++ )
374  {
375  int jPlus1 = ( j + 1 ) % nsRed; // nsRed is just 4, forget about it, usually
376  CartVect& A = redc[j];
377  CartVect& B = redc[jPlus1];
378  int np = 0;
379  double E[9];
380  intersect_great_circle_arc_with_clat_arc( A.array(), B.array(), C.array(), D.array(), R, E, np );
381  if( np == 0 ) continue;
382  if( np >= 2 )
383  {
384  std::cout << "intersection with 2 points :" << A << B << C << D << "\n";
385  }
386  for( int k = 0; k < np; k++ )
387  {
388  gnomonic_projection( CartVect( E + k * 3 ), R, plane, points[2 * nPoints],
389  points[2 * nPoints + 1] );
390  nPoints++;
391  }
392  markb[i] = 1; // so neighbor number i of blue will be considered too.
393  markr[j] = 1; // this will be used in advancing red around blue quad
394  }
395  }
396  }
397  return MB_SUCCESS;
398 }
399 
400 // vec utils related to gnomonic projection on a sphere
401 
402 // vec utils
403 
404 /*
405  *
406  * position on a sphere of radius R
407  * if plane specified, use it; if not, return the plane, and the point in the plane
408  * there are 6 planes, numbered 1 to 6
409  * plane 1: x=R, plane 2: y=R, 3: x=-R, 4: y=-R, 5: z=-R, 6: z=R
410  *
411  * projection on the plane will preserve the orientation, such that a triangle, quad pointing
412  * outside the sphere will have a positive orientation in the projection plane
413  */
414 void IntxUtils::decide_gnomonic_plane( const CartVect& pos, int& plane )
415 {
416  // decide plane, based on max x, y, z
417  if( fabs( pos[0] ) < fabs( pos[1] ) )
418  {
419  if( fabs( pos[2] ) < fabs( pos[1] ) )
420  {
421  // pos[1] is biggest
422  if( pos[1] > 0 )
423  plane = 2;
424  else
425  plane = 4;
426  }
427  else
428  {
429  // pos[2] is biggest
430  if( pos[2] < 0 )
431  plane = 5;
432  else
433  plane = 6;
434  }
435  }
436  else
437  {
438  if( fabs( pos[2] ) < fabs( pos[0] ) )
439  {
440  // pos[0] is the greatest
441  if( pos[0] > 0 )
442  plane = 1;
443  else
444  plane = 3;
445  }
446  else
447  {
448  // pos[2] is biggest
449  if( pos[2] < 0 )
450  plane = 5;
451  else
452  plane = 6;
453  }
454  }
455  return;
456 }
457 
459 {
460 
461  double d = P.length();
462  if( d == 0.0 )
463  {
464  MB_CHK_SET_ERR( MB_FAILURE, "point P is at the origin" );
465  }
466  double x = P[0];
467  double y = P[1];
468  double z = P[2];
469  // easy cases
470  if( x == 0.0 && y == 0.0 )
471  {
472  if( z > 0. )
473  {
474  u = CartVect( 1., 0., 0. );
475  v = CartVect( 0., 1., 0. ); // gnomonic plane 6
476  }
477  else
478  {
479  u = CartVect( 0., 1., 0. );
480  v = CartVect( 1., 0., 0. ); // gnomonic plane 5
481  }
482  return MB_SUCCESS;
483  }
484  if( x == 0.0 && z == 0.0 )
485  {
486  if( y > 0. )
487  {
488  u = CartVect( -1., 0., 0. );
489  v = CartVect( 0., 0., 1. ); // gnomonic plane 2
490  }
491  else
492  {
493  u = CartVect( 0., 0., 1. );
494  v = CartVect( -1., 0., 0. ); // gnomonic plane 4
495  }
496  return MB_SUCCESS;
497  }
498  if( z == 0.0 && y == 0.0 )
499  {
500  if( x > 0. )
501  {
502  u = CartVect( 0., 1., 0. );
503  v = CartVect( 0., 0., 1. ); // gnomonic plane 1
504  }
505  else
506  {
507  u = CartVect( 0., 0., 1. );
508  v = CartVect( 0., 1., 0. ); // gnomonic plane 3
509  }
510  return MB_SUCCESS;
511  }
512  int plane;
514  if( 1 == plane ) // towards x > 0
515  {
516  u = CartVect( 1., 0., 0. ) * P;
517  }
518 
519  if( 2 == plane ) // towards y > 0
520  {
521  u = CartVect( 0., 1., 0. ) * P;
522  }
523  if( 3 == plane ) // towards x < 0
524  {
525  u = CartVect( -1., 0., 0. ) * P;
526  }
527  if( 4 == plane ) // towards y < 0
528  {
529  u = CartVect( 0., -1., 0. ) * P;
530  }
531  if( 5 == plane ) // towards z < 0
532  {
533  u = CartVect( 0., 0., -1. ) * P;
534  }
535  if( 6 == plane ) // towards z > 0
536  {
537  u = CartVect( 0., 0., 1. ) * P;
538  }
539  v = P * u;
540  u.normalize();
541  v.normalize();
542 
543  return MB_SUCCESS;
544 }
545 
547  const CartVect axis[3],
548  double& c1,
549  double& c2 )
550 {
551  double ang = angle( pos, axis[0] );
552  if( ang > 1.57 ) // pi/2 do not project if very close to hemisphere
553  return MB_FAILURE;
554  // solve the equation in plane, (alfa * pos - axis[0]) % axis[0] = 0.0
555  double alpha = axis[0] % axis[0] / ( pos % axis[0] ); // we know this denominator is greater than 0
556  CartVect planeVect = alpha * pos - axis[0]; // axis[0] is P
557  c1 = planeVect % axis[1];
558  c2 = planeVect % axis[2];
559  return MB_SUCCESS;
560 }
561 
562 // point on a sphere is projected on one of six planes, decided earlier
563 ErrorCode IntxUtils::gnomonic_projection( const CartVect& pos, double R, int plane, double& c1, double& c2 )
564 {
565  double alfa = 1.; // the new point will be on line alfa*pos
566 
567  switch( plane )
568  {
569  case 1: {
570  // the plane with x = R; x>y, x>z
571  // c1->y, c2->z
572  alfa = R / pos[0];
573  c1 = alfa * pos[1];
574  c2 = alfa * pos[2];
575  break;
576  }
577  case 2: {
578  // y = R -> zx
579  alfa = R / pos[1];
580  c1 = alfa * pos[2];
581  c2 = alfa * pos[0];
582  break;
583  }
584  case 3: {
585  // x=-R, -> yz
586  alfa = -R / pos[0];
587  c1 = -alfa * pos[1]; // the sign is to preserve orientation
588  c2 = alfa * pos[2];
589  break;
590  }
591  case 4: {
592  // y = -R
593  alfa = -R / pos[1];
594  c1 = -alfa * pos[2]; // the sign is to preserve orientation
595  c2 = alfa * pos[0];
596  break;
597  }
598  case 5: {
599  // z = -R
600  alfa = -R / pos[2];
601  c1 = -alfa * pos[0]; // the sign is to preserve orientation
602  c2 = alfa * pos[1];
603  break;
604  }
605  case 6: {
606  alfa = R / pos[2];
607  c1 = alfa * pos[0];
608  c2 = alfa * pos[1];
609  break;
610  }
611  default:
612  return MB_FAILURE; // error
613  }
614 
615  return MB_SUCCESS; // no error
616 }
617 
618 // given the position on plane (one out of 6), find out the position on sphere
620  const double& c2,
621  double R,
622  int plane,
623  CartVect& pos )
624 {
625 
626  // the new point will be on line beta*pos
627  double len = sqrt( c1 * c1 + c2 * c2 + R * R );
628  double beta = R / len; // it is less than 1, in general
629 
630  switch( plane )
631  {
632  case 1: {
633  // the plane with x = R; x>y, x>z
634  // c1->y, c2->z
635  pos[0] = beta * R;
636  pos[1] = c1 * beta;
637  pos[2] = c2 * beta;
638  break;
639  }
640  case 2: {
641  // y = R -> zx
642  pos[1] = R * beta;
643  pos[2] = c1 * beta;
644  pos[0] = c2 * beta;
645  break;
646  }
647  case 3: {
648  // x=-R, -> yz
649  pos[0] = -R * beta;
650  pos[1] = -c1 * beta; // the sign is to preserve orientation
651  pos[2] = c2 * beta;
652  break;
653  }
654  case 4: {
655  // y = -R
656  pos[1] = -R * beta;
657  pos[2] = -c1 * beta; // the sign is to preserve orientation
658  pos[0] = c2 * beta;
659  break;
660  }
661  case 5: {
662  // z = -R
663  pos[2] = -R * beta;
664  pos[0] = -c1 * beta; // the sign is to preserve orientation
665  pos[1] = c2 * beta;
666  break;
667  }
668  case 6: {
669  pos[2] = R * beta;
670  pos[0] = c1 * beta;
671  pos[1] = c2 * beta;
672  break;
673  }
674  default:
675  return MB_FAILURE; // error
676  }
677 
678  return MB_SUCCESS; // no error
679 }
680 
681 void IntxUtils::gnomonic_unroll( double& c1, double& c2, double R, int plane )
682 {
683  double tmp;
684  switch( plane )
685  {
686  case 1:
687  break; // nothing
688  case 2: // rotate + 90
689  tmp = c1;
690  c1 = -c2;
691  c2 = tmp;
692  c1 = c1 + 2 * R;
693  break;
694  case 3:
695  c1 = c1 + 4 * R;
696  break;
697  case 4: // rotate with -90 x-> -y; y -> x
698 
699  tmp = c1;
700  c1 = c2;
701  c2 = -tmp;
702  c1 = c1 - 2 * R;
703  break;
704  case 5: // South Pole
705  // rotate 180 then move to (-2, -2)
706  c1 = -c1 - 2. * R;
707  c2 = -c2 - 2. * R;
708  break;
709  ;
710  case 6: // North Pole
711  c1 = c1 - 2. * R;
712  c2 = c2 + 2. * R;
713  break;
714  }
715  return;
716 }
717 
718 // given a mesh on a hemisphere, and a point P that defines the hemisphere, project the mesh
719 // on a plane tangent at P (gnomonic plane at P)
721  EntityHandle inSet,
722  CartVect P,
723  EntityHandle& outSet )
724 {
725  std::string parTagName( "PARALLEL_PARTITION" );
726  Tag part_tag;
727  Tag gidTag = mb->globalId_tag();
728  Tag targetParentTag, sourceParentTag;
729  mb->tag_get_handle( "TargetParent", targetParentTag );
730  mb->tag_get_handle( "SourceParent", sourceParentTag );
731  bool intxMesh = false;
732  if( targetParentTag != nullptr && sourceParentTag != nullptr )
733  intxMesh = true; // interested in source and target parent tags then
734  Range partSets;
735  ErrorCode rval = mb->tag_get_handle( parTagName.c_str(), part_tag );
736  if( MB_SUCCESS == rval && part_tag != 0 )
737  {
738  rval =
739  mb->get_entities_by_type_and_tag( inSet, MBENTITYSET, &part_tag, nullptr, 1, partSets, Interface::UNION );MB_CHK_ERR( rval );
740  }
741  rval = ScaleToRadius( mb, inSet, 1.0 );MB_CHK_ERR( rval );
742  // Get all entities of dimension 2
743  Range inputRange; // get
744  rval = mb->get_entities_by_dimension( inSet, 1, inputRange );MB_CHK_ERR( rval );
745  rval = mb->get_entities_by_dimension( inSet, 2, inputRange );MB_CHK_ERR( rval );
746 
747  std::map< EntityHandle, int > partsAssign;
748  std::map< int, EntityHandle > newPartSets;
749  if( !partSets.empty() )
750  {
751  // get all cells, and assign parts
752  for( Range::iterator setIt = partSets.begin(); setIt != partSets.end(); ++setIt )
753  {
754  EntityHandle pSet = *setIt;
755  Range ents;
756  rval = mb->get_entities_by_handle( pSet, ents );MB_CHK_ERR( rval );
757  int val;
758  rval = mb->tag_get_data( part_tag, &pSet, 1, &val );MB_CHK_ERR( rval );
759  // create a new set with the same part id tag, in the outSet
760  EntityHandle newPartSet;
761  rval = mb->create_meshset( MESHSET_SET, newPartSet );MB_CHK_ERR( rval );
762  rval = mb->tag_set_data( part_tag, &newPartSet, 1, &val );MB_CHK_ERR( rval );
763  newPartSets[val] = newPartSet;
764  rval = mb->add_entities( outSet, &newPartSet, 1 );MB_CHK_ERR( rval );
765  for( Range::iterator it = ents.begin(); it != ents.end(); ++it )
766  {
767  partsAssign[*it] = val;
768  }
769  }
770  }
771 
772  // decide gnomonic plane
773  CartVect axis[3];
774  axis[0] = P;
775  IntxUtils::gnomonic_projection_plane_at_point( axis[0], axis[1], axis[2] );
776  // project all vertices, and then create new cells
777 
778  Range verts;
779  rval = mb->get_connectivity( inputRange, verts );MB_CHK_ERR( rval );
780  std::map< EntityHandle, EntityHandle > corr;
781  for( Range::iterator vt = verts.begin(); vt != verts.end(); ++vt )
782  {
783  CartVect vect;
784  EntityHandle v = *vt;
785  rval = mb->get_coords( &v, 1, vect.array() );MB_CHK_ERR( rval );
786  double c[3];
787  c[2] = 0.;
788  IntxUtils::gnomonic_projection_generalized( vect, axis, c[0], c[1] );
789 
791  rval = mb->create_vertex( c, vertex );MB_CHK_ERR( rval );
792  int vID;
793  if( !intxMesh )
794  {
795  rval = mb->tag_get_data( gidTag, &v, 1, &vID );MB_CHK_SET_ERR( rval, "can't get id tag on vertex" );
796  // new vertex will get old ID
797  rval = mb->tag_set_data( gidTag, &vertex, 1, &vID );MB_CHK_SET_ERR( rval, "can't get id tag on vertex" );
798  }
799  corr[v] = vertex; // for new connectivity
800  }
801  EntityHandle new_conn[20]; // max edges in 2d ?
802  for( Range::iterator eit = inputRange.begin(); eit != inputRange.end(); ++eit )
803  {
804  EntityHandle eh = *eit;
805  const EntityHandle* conn = nullptr;
806  int num_nodes;
807  rval = mb->get_connectivity( eh, conn, num_nodes );MB_CHK_ERR( rval );
808  // build a new vertex array
809  for( int j = 0; j < num_nodes; j++ )
810  new_conn[j] = corr[conn[j]];
811  EntityType type = mb->type_from_handle( eh );
812  EntityHandle newCell;
813  rval = mb->create_element( type, new_conn, num_nodes, newCell );MB_CHK_ERR( rval );
814  rval = mb->add_entities( outSet, &newCell, 1 );MB_CHK_ERR( rval );
815  int eID;
816  if( !intxMesh )
817  {
818  rval = mb->tag_get_data( gidTag, &eh, 1, &eID );MB_CHK_SET_ERR( rval, "can't get id tag on entity handle" );
819  // new vertex will get old ID
820  rval = mb->tag_set_data( gidTag, &newCell, 1, &eID );MB_CHK_SET_ERR( rval, "can't set id tag on new cell" );
821  }
822  else
823  {
824  // look for parent tags if intx mesh targetParentTag , sourceParentTag
825  if( type >= moab::MBPOLYGON )
826  {
827  rval = mb->tag_get_data( targetParentTag, &eh, 1, &eID );MB_CHK_SET_ERR( rval, "can't get parent tag on entity handle" );
828  rval = mb->tag_set_data( targetParentTag, &newCell, 1, &eID );MB_CHK_SET_ERR( rval, "can't set parent tag on entity handle" );
829  rval = mb->tag_get_data( sourceParentTag, &eh, 1, &eID );MB_CHK_SET_ERR( rval, "can't get parent tag on entity handle" );
830  rval = mb->tag_set_data( sourceParentTag, &newCell, 1, &eID );MB_CHK_SET_ERR( rval, "can't set parent tag on entity handle" );
831  }
832  }
833  std::map< EntityHandle, int >::iterator mit = partsAssign.find( eh );
834  if( mit != partsAssign.end() )
835  {
836  int val = mit->second;
837  rval = mb->add_entities( newPartSets[val], &newCell, 1 );MB_CHK_ERR( rval );
838  }
839  }
840  return MB_SUCCESS;
841 }
842 
843 // given a mesh on the sphere, project all centers in 6 gnomonic planes, or project mesh too
845  EntityHandle inSet,
846  double R,
847  bool centers_only,
848  EntityHandle& outSet )
849 {
850  std::string parTagName( "PARALLEL_PARTITION" );
851  Tag part_tag;
852  Tag gidTag = mb->globalId_tag();
853  Tag targetParentTag, sourceParentTag;
854  mb->tag_get_handle( "TargetParent", targetParentTag );
855  mb->tag_get_handle( "SourceParent", sourceParentTag );
856  bool intxMesh = false;
857  if( targetParentTag != nullptr && sourceParentTag != nullptr )
858  intxMesh = true; // interested in source and target parent tags then
859  Range partSets;
860  ErrorCode rval = mb->tag_get_handle( parTagName.c_str(), part_tag );
861  if( MB_SUCCESS == rval && part_tag != 0 )
862  {
863  MB_CHK_ERR(
864  mb->get_entities_by_type_and_tag( inSet, MBENTITYSET, &part_tag, nullptr, 1, partSets, Interface::UNION ) );
865  }
866  MB_CHK_ERR( ScaleToRadius( mb, inSet, 1.0 ) );
867  // Get all entities of dimension 2
868  Range inputRange; // get
869  MB_CHK_ERR( mb->get_entities_by_dimension( inSet, 1, inputRange ) );
870  MB_CHK_ERR( mb->get_entities_by_dimension( inSet, 2, inputRange ) );
871 
872  std::map< EntityHandle, int > partsAssign;
873  std::map< int, EntityHandle > newPartSets;
874  if( !partSets.empty() )
875  {
876  // get all cells, and assign parts
877  for( Range::iterator setIt = partSets.begin(); setIt != partSets.end(); ++setIt )
878  {
879  EntityHandle pSet = *setIt;
880  Range ents;
881  MB_CHK_ERR( mb->get_entities_by_handle( pSet, ents ) );
882  int val;
883  MB_CHK_ERR( mb->tag_get_data( part_tag, &pSet, 1, &val ) );
884  // create a new set with the same part id tag, in the outSet
885  EntityHandle newPartSet;
886  MB_CHK_ERR( mb->create_meshset( MESHSET_SET, newPartSet ) );
887  MB_CHK_ERR( mb->tag_set_data( part_tag, &newPartSet, 1, &val ) );
888  newPartSets[val] = newPartSet;
889  MB_CHK_ERR( mb->add_entities( outSet, &newPartSet, 1 ) );
890  for( Range::iterator it = ents.begin(); it != ents.end(); ++it )
891  {
892  partsAssign[*it] = val;
893  }
894  }
895  }
896 
897  if( centers_only )
898  {
899  for( Range::iterator it = inputRange.begin(); it != inputRange.end(); ++it )
900  {
902  EntityHandle cell = *it;
903  MB_CHK_ERR( mb->get_coords( &cell, 1, center.array() ) );
904  int globalID = 0;
905  if( !intxMesh )
906  {
907  MB_CHK_SET_ERR( mb->tag_get_data( gidTag, &cell, 1, &globalID ), "can't get id tag on cell" );
908  }
909 
910  int plane;
911  decide_gnomonic_plane( center, plane );
912  double c[3];
913  c[2] = 0.;
914  gnomonic_projection( center, R, plane, c[0], c[1] );
915 
916  gnomonic_unroll( c[0], c[1], R, plane );
917 
919  MB_CHK_ERR( mb->create_vertex( c, vertex ) );
920 
921  if( !intxMesh )
922  {
923  MB_CHK_SET_ERR( mb->tag_set_data( gidTag, &vertex, 1, &globalID ), "can't set id tag on center" );
924  }
925  MB_CHK_ERR( mb->add_entities( outSet, &vertex, 1 ) );
926  }
927  }
928  else
929  {
930  // distribute the cells to 6 planes, based on the center
931  Range subranges[6];
932  for( Range::iterator it = inputRange.begin(); it != inputRange.end(); ++it )
933  {
935  EntityHandle cell = *it;
936  MB_CHK_ERR( mb->get_coords( &cell, 1, center.array() ) );
937  int plane;
938  decide_gnomonic_plane( center, plane );
939  subranges[plane - 1].insert( cell ); // includes edges if they exist
940  }
941  for( int i = 1; i <= 6; i++ )
942  {
943  Range verts;
944  MB_CHK_ERR( mb->get_connectivity( subranges[i - 1], verts ) );
945  std::map< EntityHandle, EntityHandle > corr;
946  for( Range::iterator vt = verts.begin(); vt != verts.end(); ++vt )
947  {
948  CartVect vect;
949  EntityHandle v = *vt;
950  MB_CHK_ERR( mb->get_coords( &v, 1, vect.array() ) );
951  double c[3];
952  c[2] = 0.;
953  gnomonic_projection( vect, R, i, c[0], c[1] );
954  gnomonic_unroll( c[0], c[1], R, i );
956  MB_CHK_ERR( mb->create_vertex( c, vertex ) );
957 
958  int vID;
959  if( !intxMesh )
960  {
961  MB_CHK_SET_ERR( mb->tag_get_data( gidTag, &v, 1, &vID ), "can't get id tag on vertex" );
962  // new vertex will get old ID
963  MB_CHK_SET_ERR( mb->tag_set_data( gidTag, &vertex, 1, &vID ), "can't get id tag on vertex" );
964  }
965  corr[v] = vertex; // for new connectivity
966  }
967  EntityHandle new_conn[20]; // max edges in 2d ?
968  for( Range::iterator eit = subranges[i - 1].begin(); eit != subranges[i - 1].end(); ++eit )
969  {
970  EntityHandle eh = *eit;
971  const EntityHandle* conn = nullptr;
972  int num_nodes;
973  MB_CHK_ERR( mb->get_connectivity( eh, conn, num_nodes ) );
974 
975  // build a new vertex array
976  for( int j = 0; j < num_nodes; j++ )
977  new_conn[j] = corr[conn[j]];
978 
979  EntityType type = mb->type_from_handle( eh );
980  EntityHandle newCell;
981  MB_CHK_ERR( mb->create_element( type, new_conn, num_nodes, newCell ) );
982  MB_CHK_ERR( mb->add_entities( outSet, &newCell, 1 ) );
983 
984  int eID;
985  if( !intxMesh )
986  {
987  MB_CHK_SET_ERR( mb->tag_get_data( gidTag, &eh, 1, &eID ), "can't get id tag on entity handle" );
988  // new vertex will get old ID
989  MB_CHK_SET_ERR( mb->tag_set_data( gidTag, &newCell, 1, &eID ), "can't set id tag on new cell" );
990  }
991  else
992  {
993  // look for parent tags if intx mesh targetParentTag , sourceParentTag
994  if( type >= moab::MBPOLYGON )
995  {
996  MB_CHK_SET_ERR( mb->tag_get_data( targetParentTag, &eh, 1, &eID ),
997  "can't get parent tag on entity handle" );
998  MB_CHK_SET_ERR( mb->tag_set_data( targetParentTag, &newCell, 1, &eID ),
999  "can't set parent tag on entity handle" );
1000  MB_CHK_SET_ERR( mb->tag_get_data( sourceParentTag, &eh, 1, &eID ),
1001  "can't get parent tag on entity handle" );
1002  MB_CHK_SET_ERR( mb->tag_set_data( sourceParentTag, &newCell, 1, &eID ),
1003  "can't set parent tag on entity handle" );
1004  }
1005  }
1006 
1007  std::map< EntityHandle, int >::iterator mit = partsAssign.find( eh );
1008  if( mit != partsAssign.end() )
1009  {
1010  int val = mit->second;
1011  MB_CHK_ERR( mb->add_entities( newPartSets[val], &newCell, 1 ) );
1012  }
1013  }
1014  }
1015  }
1016 
1017  return MB_SUCCESS;
1018 }
1019 
1020 void IntxUtils::transform_coordinates( double* avg_position, int projection_type )
1021 {
1022  if( projection_type == 1 )
1023  {
1024  double R =
1025  avg_position[0] * avg_position[0] + avg_position[1] * avg_position[1] + avg_position[2] * avg_position[2];
1026  R = sqrt( R );
1027  double lat = asin( avg_position[2] / R );
1028  double lon = atan2( avg_position[1], avg_position[0] );
1029  avg_position[0] = lon;
1030  avg_position[1] = lat;
1031  avg_position[2] = R;
1032  }
1033  else if( projection_type == 2 ) // gnomonic projection
1034  {
1035  CartVect pos( avg_position );
1036  int gplane;
1037  IntxUtils::decide_gnomonic_plane( pos, gplane );
1038 
1039  IntxUtils::gnomonic_projection( pos, 1.0, gplane, avg_position[0], avg_position[1] );
1040  avg_position[2] = 0;
1041  IntxUtils::gnomonic_unroll( avg_position[0], avg_position[1], 1.0, gplane );
1042  }
1043 }
1044 
1045 /*
1046  *
1047  use physical_constants, only : dd_pi
1048  type(cartesian3D_t), intent(in) :: cart
1049  type(spherical_polar_t) :: sphere
1050 
1051  sphere%r=distance(cart)
1052  sphere%lat=ASIN(cart%z/sphere%r)
1053  sphere%lon=0
1054 
1055  ! ==========================================================
1056  ! enforce three facts:
1057  !
1058  ! 1) lon at poles is defined to be zero
1059  !
1060  ! 2) Grid points must be separated by about .01 Meter (on earth)
1061  ! from pole to be considered "not the pole".
1062  !
1063  ! 3) range of lon is { 0<= lon < 2*pi }
1064  !
1065  ! ==========================================================
1066 
1067  if (distance(cart) >= DIST_THRESHOLD) then
1068  sphere%lon=ATAN2(cart%y,cart%x)
1069  if (sphere%lon<0) then
1070  sphere%lon=sphere%lon+2*DD_PI
1071  end if
1072  end if
1073 
1074  end function cart_to_spherical
1075  */
1077 {
1078  SphereCoords res;
1079  res.R = cart3d.length();
1080  if( res.R < 0 )
1081  {
1082  res.lon = res.lat = 0.;
1083  return res;
1084  }
1085  res.lat = asin( cart3d[2] / res.R );
1086  res.lon = atan2( cart3d[1], cart3d[0] );
1087  if( res.lon < 0 ) res.lon += 2 * M_PI; // M_PI is defined in math.h? it seems to be true, although
1088  // there are some defines it depends on :(
1089  // #if defined __USE_BSD || defined __USE_XOPEN ???
1090 
1091  return res;
1092 }
1093 
1094 /*
1095  * ! ===================================================================
1096  ! spherical_to_cart:
1097  ! converts spherical polar {lon,lat} to 3D cartesian {x,y,z}
1098  ! on unit sphere
1099  ! ===================================================================
1100 
1101  function spherical_to_cart(sphere) result (cart)
1102 
1103  type(spherical_polar_t), intent(in) :: sphere
1104  type(cartesian3D_t) :: cart
1105 
1106  cart%x=sphere%r*COS(sphere%lat)*COS(sphere%lon)
1107  cart%y=sphere%r*COS(sphere%lat)*SIN(sphere%lon)
1108  cart%z=sphere%r*SIN(sphere%lat)
1109 
1110  end function spherical_to_cart
1111  */
1113 {
1114  CartVect res;
1115  res[0] = sc.R * cos( sc.lat ) * cos( sc.lon ); // x coordinate
1116  res[1] = sc.R * cos( sc.lat ) * sin( sc.lon ); // y
1117  res[2] = sc.R * sin( sc.lat ); // z
1118  return res;
1119 }
1120 
1122 {
1123  Range nodes;
1124  ErrorCode rval = mb->get_entities_by_type( set, MBVERTEX, nodes, true ); // recursive
1125  if( rval != moab::MB_SUCCESS ) return rval;
1126 
1127  // one by one, get the node and project it on the sphere, with a radius given
1128  // the center of the sphere is at 0,0,0
1129  for( Range::iterator nit = nodes.begin(); nit != nodes.end(); ++nit )
1130  {
1131  EntityHandle nd = *nit;
1132  CartVect pos;
1133  rval = mb->get_coords( &nd, 1, pos.array() );
1134  if( rval != moab::MB_SUCCESS ) return rval;
1135  double len = pos.length();
1136  if( len == 0. ) return MB_FAILURE;
1137  pos = R / len * pos;
1138  rval = mb->set_coords( &nd, 1, pos.array() );
1139  if( rval != moab::MB_SUCCESS ) return rval;
1140  }
1141  return MB_SUCCESS;
1142 }
1143 
1144 // assume they are one the same sphere
1145 double IntxAreaUtils::spherical_angle( const double* A, const double* B, const double* C, double Radius )
1146 {
1147  // the angle by definition is between the planes OAB and OBC
1148  CartVect a( A );
1149  CartVect b( B );
1150  CartVect c( C );
1151  double err1 = a.length_squared() - Radius * Radius;
1152  if( fabs( err1 ) > 0.0001 )
1153  {
1154  std::cout << " error in input " << a << " radius: " << Radius << " error:" << err1 << "\n";
1155  }
1156  CartVect normalOAB = a * b;
1157  CartVect normalOCB = c * b;
1158  return angle( normalOAB, normalOCB );
1159 }
1160 
1161 // could be bigger than M_PI;
1162 // angle at B could be bigger than M_PI, if the orientation is such that ABC points toward the
1163 // interior
1164 double IntxUtils::oriented_spherical_angle( const double* A, const double* B, const double* C )
1165 {
1166  // assume the same radius, sphere at origin
1167  CartVect a( A ), b( B ), c( C );
1168  CartVect normalOAB = a * b;
1169  CartVect normalOCB = c * b;
1170  CartVect orient = ( c - b ) * ( a - b );
1171  double ang = angle( normalOAB, normalOCB ); // this is between 0 and M_PI
1172  if( ang != ang )
1173  {
1174  // signal of a nan
1175  std::cout << a << " " << b << " " << c << "\n";
1176  std::cout << ang << "\n";
1177  }
1178  if( orient % b < 0 ) return ( 2 * M_PI - ang ); // the other angle, supplement
1179 
1180  return ang;
1181 }
1182 
1183 double IntxAreaUtils::area_spherical_triangle( const double* A, const double* B, const double* C, double Radius )
1184 {
1185  switch( m_eAreaMethod )
1186  {
1187  case Girard:
1188  return area_spherical_triangle_girard( A, B, C, Radius );
1189 #ifdef MOAB_HAVE_TEMPESTREMAP
1190  case GaussQuadrature:
1191  return area_spherical_triangle_GQ( A, B, C );
1192 #endif
1193  case lHuiller:
1194  return area_spherical_triangle_lHuiller( A, B, C, Radius );
1195  case VanOosteromStrackee:
1196  default:
1197  return area_spherical_triangle_VOS( A, B, C, Radius );
1198  }
1199 }
1200 
1201 double IntxAreaUtils::area_spherical_polygon( const double* A, int N, double Radius, int* sign )
1202 {
1203  switch( m_eAreaMethod )
1204  {
1205  case Girard:
1206  return area_spherical_polygon_girard( A, N, Radius );
1207 #ifdef MOAB_HAVE_TEMPESTREMAP
1208  case GaussQuadrature:
1209  return area_spherical_polygon_GQ( A, N ) * Radius * Radius; //area_spherical_polygon_GQ normalizes
1210 #endif
1211  case lHuiller:
1212  return area_spherical_polygon_lHuiller( A, N, Radius, sign );
1213  case VanOosteromStrackee:
1214  default:
1215  return area_spherical_polygon_VOS( A, N, Radius, sign );
1216  }
1217 }
1218 
1219 bool IntxAreaUtils::area_method_from_name( const std::string& name, AreaMethod& method )
1220 {
1221  std::string lname( name );
1222  std::transform( lname.begin(), lname.end(), lname.begin(), ::tolower );
1223 
1224  if( lname == "lhuiller" || lname == "lhuilier" )
1225  method = lHuiller;
1226  else if( lname == "girard" )
1227  method = Girard;
1228  else if( lname == "gquad" || lname == "gaussquadrature" )
1229  method = GaussQuadrature;
1230  else if( lname == "vos" || lname == "vanoosterom" )
1231  method = VanOosteromStrackee;
1232  else
1233  return false;
1234 
1235  return true;
1236 }
1237 
1239 {
1240  switch( method )
1241  {
1242  case Girard:
1243  return "girard";
1244  case GaussQuadrature:
1245  return "gquad";
1246  case VanOosteromStrackee:
1247  return "vos";
1248  case lHuiller:
1249  default:
1250  return "lhuiller";
1251  }
1252 }
1253 
1254 /*
1255  * Van Oosterom & Strackee (1983), "The Solid Angle of a Plane Triangle",
1256  * IEEE Trans. Biomed. Eng. BME-30(2):125-126.
1257  *
1258  * The signed spherical excess of the triangle ABC on the unit sphere is
1259  *
1260  * E = 2 * atan2( A . (B x C), 1 + A.B + B.C + C.A )
1261  *
1262  * The sign follows the orientation of ABC, so unlike the l'Huilier path there is
1263  * no need for a separate triple-product test. Both arguments of atan2 are formed
1264  * from dot/cross products of the vertices directly: no differences of nearly equal
1265  * arc lengths are taken, which is exactly the cancellation that destroys l'Huilier
1266  * for sliver triangles.
1267  */
1268 double IntxAreaUtils::area_spherical_triangle_VOS( const double* A, const double* B, const double* C, double Radius )
1269 {
1270  CartVect ua( A ), ub( B ), uc( C );
1271 
1272  // Work on the unit sphere; scale the excess by Radius^2 at the end. Normalize
1273  // rather than dividing by Radius so that inputs which are only approximately on
1274  // the sphere do not bias the excess.
1275  ua.normalize();
1276  ub.normalize();
1277  uc.normalize();
1278 
1279  const double numerator = ua % ( ub * uc ); // ua . (ub x uc)
1280  const double denominator = 1.0 + ( ua % ub ) + ( ub % uc ) + ( uc % ua );
1281 
1282  // atan2 is well defined for denominator <= 0 (triangles covering more than a
1283  // hemisphere), and returns 0 for a fully degenerate triangle.
1284  const double excess = 2.0 * atan2( numerator, denominator );
1285 
1286  return excess * Radius * Radius;
1287 }
1288 
1289 double IntxAreaUtils::area_spherical_polygon_VOS( const double* A, int N, double Radius, int* sign )
1290 {
1291  // Fan the polygon from its first vertex. Each sub-triangle contributes its
1292  // signed excess, so concave polygons accumulate correctly without any special
1293  // handling and the total carries the polygon orientation.
1294  double area = 0.0;
1295  for( int i = 1; i < N - 1; i++ )
1296  {
1297  area += area_spherical_triangle_VOS( A, A + 3 * i, A + 3 * ( i + 1 ), Radius );
1298  }
1299 
1300  if( sign ) *sign = ( area < 0.0 ? -1 : ( area > 0.0 ? 1 : 0 ) );
1301 
1302  return area;
1303 }
1304 
1305 double IntxAreaUtils::area_spherical_triangle_girard( const double* A, const double* B, const double* C, double Radius )
1306 {
1307  double correction = spherical_angle( A, B, C, Radius ) + spherical_angle( B, C, A, Radius ) +
1308  spherical_angle( C, A, B, Radius ) - M_PI;
1309  double area = Radius * Radius * correction;
1310  // now, is it negative or positive? is it pointing toward the center or outward?
1311  CartVect a( A ), b( B ), c( C );
1312  CartVect abc = ( b - a ) * ( c - a );
1313  if( abc % a > 0 ) // dot product positive, means ABC points out
1314  return area;
1315  else
1316  return -area;
1317 }
1318 
1319 double IntxAreaUtils::area_spherical_polygon_girard( const double* A, int N, double Radius )
1320 {
1321  // this should work for non-convex polygons too
1322  // assume that the A, A+3, ..., A+3*(N-1) are the coordinates
1323  //
1324  if( N <= 2 ) return 0.;
1325  double sum_angles = 0.;
1326  for( int i = 0; i < N; i++ )
1327  {
1328  int i1 = ( i + 1 ) % N;
1329  int i2 = ( i + 2 ) % N;
1330  sum_angles += IntxUtils::oriented_spherical_angle( A + 3 * i, A + 3 * i1, A + 3 * i2 );
1331  }
1332  double correction = sum_angles - ( N - 2 ) * M_PI;
1333  return Radius * Radius * correction;
1334 }
1335 
1336 double IntxAreaUtils::area_spherical_polygon_lHuiller( const double* A, int N, double Radius, int* sign )
1337 {
1338  // This should work for non-convex polygons too
1339  // In the input vector A, assume that the A, A+3, ..., A+3*(N-1) are the coordinates
1340  // We also assume that the orientation is positive;
1341  // If negative orientation, the area will be negative
1342  if( N <= 2 ) return 0.;
1343 
1344  int lsign = 1; // assume positive orientain
1345  double area = 0.;
1346  for( int i = 1; i < N - 1; i++ )
1347  {
1348  int i1 = i + 1;
1349  double areaTriangle = area_spherical_triangle_lHuiller( A, A + 3 * i, A + 3 * i1, Radius );
1350  if( areaTriangle < 0 ) lsign = -1; // signal that we have at least one triangle with negative orientation ;
1351  // possible nonconvex polygon
1352  area += areaTriangle;
1353  }
1354  if( sign ) *sign = lsign;
1355 
1356  return area;
1357 }
1358 
1359 #ifdef MOAB_HAVE_TEMPESTREMAP
1360 
1361 double IntxAreaUtils::area_spherical_polygon_GQ( const double* A, int N )
1362 {
1363  // this should work for non-convex polygons too
1364  // In the input vector A, assume that the A, A+3, ..., A+3*(N-1) are the coordinates
1365  // We also assume that the orientation is positive;
1366  // If negative orientation, the area can be negative
1367  if( N <= 2 ) return 0.;
1368 
1369  // assume positive orientation
1370  double area = 0.;
1371  for( int i = 1; i < N - 1; i++ )
1372  {
1373  area += area_spherical_triangle_GQ( A, A + 3 * i, A + 3 * ( i + 1 ) );
1374  }
1375  return area;
1376 }
1377 
1378 template < typename Derived >
1379 Eigen::Array< typename Derived::Scalar, Derived::RowsAtCompileTime, Derived::ColsAtCompileTime > shift(
1380  const Eigen::ArrayBase< Derived >& array,
1381  int positions )
1382 {
1383  Eigen::Array< typename Derived::Scalar, Derived::RowsAtCompileTime, Derived::ColsAtCompileTime > result = array;
1384  if( positions > 0 )
1385  {
1386  result.segment( positions, array.size() - positions ) = array.head( array.size() - positions );
1387  result.head( positions ).setZero();
1388  }
1389  else if( positions < 0 )
1390  {
1391  result.head( array.size() + positions ) = array.tail( array.size() + positions );
1392  result.tail( -positions ).setZero();
1393  }
1394  return result;
1395 }
1396 
1397 double IntxAreaUtils::area_spherical_triangle_GQ( const double* inode1, const double* inode2, const double* inode3 )
1398 {
1399 #if defined( MOAB_HAVE_EIGEN3 )
1400  typedef Eigen::Map< const Eigen::Vector3d > V3d;
1401  const V3d node1( inode1 );
1402  const V3d node2( inode2 );
1403  const V3d node3( inode3 );
1404  const int nOrder = 6;
1405 
1406  // If we change the quadrature order, use the call: GaussQuadrature::GetPoints(nOrder, 0.0, 1.0, dG, dW);
1407  const double dG[6] = { 0.03376524289842397, 0.1693953067668678, 0.3806904069584016,
1408  0.6193095930415985, 0.8306046932331322, 0.966234757101576 };
1409  const double dW[6] = { 0.08566224618958521, 0.1803807865240693, 0.2339569672863455,
1410  0.2339569672863455, 0.1803807865240693, 0.08566224618958521 };
1411 
1412  double dFaceArea = 0.0;
1413  Eigen::Vector3d dF, dF2, dDaF, dDbF, dDaG, dDbG;
1414  double nodeCross[3];
1415 
1416  // Calculate area at quadrature node and sum it up
1417  for( int p = 0; p < nOrder; p++ )
1418  {
1419  for( int q = 0; q < nOrder; q++ )
1420  {
1421 
1422  const double dA = dG[p];
1423  const double dB = dG[q];
1424 
1425  // V3d dF = (1.0 - dB) * (((1.0 - dA) * node1) + (dA * node2)) + (dB * node3);
1426  dF = ( ( ( 1.0 - dB ) * ( 1.0 - dA ) ) * node1 ) + ( ( ( 1.0 - dB ) * dA ) * node2 ) + ( dB * node3 );
1427  dF2 = dF.array().square();
1428 
1429  dDaF = ( node1 - node2 );
1430 
1431  // dDbF = -( 1.0 - dA ) * node1 - dA * node2 + node3;
1432  dDbF = ( node3 - node1 ) + dA * dDaF;
1433  dDaF *= ( dB - 1.0 );
1434 
1435  const double dDenomTerm = std::pow( dF.norm(), -3.0 );
1436 
1437  // Eigen::Vector3d temp1 = dF2;
1438  // temp1( 2 ) += dF2( 0 ); // temp1 = [dF2(0), dF2(1), dF2(0)+dF2(2)]
1439  // Eigen::Vector3d temp2 = dDaF.cwiseProduct( dF ); // temp2 = [dDaF(0)*dF(0), dDaF(1)*dF(1), dDaF(2)*dF(2)]
1440  // dDaG = dDaF.cwiseProduct( temp1.segment( 1, 2 ) ) - temp2.segment( 1, 2 );
1441  // Eigen::Vector3d temp3 = dDbF.cwiseProduct( dF ); // temp3 = [dDbF(0)*dF(0), dDbF(1)*dF(1), dDbF(2)*dF(2)]
1442  // dDbG = dDbF.cwiseProduct( temp1.segment( 1, 2 ) ) - temp3.segment( 1, 2 );
1443 
1444  // Eigen::Vector3d dF2_shifted = dF2 + Eigen::Vector3d( dF2( 1 ), dF2( 2 ), dF2( 0 ) );
1445  // Eigen::Vector3d dDaF_shifted = Eigen::Vector3d( dDaF( 1 ), dDaF( 2 ), dDaF( 0 ) ).cwiseProduct( dF );
1446 
1447  // dDaG = dDaF.cwiseProduct( dF2_shifted ) - dF.cwiseProduct( dDaF_shifted );
1448 
1449  // Eigen::Vector3d dDbF_shifted = Eigen::Vector3d( dDbF( 1 ), dDbF( 2 ), dDbF( 0 ) ).cwiseProduct( dF );
1450 
1451  // dDbG = dDbF.cwiseProduct( dF2_shifted ) - dF.cwiseProduct( dDbF_shifted );
1452 
1453  dDaG( 0 ) = dDaF( 0 ) * ( dF2( 1 ) + dF2( 2 ) ) - dF( 0 ) * ( dDaF( 1 ) * dF( 1 ) + dDaF( 2 ) * dF( 2 ) );
1454  dDaG( 1 ) = dDaF( 1 ) * ( dF2( 0 ) + dF2( 2 ) ) - dF( 1 ) * ( dDaF( 0 ) * dF( 0 ) + dDaF( 2 ) * dF( 2 ) );
1455  dDaG( 2 ) = dDaF( 2 ) * ( dF2( 0 ) + dF2( 1 ) ) - dF( 2 ) * ( dDaF( 0 ) * dF( 0 ) + dDaF( 1 ) * dF( 1 ) );
1456 
1457  // dDbG = dDbF.cwiseProduct( ( dF2.array().shift( -1 ) + dF2.array().shift( -2 ) ) ) + dF.cwiseProduct( dDbF );
1458  dDbG( 0 ) = dDbF( 0 ) * ( dF2( 1 ) + dF2( 2 ) ) - dF( 0 ) * ( dDbF( 1 ) * dF( 1 ) + dDbF( 2 ) * dF( 2 ) );
1459  dDbG( 1 ) = dDbF( 1 ) * ( dF2( 0 ) + dF2( 2 ) ) - dF( 1 ) * ( dDbF( 0 ) * dF( 0 ) + dDbF( 2 ) * dF( 2 ) );
1460  dDbG( 2 ) = dDbF( 2 ) * ( dF2( 0 ) + dF2( 1 ) ) - dF( 2 ) * ( dDbF( 0 ) * dF( 0 ) + dDbF( 1 ) * dF( 1 ) );
1461 
1462  // Scale the vectors by the denominator
1463  dDaG *= dDenomTerm;
1464  dDbG *= dDenomTerm;
1465 
1466  // Cross product gives local Jacobian: dGaG x dDbG
1467  nodeCross[0] = dDaG( 1 ) * dDbG( 2 ) - dDaG( 2 ) * dDbG( 1 );
1468  nodeCross[1] = dDaG( 2 ) * dDbG( 0 ) - dDaG( 0 ) * dDbG( 2 );
1469  nodeCross[2] = dDaG( 0 ) * dDbG( 1 ) - dDaG( 1 ) * dDbG( 0 );
1470 
1471  const double dJacobian =
1472  std::sqrt( nodeCross[0] * nodeCross[0] + nodeCross[1] * nodeCross[1] + nodeCross[2] * nodeCross[2] );
1473 
1474  // dFaceArea += 2.0 * dW[p] * dW[q] * (1.0 - dG[q]) * dJacobian;
1475  dFaceArea += dW[p] * dW[q] * dJacobian;
1476  }
1477  }
1478 
1479  return dFaceArea;
1480 #else
1481  /* compute the area by using Gauss-Quadratures; use TR interfaces directly */
1482  Face face( 3 );
1483  NodeVector nodes( 3 );
1484  nodes[0] = Node( inode1[0], inode1[1], inode1[2] );
1485  nodes[1] = Node( inode2[0], inode2[1], inode2[2] );
1486  nodes[2] = Node( inode3[0], inode3[1], inode3[2] );
1487  face.SetNode( 0, 0 );
1488  face.SetNode( 1, 1 );
1489  face.SetNode( 2, 2 );
1490  return CalculateFaceArea( face, nodes );
1491 #endif
1492 }
1493 
1494 #endif
1495 
1496 /*
1497  * l'Huiller's formula for spherical triangle
1498  * http://williams.best.vwh.net/avform.htm
1499  * a, b, c are arc measures in radians, too
1500  * A, B, C are angles on the sphere, for which we already have formula
1501  * c
1502  * A -------B
1503  * \ |
1504  * \ |
1505  * \b |a
1506  * \ |
1507  * \ |
1508  * \ |
1509  * \C|
1510  * \|
1511  *
1512  * (The angle at B is not necessarily a right angle)
1513  *
1514  * sin(a) sin(b) sin(c)
1515  * ----- = ------ = ------
1516  * sin(A) sin(B) sin(C)
1517  *
1518  * In terms of the sides (this is excess, as before, but numerically stable)
1519  *
1520  * E = 4*atan(sqrt(tan(s/2)*tan((s-a)/2)*tan((s-b)/2)*tan((s-c)/2)))
1521  */
1523  const double* ptB,
1524  const double* ptC,
1525  double Radius )
1526 {
1527 
1528  // now, a is angle BOC, O is origin
1529  CartVect vA( ptA ), vB( ptB ), vC( ptC );
1530  double a = angle_robust( vB, vC );
1531  double b = angle_robust( vC, vA );
1532  double c = angle_robust( vA, vB );
1533  int sign = 1;
1534  // if( fabs( ( vA * vB ) % vC ) < 1e-17 ) sign = -1;
1535  if( ( vA * vB ) % vC < 0 ) sign = -1;
1536  double s = ( a + b + c ) / 2;
1537  double a1 = ( s - a ) / 2;
1538  double b1 = ( s - b ) / 2;
1539  double c1 = ( s - c ) / 2;
1540 #ifdef MOAB_HAVE_TEMPESTREMAP
1541  if( fabs( a1 ) < 1.e-14 || fabs( b1 ) < 1.e-14 || fabs( c1 ) < 1.e-14 )
1542  {
1543  double area = area_spherical_triangle_GQ( ptA, ptB, ptC ) * sign;
1544 #ifdef VERBOSE
1545  std::cout << " very obtuse angle, use TR to compute area "
1546  << " a1:" << a1 << " b1:" << b1 << " c1:" << c1 << "\n";
1547  std::cout << " area with TR: " << area << "\n";
1548 #endif
1549  return area;
1550  }
1551 #endif
1552  double tmp = tan( s / 2 ) * tan( a1 ) * tan( b1 ) * tan( c1 );
1553  if( tmp < 0. ) tmp = 0.;
1554 
1555  double E = 4 * atan( sqrt( tmp ) );
1556  if( E != E ) std::cout << " NaN at spherical triangle area \n";
1557 
1558  double area = sign * E * Radius * Radius;
1559 
1560  // NOTE: no negative-area diagnostic here. A single sub-triangle of a fan is
1561  // legitimately negative for concave cells, and orientation probes call this
1562  // precisely to *find* negatively wound cells before repairing them, so a report
1563  // at this level fires once per sub-triangle on perfectly valid input. The check
1564  // now lives in area_spherical_element(), which sees a whole cell and can tell a
1565  // genuinely inverted element from an ordinary negative fan contribution.
1566 
1567  return area;
1568 }
1569 
1571 {
1572  // Get all entities of dimension 2
1573  Range inputRange;
1574  ErrorCode rval = mb->get_entities_by_dimension( set, 2, inputRange );MB_CHK_ERR_RET_VAL( rval, -1.0 );
1575 
1576  // Filter by elements that are owned by current process
1577  std::vector< int > ownerinfo( inputRange.size(), -1 );
1578  Tag intxOwnerTag;
1579  rval = mb->tag_get_handle( "ORIG_PROC", intxOwnerTag );
1580  if( MB_SUCCESS == rval )
1581  {
1582  rval = mb->tag_get_data( intxOwnerTag, inputRange, &ownerinfo[0] );MB_CHK_ERR_RET_VAL( rval, -1.0 );
1583  }
1584 
1585  // compare total area with 4*M_PI * R^2
1586  int ie = 0;
1587  double total_area = 0.;
1588  for( Range::iterator eit = inputRange.begin(); eit != inputRange.end(); ++eit )
1589  {
1590 
1591  // All zero/positive owner data represents ghosted elems
1592  if( ownerinfo[ie++] >= 0 ) continue;
1593 
1594  EntityHandle eh = *eit;
1595  const double elem_area = this->area_spherical_element( mb, eh, R );
1596 
1597  // check whether the area of the spherical element is positive.
1598  if( elem_area <= 0 )
1599  {
1600  std::cout << "Area of element " << mb->id_from_handle( eh ) << " is = " << elem_area << "\n";
1601  mb->list_entity( eh );
1602  }
1603  assert( elem_area > 0 );
1604 
1605  // sum up the contribution
1606  total_area += elem_area;
1607  }
1608 
1609  // return total mesh area
1610  return total_area;
1611 }
1612 
1614 {
1615  // get the nodes, then the coordinates
1616  const EntityHandle* verts;
1617  int nsides;
1618  ErrorCode rval = mb->get_connectivity( elem, verts, nsides );MB_CHK_ERR_RET_VAL( rval, -1.0 );
1619 
1620  // account for possible padded polygons
1621  while( verts[nsides - 2] == verts[nsides - 1] && nsides > 3 )
1622  nsides--;
1623 
1624  // get coordinates
1625  std::vector< double > coords( 3 * nsides );
1626  rval = mb->get_coords( verts, nsides, &coords[0] );MB_CHK_ERR_RET_VAL( rval, -1.0 );
1627 
1628  // compute the area of the polygonal element
1629  const double area = area_spherical_polygon( &coords[0], nsides, R );
1630 
1631  // A negative area for a complete element means the cell is inverted (wound
1632  // clockwise) -- unlike an individual fan sub-triangle, this is always a defect.
1633  // Report the whole cell so the offending element can actually be located. This
1634  // is deliberately NOT gated by CHECKNEGATIVEAREA: it fires only on a genuinely
1635  // inverted cell (rare), unlike positive_orientation()'s per-sub-triangle probe.
1636  //
1637  // Only report cells whose area is negative by a meaningful margin. Vertices
1638  // closer than the merge tolerance (1e-12) are collapsed before intersection, so
1639  // any residual sliver sits far below this threshold and its sign carries no
1640  // information -- reporting those is noise, not diagnosis.
1641  if( area < -NEGATIVE_AREA_TOLERANCE )
1642  {
1643  const std::streamsize oldprec = std::cout.precision();
1644  std::cout << "negative area: " << std::setprecision( 15 ) << area << " for element "
1645  << mb->id_from_handle( elem ) << " with " << nsides << " vertices\n";
1646  for( int iv = 0; iv < nsides; iv++ )
1647  {
1648  std::cout << " v" << iv << ": " << coords[3 * iv] << " " << coords[3 * iv + 1] << " "
1649  << coords[3 * iv + 2] << "\n";
1650  }
1651  std::cout.precision( oldprec );
1652  }
1653 
1654  return area;
1655 }
1656 
1658 {
1659  SphereCoords sph1 = cart_to_spherical( p1 );
1660  SphereCoords sph2 = cart_to_spherical( p2 );
1661  // radius should be the same
1662  return sph1.R *
1663  acos( sin( sph1.lon ) * sin( sph2.lon ) + cos( sph1.lat ) * cos( sph2.lat ) * cos( sph2.lon - sph2.lon ) );
1664 }
1665 
1666 //
1667 /**
1668  * @brief Enforces convexity for a given set of polygons.
1669  *
1670  * This function checks each polygon in the input set and computes the angles of each vertex.
1671  * If a reflex angle is found, the polygon is broken into triangles and added back to the set.
1672  * This process continues until all polygons in the set are convex.
1673  *
1674  * @param mb The interface to the MOAB instance.
1675  * @param lset The handle of the input set containing the polygons.
1676  * @param my_rank The rank of the local process.
1677  * @return The error code indicating the success or failure of the operation.
1678  */
1680 {
1681  Range inputRange;
1682  MB_CHK_ERR( mb->get_entities_by_dimension( lset, 2, inputRange ) );
1683 
1684  Tag corrTag = nullptr;
1685  EntityHandle dumH = 0;
1686  // no need to check return error
1687  mb->tag_get_handle( CORRTAGNAME, 1, MB_TYPE_HANDLE, corrTag, MB_TAG_DENSE, &dumH );
1688 
1689  Tag gidTag = mb->globalId_tag();
1690 
1691  std::vector< double > coords;
1692  coords.resize( 3 * MAXEDGES ); // initial guess only; resized per cell below
1693  // we should create a queue with new polygons that need processing for reflex angles
1694  // (obtuse)
1695  std::queue< EntityHandle > newPolys;
1696  int brokenPolys = 0;
1697  Range::iterator eit = inputRange.begin();
1698  while( eit != inputRange.end() || !newPolys.empty() )
1699  {
1700  EntityHandle eh;
1701  if( eit != inputRange.end() )
1702  {
1703  eh = *eit;
1704  ++eit;
1705  }
1706  else
1707  {
1708  eh = newPolys.front();
1709  newPolys.pop();
1710  }
1711  // get the nodes, then the coordinates
1712  const EntityHandle* verts;
1713  int num_nodes;
1714  MB_CHK_ERR( mb->get_connectivity( eh, verts, num_nodes ) );
1715  int nsides = num_nodes;
1716  // account for possible padded polygons
1717  while( verts[nsides - 2] == verts[nsides - 1] && nsides > 3 )
1718  nsides--;
1719  EntityHandle corrHandle = 0;
1720  if( corrTag )
1721  {
1722  MB_CHK_ERR( mb->tag_get_data( corrTag, &eh, 1, &corrHandle ) );
1723  }
1724  int gid = 0;
1725  MB_CHK_ERR( mb->tag_get_data( gidTag, &eh, 1, &gid ) );
1726  coords.resize( 3 * nsides );
1727  if( nsides < 4 ) continue; // if already triangles, don't bother
1728  // get coordinates
1729  MB_CHK_ERR( mb->get_coords( verts, nsides, &coords[0] ) );
1730  // compute each angle
1731  bool alreadyBroken = false;
1732 
1733  for( int i = 0; i < nsides; i++ )
1734  {
1735  double* A = &coords[3 * i];
1736  double* B = &coords[3 * ( ( i + 1 ) % nsides )];
1737  double* C = &coords[3 * ( ( i + 2 ) % nsides )];
1738  double angle = IntxUtils::oriented_spherical_angle( A, B, C );
1739  if( angle - M_PI > 0. ) // even almost reflex is bad; break it!
1740  {
1741  if( alreadyBroken )
1742  {
1743  mb->list_entities( &eh, 1 );
1744  mb->list_entities( verts, nsides );
1745  double* D = &coords[3 * ( ( i + 3 ) % nsides )];
1746  std::cout << "ABC: " << angle << " \n";
1747  std::cout << "BCD: " << IntxUtils::oriented_spherical_angle( B, C, D ) << " \n";
1748  std::cout << "CDA: " << IntxUtils::oriented_spherical_angle( C, D, A ) << " \n";
1749  std::cout << "DAB: " << IntxUtils::oriented_spherical_angle( D, A, B ) << " \n";
1750  std::cout << " this cell has at least 2 angles > 180, it has serious issues\n";
1751 
1752  return MB_FAILURE;
1753  }
1754  // the bad angle is at i+1;
1755  // create 1 triangle and one polygon; add the polygon to the input range, so
1756  // it will be processed too
1757  // also, add both to the set :) and remove the original polygon from the set
1758  // break the next triangle, even though not optimal
1759  // so create the triangle i+1, i+2, i+3; remove i+2 from original list
1760  // even though not optimal in general, it is good enough.
1761  EntityHandle conn3[3] = { verts[( i + 1 ) % nsides], verts[( i + 2 ) % nsides],
1762  verts[( i + 3 ) % nsides] };
1763  // create a polygon with num_nodes-1 vertices, and connectivity
1764  // verts[i+1], verts[i+3], (all except i+2)
1765  std::vector< EntityHandle > conn( nsides - 1 );
1766  for( int j = 1; j < nsides; j++ )
1767  {
1768  conn[j - 1] = verts[( i + j + 2 ) % nsides];
1769  }
1770  EntityHandle newElement;
1771  MB_CHK_ERR( mb->create_element( MBTRI, conn3, 3, newElement ) );
1772 
1773  MB_CHK_ERR( mb->add_entities( lset, &newElement, 1 ) );
1774  if( corrTag )
1775  {
1776  MB_CHK_ERR( mb->tag_set_data( corrTag, &newElement, 1, &corrHandle ) );
1777  }
1778  MB_CHK_ERR( mb->tag_set_data( gidTag, &newElement, 1, &gid ) );
1779  if( nsides == 4 )
1780  {
1781  // create another triangle
1782  MB_CHK_ERR( mb->create_element( MBTRI, &conn[0], 3, newElement ) );
1783  }
1784  else
1785  {
1786  // create another polygon, and add it to the inputRange
1787  MB_CHK_ERR( mb->create_element( MBPOLYGON, &conn[0], nsides - 1, newElement ) );
1788  newPolys.push( newElement ); // because it has less number of edges, the
1789  // reverse should work to find it.
1790  }
1791  MB_CHK_ERR( mb->add_entities( lset, &newElement, 1 ) );
1792  if( corrTag )
1793  {
1794  MB_CHK_ERR( mb->tag_set_data( corrTag, &newElement, 1, &corrHandle ) );
1795  }
1796  MB_CHK_ERR( mb->tag_set_data( gidTag, &newElement, 1, &gid ) );
1797  MB_CHK_ERR( mb->remove_entities( lset, &eh, 1 ) );
1798  brokenPolys++;
1799  alreadyBroken = true; // get out of the loop, element is broken
1800  }
1801  }
1802  }
1803  if( brokenPolys > 0 )
1804  {
1805  std::cout << "on local process " << my_rank << ", " << brokenPolys
1806  << " concave polygons were decomposed in convex ones \n";
1807 #ifdef VERBOSE
1808  std::stringstream fff;
1809  fff << "file_set" << mb->id_from_handle( lset ) << "rk_" << my_rank << ".h5m";
1810  MB_CHK_ERR( mb->write_file( fff.str().c_str(), 0, 0, &lset, 1 ) );
1811  std::cout << "wrote new file set: " << fff.str() << "\n";
1812 #endif
1813  }
1814  return MB_SUCCESS;
1815 }
1816 
1817 // looking at quad connectivity, collapse to triangle if 2 nodes equal
1818 // then delete the old quad
1820 {
1821  Range quads;
1822  MB_CHK_ERR( mb->get_entities_by_type( set, MBQUAD, quads ) );
1823 
1824  Tag gid = mb->globalId_tag();
1825  for( Range::iterator qit = quads.begin(); qit != quads.end(); ++qit )
1826  {
1827  EntityHandle quad = *qit;
1828  const EntityHandle* conn4 = nullptr;
1829  int num_nodes = 0;
1830  MB_CHK_ERR( mb->get_connectivity( quad, conn4, num_nodes ) );
1831  for( int i = 0; i < num_nodes; i++ )
1832  {
1833  int next_node_index = ( i + 1 ) % num_nodes;
1834  if( conn4[i] == conn4[next_node_index] )
1835  {
1836  // form a triangle and delete the quad
1837  // first get the global id, to set it on triangle later
1838  int global_id = 0;
1839  MB_CHK_ERR( mb->tag_get_data( gid, &quad, 1, &global_id ) );
1840  int i2 = ( i + 2 ) % num_nodes;
1841  int i3 = ( i + 3 ) % num_nodes;
1842  EntityHandle conn3[3] = { conn4[i], conn4[i2], conn4[i3] };
1843  EntityHandle tri;
1844  MB_CHK_ERR( mb->create_element( MBTRI, conn3, 3, tri ) );
1845  MB_CHK_ERR( mb->add_entities( set, &tri, 1 ) );
1846  MB_CHK_ERR( mb->remove_entities( set, &quad, 1 ) );
1847  MB_CHK_ERR( mb->delete_entities( &quad, 1 ) );
1848  MB_CHK_ERR( mb->tag_set_data( gid, &tri, 1, &global_id ) );
1849  }
1850  }
1851  }
1852  return MB_SUCCESS;
1853 }
1854 
1856 {
1857  Range cells2d;
1858  MB_CHK_ERR( mb->get_entities_by_dimension( set, 2, cells2d ) );
1859 
1860  // Cells that are locally nonconvex at their first connectivity vertex but whose
1861  // total area confirms correct orientation (see below) -- tracked unconditionally,
1862  // regardless of CHECKNEGATIVEAREA, so a summary is always available to decide
1863  // whether the per-cell detail is worth turning on.
1864  size_t nNonconvex = 0;
1865  double maxNonconvexMagnitude = 0.0;
1866 
1867  for( Range::iterator qit = cells2d.begin(); qit != cells2d.end(); ++qit )
1868  {
1869  EntityHandle cell = *qit;
1870  const EntityHandle* conn = nullptr;
1871  int num_nodes = 0;
1872  MB_CHK_ERR( mb->get_connectivity( cell, conn, num_nodes ) );
1873  if( num_nodes < 3 ) return MB_FAILURE;
1874 
1875  // Pick three distinct vertices for the winding probe.
1876  //
1877  // Taking conn[0..2] blindly breaks on degenerate cells: a quad carrying a
1878  // duplicate vertex (as RLL polar cells do before fix_degenerate_quads() runs)
1879  // can place that duplicate inside the probe triangle, collapsing it to ~1e-18
1880  // with an arbitrary sign. When the sign comes out positive the cell is judged
1881  // correctly wound and an inverted cell survives. Callers are expected to run
1882  // fix_degenerate_quads() first, but the probe should not depend on it.
1883  EntityHandle probe[3];
1884  int nprobe = 0;
1885  for( int i = 0; i < num_nodes && nprobe < 3; i++ )
1886  {
1887  bool duplicate = false;
1888  for( int j = 0; j < nprobe; j++ )
1889  if( probe[j] == conn[i] ) duplicate = true;
1890  if( !duplicate ) probe[nprobe++] = conn[i];
1891  }
1892  // Fewer than three distinct vertices means the cell has no area at all; there
1893  // is no orientation to correct, so leave it for the caller's degeneracy pass.
1894  if( nprobe < 3 ) continue;
1895 
1896  double coords[9];
1897  MB_CHK_ERR( mb->get_coords( probe, 3, coords ) );
1898 
1899  // Probe the winding of the cell. This deliberately looks for negatively
1900  // oriented cells -- they are the ones about to be repaired -- so it must not
1901  // route through a path that reports a negative result as an anomaly.
1902  //
1903  // Van Oosterom & Strackee returns the signed excess directly and exactly,
1904  // which is precisely what an orientation test needs, and it stays accurate
1905  // for the sliver cells found near RLL poles where l'Huilier's tan((s-a)/2)
1906  // terms lose all significance.
1907  double area;
1908  if( R > 0 )
1909  area = area_spherical_triangle_VOS( coords, coords + 3, coords + 6, R );
1910  else
1911  area = IntxUtils::area2D( coords, coords + 3, coords + 6 );
1912  if( area < 0 )
1913  {
1914  // compute all area, do not revert if total area is positive
1915  std::vector< double > coords2( 3 * num_nodes );
1916  // get coordinates
1917  MB_CHK_ERR( mb->get_coords( conn, num_nodes, &coords2[0] ) );
1918  double totArea = ( R > 0 ? area_spherical_polygon_VOS( &coords2[0], num_nodes, R )
1919  : area_spherical_polygon_lHuiller( &coords2[0], num_nodes, R ) );
1920  if( totArea < 0 )
1921  {
1922  std::vector< EntityHandle > newconn( num_nodes );
1923  for( int i = 0; i < num_nodes; i++ )
1924  {
1925  newconn[num_nodes - 1 - i] = conn[i];
1926  }
1927  MB_CHK_ERR( mb->set_connectivity( cell, &newconn[0], num_nodes ) );
1928  }
1929  else if( area < -NEGATIVE_AREA_TOLERANCE )
1930  {
1931  // The probe triangle (first three distinct vertices) came out negative
1932  // while the whole cell integrates to a non-negative area: a genuinely
1933  // concave/reflex first vertex, not an inverted cell -- no repair needed.
1934  // The sign is a real geometric fact here (not roundoff -- see
1935  // NEGATIVE_AREA_TOLERANCE above), so this is expected on any mesh with
1936  // nonconvex overlap cells and requires no action.
1937  //
1938  // Always count it, so a summary is available without recompiling. Only
1939  // print per-cell detail under CHECKNEGATIVEAREA: on a fine mesh with many
1940  // legitimately nonconvex overlap cells this fires often enough to flood
1941  // the log without indicating any problem.
1942  ++nNonconvex;
1943  maxNonconvexMagnitude = std::max( maxNonconvexMagnitude, -area );
1944 #ifdef CHECKNEGATIVEAREA
1945  std::cout << " nonconvex problem first area:" << area << " total area: " << totArea << std::endl;
1946 #endif
1947  }
1948  }
1949  }
1950 
1951  // "may have had" is deliberate: these cells are already confirmed correctly
1952  // oriented (the total-area check above passed), not merely suspect. This line
1953  // exists so a normal run can tell, at a glance, whether -DCHECKNEGATIVEAREA is
1954  // worth turning on for a per-cell breakdown -- not to flag a defect.
1955  if( nNonconvex > 0 )
1956  std::cout << "positive_orientation: " << nNonconvex
1957  << " element(s) had non-convex overlap sub-cells (orientation corrected), "
1958  "max |probe area| = "
1959  << maxNonconvexMagnitude
1960  << std::endl;
1961 
1962  return MB_SUCCESS;
1963 }
1964 
1965 // distance along a great circle on a sphere of radius 1
1966 double IntxUtils::distance_on_sphere( double la1, double te1, double la2, double te2 )
1967 {
1968  return acos( sin( te1 ) * sin( te2 ) + cos( te1 ) * cos( te2 ) * cos( la1 - la2 ) );
1969 }
1970 
1971 /*
1972  * given 2 great circle arcs, AB and CD, compute the unique intersection point, if it exists
1973  * in between
1974  */
1975 ErrorCode IntxUtils::intersect_great_circle_arcs( double* A, double* B, double* C, double* D, double R, double* E )
1976 {
1977  // first verify A, B, C, D are on the same sphere
1978  double R2 = R * R;
1979  const double Tolerance = 1.e-12 * R2;
1980 
1981  CartVect a( A ), b( B ), c( C ), d( D );
1982 
1983  if( fabs( a.length_squared() - R2 ) + fabs( b.length_squared() - R2 ) + fabs( c.length_squared() - R2 ) +
1984  fabs( d.length_squared() - R2 ) >
1985  10 * Tolerance )
1986  return MB_FAILURE;
1987 
1988  CartVect n1 = a * b;
1989  if( n1.length_squared() < Tolerance ) return MB_FAILURE;
1990 
1991  CartVect n2 = c * d;
1992  if( n2.length_squared() < Tolerance ) return MB_FAILURE;
1993  CartVect n3 = n1 * n2;
1994  n3.normalize();
1995 
1996  n3 = R * n3;
1997  // the intersection is either n3 or -n3
1998  CartVect n4 = a * n3, n5 = n3 * b;
1999  if( n1 % n4 >= -Tolerance && n1 % n5 >= -Tolerance )
2000  {
2001  // n3 is good for ab, see if it is good for cd
2002  n4 = c * n3;
2003  n5 = n3 * d;
2004  if( n2 % n4 >= -Tolerance && n2 % n5 >= -Tolerance )
2005  {
2006  E[0] = n3[0];
2007  E[1] = n3[1];
2008  E[2] = n3[2];
2009  }
2010  else
2011  return MB_FAILURE;
2012  }
2013  else
2014  {
2015  // try -n3
2016  n3 = -n3;
2017  n4 = a * n3, n5 = n3 * b;
2018  if( n1 % n4 >= -Tolerance && n1 % n5 >= -Tolerance )
2019  {
2020  // n3 is good for ab, see if it is good for cd
2021  n4 = c * n3;
2022  n5 = n3 * d;
2023  if( n2 % n4 >= -Tolerance && n2 % n5 >= -Tolerance )
2024  {
2025  E[0] = n3[0];
2026  E[1] = n3[1];
2027  E[2] = n3[2];
2028  }
2029  else
2030  return MB_FAILURE;
2031  }
2032  else
2033  return MB_FAILURE;
2034  }
2035 
2036  return MB_SUCCESS;
2037 }
2038 
2039 // verify that result is in between a and b on a great circle arc, and between c and d on a constant
2040 // latitude arc
2041 static bool verify( CartVect a, CartVect b, CartVect c, CartVect d, double x, double y, double z )
2042 {
2043  // to check, the point has to be between a and b on a great arc, and between c and d on a const
2044  // lat circle
2045  CartVect s( x, y, z );
2046  CartVect n1 = a * b;
2047  CartVect n2 = a * s;
2048  CartVect n3 = s * b;
2049  if( n1 % n2 < 0 || n1 % n3 < 0 ) return false;
2050 
2051  // do the same for c, d, s, in plane z=0
2052  c[2] = d[2] = s[2] = 0.; // bring everything in the same plane, z=0;
2053 
2054  n1 = c * d;
2055  n2 = c * s;
2056  n3 = s * d;
2057  if( n1 % n2 < 0 || n1 % n3 < 0 ) return false;
2058 
2059  return true;
2060 }
2061 
2063  double* B,
2064  double* C,
2065  double* D,
2066  double R,
2067  double* E,
2068  int& np )
2069 {
2070  const double distTol = R * 1.e-6;
2071  const double Tolerance = R * R * 1.e-12; // radius should be 1, usually
2072  np = 0; // number of points in intersection
2073  CartVect a( A ), b( B ), c( C ), d( D );
2074  // check input first
2075  double R2 = R * R;
2076  if( fabs( a.length_squared() - R2 ) + fabs( b.length_squared() - R2 ) + fabs( c.length_squared() - R2 ) +
2077  fabs( d.length_squared() - R2 ) >
2078  10 * Tolerance )
2079  return MB_FAILURE;
2080 
2081  if( ( a - b ).length_squared() < Tolerance ) return MB_FAILURE;
2082  if( ( c - d ).length_squared() < Tolerance ) // edges are too short
2083  return MB_FAILURE;
2084 
2085  // CD is the const latitude arc
2086  if( fabs( C[2] - D[2] ) > distTol ) // cd is not on the same z (constant latitude)
2087  return MB_FAILURE;
2088 
2089  if( fabs( R - C[2] ) < distTol || fabs( R + C[2] ) < distTol ) return MB_FAILURE; // too close to the poles
2090 
2091  // find the points on the circle P(teta) = (r*sin(teta), r*cos(teta), C[2]) that are on the
2092  // great circle arc AB normal to the AB circle:
2093  CartVect n1 = a * b; // the normal to the great circle arc (circle)
2094  // solve the system of equations:
2095  /*
2096  * n1%(x, y, z) = 0 // on the great circle
2097  * z = C[2];
2098  * x^2+y^2+z^2 = R^2
2099  */
2100  double z = C[2];
2101  if( fabs( n1[0] ) + fabs( n1[1] ) < 2 * Tolerance )
2102  {
2103  // it is the Equator; check if the const lat edge is Equator too
2104  if( fabs( C[2] ) > distTol )
2105  {
2106  return MB_FAILURE; // no intx, too far from Eq
2107  }
2108  else
2109  {
2110  // all points are on the equator
2111  //
2112  CartVect cd = c * d;
2113  // by convention, c<d, positive is from c to d
2114  // is a or b between c , d?
2115  CartVect ca = c * a;
2116  CartVect ad = a * d;
2117  CartVect cb = c * b;
2118  CartVect bd = b * d;
2119  bool agtc = ( ca % cd >= -Tolerance ); // a>c?
2120  bool dgta = ( ad % cd >= -Tolerance ); // d>a?
2121  bool bgtc = ( cb % cd >= -Tolerance ); // b>c?
2122  bool dgtb = ( bd % cd >= -Tolerance ); // d>b?
2123  if( agtc )
2124  {
2125  if( dgta )
2126  {
2127  // a is for sure a point
2128  E[0] = a[0];
2129  E[1] = a[1];
2130  E[2] = a[2];
2131  np++;
2132  if( bgtc )
2133  {
2134  if( dgtb )
2135  {
2136  // b is also in between c and d
2137  E[3] = b[0];
2138  E[4] = b[1];
2139  E[5] = b[2];
2140  np++;
2141  }
2142  else
2143  {
2144  // then order is c a d b, intx is ad
2145  E[3] = d[0];
2146  E[4] = d[1];
2147  E[5] = d[2];
2148  np++;
2149  }
2150  }
2151  else
2152  {
2153  // b is less than c, so b c a d, intx is ac
2154  E[3] = c[0];
2155  E[4] = c[1];
2156  E[5] = c[2];
2157  np++; // what if E[0] is E[3]?
2158  }
2159  }
2160  else // c < d < a
2161  {
2162  if( dgtb ) // d is for sure in
2163  {
2164  E[0] = d[0];
2165  E[1] = d[1];
2166  E[2] = d[2];
2167  np++;
2168  if( bgtc ) // c<b<d<a
2169  {
2170  // another point is b
2171  E[3] = b[0];
2172  E[4] = b[1];
2173  E[5] = b[2];
2174  np++;
2175  }
2176  else // b<c<d<a
2177  {
2178  // another point is c
2179  E[3] = c[0];
2180  E[4] = c[1];
2181  E[5] = c[2];
2182  np++;
2183  }
2184  }
2185  else
2186  {
2187  // nothing, order is c, d < a, b
2188  }
2189  }
2190  }
2191  else // a < c < d
2192  {
2193  if( bgtc )
2194  {
2195  // c is for sure in
2196  E[0] = c[0];
2197  E[1] = c[1];
2198  E[2] = c[2];
2199  np++;
2200  if( dgtb )
2201  {
2202  // a < c < b < d; second point is b
2203  E[3] = b[0];
2204  E[4] = b[1];
2205  E[5] = b[2];
2206  np++;
2207  }
2208  else
2209  {
2210  // a < c < d < b; second point is d
2211  E[3] = d[0];
2212  E[4] = d[1];
2213  E[5] = d[2];
2214  np++;
2215  }
2216  }
2217  else // a, b < c < d
2218  {
2219  // nothing
2220  }
2221  }
2222  }
2223  // for the 2 points selected, see if it is only one?
2224  // no problem, maybe it will be collapsed later anyway
2225  if( np > 0 ) return MB_SUCCESS;
2226  return MB_FAILURE; // no intersection
2227  }
2228  {
2229  if( fabs( n1[0] ) <= fabs( n1[1] ) )
2230  {
2231  // resolve eq in x: n0 * x + n1 * y +n2*z = 0; y = -n2/n1*z -n0/n1*x
2232  // (u+v*x)^2+x^2=R2-z^2
2233  // (v^2+1)*x^2 + 2*u*v *x + u^2+z^2-R^2 = 0
2234  // delta = 4*u^2*v^2 - 4*(v^2-1)(u^2+z^2-R^2)
2235  // x1,2 =
2236  double u = -n1[2] / n1[1] * z, v = -n1[0] / n1[1];
2237  double a1 = v * v + 1, b1 = 2 * u * v, c1 = u * u + z * z - R2;
2238  double delta = b1 * b1 - 4 * a1 * c1;
2239  if( delta < -Tolerance ) return MB_FAILURE; // no intersection
2240  if( delta > Tolerance ) // 2 solutions possible
2241  {
2242  double x1 = ( -b1 + sqrt( delta ) ) / 2 / a1;
2243  double x2 = ( -b1 - sqrt( delta ) ) / 2 / a1;
2244  double y1 = u + v * x1;
2245  double y2 = u + v * x2;
2246  if( verify( a, b, c, d, x1, y1, z ) )
2247  {
2248  E[0] = x1;
2249  E[1] = y1;
2250  E[2] = z;
2251  np++;
2252  }
2253  if( verify( a, b, c, d, x2, y2, z ) )
2254  {
2255  E[3 * np + 0] = x2;
2256  E[3 * np + 1] = y2;
2257  E[3 * np + 2] = z;
2258  np++;
2259  }
2260  }
2261  else
2262  {
2263  // one solution
2264  double x1 = -b1 / 2 / a1;
2265  double y1 = u + v * x1;
2266  if( verify( a, b, c, d, x1, y1, z ) )
2267  {
2268  E[0] = x1;
2269  E[1] = y1;
2270  E[2] = z;
2271  np++;
2272  }
2273  }
2274  }
2275  else
2276  {
2277  // resolve eq in y, reverse
2278  // n0 * x + n1 * y +n2*z = 0; x = -n2/n0*z -n1/n0*y = u+v*y
2279  // (u+v*y)^2+y^2 -R2+z^2 =0
2280  // (v^2+1)*y^2 + 2*u*v *y + u^2+z^2-R^2 = 0
2281  //
2282  // x1,2 =
2283  double u = -n1[2] / n1[0] * z, v = -n1[1] / n1[0];
2284  double a1 = v * v + 1, b1 = 2 * u * v, c1 = u * u + z * z - R2;
2285  double delta = b1 * b1 - 4 * a1 * c1;
2286  if( delta < -Tolerance ) return MB_FAILURE; // no intersection
2287  if( delta > Tolerance ) // 2 solutions possible
2288  {
2289  double y1 = ( -b1 + sqrt( delta ) ) / 2 / a1;
2290  double y2 = ( -b1 - sqrt( delta ) ) / 2 / a1;
2291  double x1 = u + v * y1;
2292  double x2 = u + v * y2;
2293  if( verify( a, b, c, d, x1, y1, z ) )
2294  {
2295  E[0] = x1;
2296  E[1] = y1;
2297  E[2] = z;
2298  np++;
2299  }
2300  if( verify( a, b, c, d, x2, y2, z ) )
2301  {
2302  E[3 * np + 0] = x2;
2303  E[3 * np + 1] = y2;
2304  E[3 * np + 2] = z;
2305  np++;
2306  }
2307  }
2308  else
2309  {
2310  // one solution
2311  double y1 = -b1 / 2 / a1;
2312  double x1 = u + v * y1;
2313  if( verify( a, b, c, d, x1, y1, z ) )
2314  {
2315  E[0] = x1;
2316  E[1] = y1;
2317  E[2] = z;
2318  np++;
2319  }
2320  }
2321  }
2322  }
2323 
2324  if( np <= 0 ) return MB_FAILURE;
2325  return MB_SUCCESS;
2326 }
2327 
2328 #if 0
2329 ErrorCode set_edge_type_flag(Interface * mb, EntityHandle sf1)
2330 {
2331  Range cells;
2332  ErrorCode rval = mb->get_entities_by_dimension(sf1, 2, cells);
2333  if (MB_SUCCESS!= rval)
2334  return rval;
2335  Range edges;
2336  rval = mb->get_adjacencies(cells, 1, true, edges, Interface::UNION);
2337  if (MB_SUCCESS!= rval)
2338  return rval;
2339 
2340  Tag edgeTypeTag;
2341  int default_int=0;
2342  rval = mb->tag_get_handle("edge_type", 1, MB_TYPE_INTEGER, edgeTypeTag,
2343  MB_TAG_DENSE | MB_TAG_CREAT, &default_int);
2344  if (MB_SUCCESS!= rval)
2345  return rval;
2346  // add edges to the set? not yet, maybe later
2347  // if edge horizontal, set value to 1
2348  int type_constant_lat=1;
2349  for (Range::iterator eit=edges.begin(); eit!=edges.end(); ++eit)
2350  {
2351  EntityHandle edge = *eit;
2352  const EntityHandle *conn=0;
2353  int num_n=0;
2354  rval = mb->get_connectivity(edge, conn, num_n );
2355  if (MB_SUCCESS!= rval)
2356  return rval;
2357  double coords[6];
2358  rval = mb->get_coords(conn, 2, coords);
2359  if (MB_SUCCESS!= rval)
2360  return rval;
2361  if (fabs( coords[2]-coords[5] )< 1.e-6 )
2362  {
2363  rval = mb->tag_set_data(edgeTypeTag, &edge, 1, &type_constant_lat);
2364  if (MB_SUCCESS!= rval)
2365  return rval;
2366  }
2367  }
2368 
2369  return MB_SUCCESS;
2370 }
2371 #endif
2372 
2373 // decide in a different metric if the corners of CS quad are
2374 // in the interior of an RLL quad
2376  double* red2dc,
2377  int nsRed,
2378  CartVect* bluec,
2379  int nsBlue,
2380  int* blueEdgeType,
2381  double* P,
2382  int* side,
2383  double epsil )
2384 {
2385  int extraPoints = 0;
2386  // first decide the blue z coordinates
2387  CartVect A( 0. ), B( 0. ), C( 0. ), D( 0. );
2388  for( int i = 0; i < nsBlue; i++ )
2389  {
2390  if( blueEdgeType[i] == 0 )
2391  {
2392  int iP1 = ( i + 1 ) % nsBlue;
2393  if( bluec[i][2] > bluec[iP1][2] )
2394  {
2395  A = bluec[i];
2396  B = bluec[iP1];
2397  C = bluec[( i + 2 ) % nsBlue];
2398  D = bluec[( i + 3 ) % nsBlue]; // it could be back to A, if triangle on top
2399  break;
2400  }
2401  }
2402  }
2403  if( nsBlue == 3 && B[2] < 0 )
2404  {
2405  // select D to be C
2406  D = C;
2407  C = B; // B is the south pole then
2408  }
2409  // so we have A, B, C, D, with A at the top, b going down, then C, D, D > C, A > B
2410  // AB is const longitude, BC and DA constant latitude
2411  // check now each of the red points if they are inside this rectangle
2412  for( int i = 0; i < nsRed; i++ )
2413  {
2414  const CartVect& X = redc[i];
2415  if( X[2] > A[2] || X[2] < B[2] ) continue; // it is above or below the rectangle
2416  // now decide if it is between the planes OAB and OCD
2417  if( ( ( A * B ) % X >= -epsil ) && ( ( C * D ) % X >= -epsil ) )
2418  {
2419  side[i] = 1; //
2420  // it means point X is in the rectangle that we want , on the sphere
2421  // pass the coords 2d
2422  P[extraPoints * 2] = red2dc[2 * i];
2423  P[extraPoints * 2 + 1] = red2dc[2 * i + 1];
2424  extraPoints++;
2425  }
2426  }
2427  return extraPoints;
2428 }
2429 
2431 {
2432  ReadUtilIface* read_iface;
2433  MB_CHK_ERR( mb->query_interface( read_iface ) );
2434  // create the handle tag for the corresponding element / vertex
2435 
2436  EntityHandle dum = 0;
2437  Tag corrTag = 0; // it will be created here
2438  MB_CHK_ERR( mb->tag_get_handle( CORRTAGNAME, 1, MB_TYPE_HANDLE, corrTag, MB_TAG_DENSE | MB_TAG_CREAT, &dum ) );
2439 
2440  // give the same global id to new verts and cells created in the lagr(departure) mesh
2441  Tag gid = mb->globalId_tag();
2442 
2443  Range quads;
2444  MB_CHK_ERR( mb->get_entities_by_type( source_set, MBQUAD, quads ) );
2445 
2446  Range connecVerts;
2447  MB_CHK_ERR( mb->get_connectivity( quads, connecVerts ) );
2448 
2449  std::map< EntityHandle, EntityHandle > newNodes;
2450 
2451  std::vector< double* > coords;
2452  EntityHandle start_vert, start_elem, *connect;
2453  int num_verts = connecVerts.size();
2454  MB_CHK_ERR( read_iface->get_node_coords( 3, num_verts, 0, start_vert, coords ) );
2455 
2456  // fill it up
2457  int i = 0;
2458  for( Range::iterator vit = connecVerts.begin(); vit != connecVerts.end(); ++vit, i++ )
2459  {
2460  EntityHandle oldV = *vit;
2461  CartVect posi;
2462  MB_CHK_ERR( mb->get_coords( &oldV, 1, &( posi[0] ) ) );
2463 
2464  int global_id;
2465  MB_CHK_ERR( mb->tag_get_data( gid, &oldV, 1, &global_id ) );
2466  EntityHandle new_vert = start_vert + i;
2467  // Cppcheck warning (false positive): variable coords is assigned a value that is never used
2468  coords[0][i] = posi[0];
2469  coords[1][i] = posi[1];
2470  coords[2][i] = posi[2];
2471 
2472  newNodes[oldV] = new_vert;
2473  // set also the correspondent tag :)
2474  MB_CHK_ERR( mb->tag_set_data( corrTag, &oldV, 1, &new_vert ) );
2475 
2476  // also the other side
2477  // need to check if we really need this; the new vertex will never need the old vertex
2478  // we have the global id which is the same
2479  MB_CHK_ERR( mb->tag_set_data( corrTag, &new_vert, 1, &oldV ) );
2480  // set the global id on the corresponding vertex the same as the initial vertex
2481  MB_CHK_ERR( mb->tag_set_data( gid, &new_vert, 1, &global_id ) );
2482  }
2483  // now create new quads in order (in a sequence)
2484 
2485  MB_CHK_ERR( read_iface->get_element_connect( quads.size(), 4, MBQUAD, 0, start_elem, connect ) );
2486 
2487  int ie = 0;
2488  for( Range::iterator it = quads.begin(); it != quads.end(); ++it, ie++ )
2489  {
2490  EntityHandle q = *it;
2491  int nnodes;
2492  const EntityHandle* conn;
2493  MB_CHK_ERR( mb->get_connectivity( q, conn, nnodes ) );
2494  int global_id;
2495  MB_CHK_ERR( mb->tag_get_data( gid, &q, 1, &global_id ) );
2496 
2497  for( int ii = 0; ii < nnodes; ii++ )
2498  {
2499  EntityHandle v1 = conn[ii];
2500  connect[4 * ie + ii] = newNodes[v1];
2501  }
2502  EntityHandle newElement = start_elem + ie;
2503 
2504  // set the corresponding tag; not sure we need this one, from old to new
2505  MB_CHK_ERR( mb->tag_set_data( corrTag, &q, 1, &newElement ) );
2506  MB_CHK_ERR( mb->tag_set_data( corrTag, &newElement, 1, &q ) );
2507 
2508  // set the global id
2509  MB_CHK_ERR( mb->tag_set_data( gid, &newElement, 1, &global_id ) );
2510 
2511  MB_CHK_ERR( mb->add_entities( dest_set, &newElement, 1 ) );
2512  }
2513 
2514  MB_CHK_ERR( read_iface->update_adjacencies( start_elem, quads.size(), 4, connect ) );
2515 
2516  return MB_SUCCESS;
2517 }
2518 
2520  EntityHandle file_set,
2521  double merge_tol,
2522  std::vector< Tag >& tagList )
2523 {
2524  Range verts;
2525  MB_CHK_ERR( mb->get_entities_by_dimension( file_set, 0, verts ) );
2526  MB_CHK_ERR( mb->remove_entities( file_set, verts ) );
2527 
2528  MergeMesh mm( mb );
2529 
2530  // remove the vertices from the set, before merging
2531 
2532  MB_CHK_ERR( mm.merge_all( file_set, merge_tol ) );
2533 
2534  // now correct vertices that are repeated in polygons
2535  MB_CHK_ERR( remove_padded_vertices( mb, file_set, tagList ) );
2536  return MB_SUCCESS;
2537 }
2538 
2539 ErrorCode IntxUtils::remove_padded_vertices( Interface* mb, EntityHandle file_set, std::vector< Tag >& tagList )
2540 {
2541 
2542  // now correct vertices that are repeated in polygons
2543  Range cells;
2544  MB_CHK_ERR( mb->get_entities_by_dimension( file_set, 2, cells ) );
2545 
2546  Range verts;
2547  MB_CHK_ERR( mb->get_connectivity( cells, verts ) );
2548 
2549  Range modifiedCells; // will be deleted at the end; keep the gid
2550  Range newCells;
2551 
2552  for( Range::iterator cit = cells.begin(); cit != cells.end(); ++cit )
2553  {
2554  EntityHandle cell = *cit;
2555  const EntityHandle* connec = nullptr;
2556  int num_verts = 0;
2557  MB_CHK_SET_ERR( mb->get_connectivity( cell, connec, num_verts ), "Failed to get connectivity" );
2558 
2559  std::vector< EntityHandle > newConnec;
2560  newConnec.push_back( connec[0] ); // at least one vertex
2561  int index = 0;
2562  int new_size = 1;
2563  while( index < num_verts - 2 )
2564  {
2565  int next_index = ( index + 1 );
2566  if( connec[next_index] != newConnec[new_size - 1] )
2567  {
2568  newConnec.push_back( connec[next_index] );
2569  new_size++;
2570  }
2571  index++;
2572  }
2573  // add the last one only if different from previous and first node
2574  if( ( connec[num_verts - 1] != connec[num_verts - 2] ) && ( connec[num_verts - 1] != connec[0] ) )
2575  {
2576  newConnec.push_back( connec[num_verts - 1] );
2577  new_size++;
2578  }
2579  if( new_size < num_verts && new_size >= 3 )
2580  {
2581  // cout << "new cell from " << cell << " has only " << new_size << " vertices \n";
2582  modifiedCells.insert( cell );
2583  // create a new cell with type triangle, quad or polygon
2584  EntityType type = MBTRI;
2585  if( new_size == 3 )
2586  type = MBTRI;
2587  else if( new_size == 4 )
2588  type = MBQUAD;
2589  else if( new_size > 4 )
2590  type = MBPOLYGON;
2591 
2592  // create new cell
2593  EntityHandle newCell;
2594  MB_CHK_SET_ERR( mb->create_element( type, &newConnec[0], new_size, newCell ), "Failed to create new cell" );
2595  // set the old id to the new element
2596  newCells.insert( newCell );
2597  double value; // use the same value to reset the tags, even if the tags are int (like Global ID)
2598  for( size_t i = 0; i < tagList.size(); i++ )
2599  {
2600  MB_CHK_SET_ERR( mb->tag_get_data( tagList[i], &cell, 1, &value ),
2601  "Failed to get tag value" );
2602  MB_CHK_SET_ERR( mb->tag_set_data( tagList[i], &newCell, 1, &value ),
2603  "Failed to set tag value on new cell" );
2604  }
2605  }
2606  // new_size < 3: degenerate cell collapses below a valid polygon (e.g. SCRIP-style
2607  // padded placeholder with only 2 distinct vertices). Leave the original cell in
2608  // place — its padded connectivity faithfully represents what the input file stored.
2609  }
2610 
2611  MB_CHK_SET_ERR( mb->remove_entities( file_set, modifiedCells ), "Failed to remove old cells from file set" );
2612  MB_CHK_SET_ERR( mb->delete_entities( modifiedCells ), "Failed to delete old cells" );
2613  MB_CHK_SET_ERR( mb->add_entities( file_set, newCells ), "Failed to add new cells to file set" );
2614  MB_CHK_SET_ERR( mb->add_entities( file_set, verts ), "Failed to add verts to the file set" );
2615 
2616  return MB_SUCCESS;
2617 }
2618 
2619 ErrorCode IntxUtils::max_diagonal( Interface* mb, Range cells, int max_edges, double& diagonal )
2620 {
2621  diagonal = 0.0;
2622  std::vector< CartVect > coords( max_edges ); // maximum number of nodes in a cell? hard coded ?
2623  for( auto it = cells.begin(); it != cells.end(); ++it )
2624  {
2625  // get the connectivity, then the coordinates
2626  EntityHandle cell = *it;
2627  const EntityHandle* connec = nullptr;
2628  int num_verts = 0;
2629  MB_CHK_SET_ERR( mb->get_connectivity( cell, connec, num_verts ), "Failed to get connectivity" );
2630  MB_CHK_SET_ERR( mb->get_coords( connec, num_verts, &( coords[0][0] ) ), "Failed to get coordinates" );
2631  // compute the max diagonal, in a double loop
2632  for( int i = 0; i < num_verts - 1; i++ )
2633  {
2634  for( int j = i + 1; j < num_verts; j++ )
2635  {
2636  double len_sq = ( coords[i] - coords[j] ).length_squared();
2637  if( len_sq > diagonal ) diagonal = len_sq;
2638  }
2639  }
2640  }
2641 
2642  // return the root of the diagonal
2643  diagonal = std::sqrt( diagonal );
2644  return MB_SUCCESS;
2645 }
2646 
2647 } // namespace moab