PageRenderTime 27ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/test/api/egg_ut.cpp

https://gitlab.com/philipclaude/avro2
C++ | 367 lines | 227 code | 68 blank | 72 comment | 54 complexity | ea14a001bff2e1e2ac798c42a0f15aa0 MD5 | raw file
  1. //
  2. // avro - Adaptive Voronoi Remesher
  3. //
  4. // Copyright 2017-2022, Philip Claude Caplan
  5. // All rights reserved
  6. //
  7. // Licensed under The GNU Lesser General Public License, version 2.1
  8. // See http://www.opensource.org/licenses/lgpl-2.1.php
  9. //
  10. #include "unit_tester.hpp"
  11. #include "geometry/egads/context.h"
  12. #include "geometry/egads/data.h"
  13. #include "geometry/egads/object.h"
  14. #include "graphics/application.h"
  15. #include "graphics/math.h"
  16. #include "library/ckf.h"
  17. #include "numerics/geometry.h"
  18. #include "numerics/linear_algebra.h"
  19. #include "avro.h"
  20. #include "avro_config.h"
  21. #include <egads.h>
  22. #include <cmath>
  23. using namespace avro;
  24. UT_TEST_SUITE(api_custom_geometry_test_suite)
  25. #if AVRO_NO_ESP == 0
  26. void
  27. add_children( EGADSGeneralGeometry& egg , Entity* entity , std::map<ego,ego>& duplicates ) {
  28. ego e = static_cast<EGADS::Object*>(entity)->object();
  29. // do not add the duplicate ego's into the geometry hierarchy
  30. if (duplicates.find(e) != duplicates.end()) return;
  31. // add the children of this entity
  32. for (index_t k = 0; k < entity->nb_children(); k++) {
  33. ego ek = static_cast<EGADS::Object*>(&entity->child(k))->object();
  34. // again, do not add the duplicate ego's into the geometry hierarchy
  35. if (duplicates.find(ek) != duplicates.end()) continue;
  36. // add the child and recursively add its children as well
  37. egg.add_child(e,ek);
  38. add_children(egg,&entity->child(k),duplicates);
  39. }
  40. }
  41. UT_TEST_CASE(test1)
  42. {
  43. // this example glues two boxes together and tags the interior Face between them as an "interior"
  44. // (note: interior Edges and Nodes on this Face are also tagged as interior)
  45. // please follow the different steps outlined below to set up your own custom geometry
  46. // (see the STEP ... and END STEP)
  47. // you can pass in an existing EGADS context if you want
  48. EGADS::Context ctx;
  49. // STEP 1: initialize the geometry and avro context
  50. const coord_t dim = 3;
  51. coord_t number = dim;
  52. coord_t udim = dim -1;
  53. EGADSGeneralGeometry egg(ctx.get(),number-1);
  54. avro::Context adapter(dim,dim,udim,false);
  55. // END STEP 1
  56. // create the first box
  57. ego box1;
  58. real_t params1[6] = { 0.0 , 0.0 , 0.0 , 1.0 , 1.0 , 0.5 };
  59. EGADS_ENSURE_SUCCESS( EG_makeSolidBody( egg.context()->get() , BOX , params1 , &box1 ) );
  60. // create the second box
  61. ego box2;
  62. real_t params2[6] = { 0.0 , 0.0 , 0.5 , 1.0 , 1.0 , 0.5 };
  63. EGADS_ENSURE_SUCCESS( EG_makeSolidBody( egg.context()->get() , BOX , params2 , &box2 ) );
  64. // create the avro bodies (this is only needed for this demo, ideally you would have your own wrapper around ego's)
  65. avro::EGADS::Body b1( *egg.context() , box1 );
  66. avro::EGADS::Body b2( *egg.context() , box2 );
  67. b1.build_hierarchy();
  68. b2.build_hierarchy();
  69. // add each body ego to the egg
  70. egg.add_body( box1 );
  71. egg.add_body( box2 );
  72. // retrieve the entities of the first body
  73. std::vector<Entity*> entities1;
  74. b1.get_entities(entities1);
  75. printf("body1 has %lu entities\n",entities1.size());
  76. // retrieve the entities of the second body
  77. std::vector<Entity*> entities2;
  78. b2.get_entities(entities2);
  79. printf("body2 has %lu entities\n",entities2.size());
  80. // STEP 2: add the ego's to the geometry
  81. // add the ego's for each body (even if some are duplicates and won't be used)
  82. std::vector<Entity*> entities;
  83. for (index_t k = 0; k < entities1.size(); k++) {
  84. ego ek = static_cast<EGADS::Object*>(entities1[k])->object();
  85. egg.add_object( box1 , ek );
  86. entities.push_back( entities1[k] );
  87. }
  88. for (index_t k = 0; k < entities2.size(); k++) {
  89. ego ek = static_cast<EGADS::Object*>(entities2[k])->object();
  90. egg.add_object( box2 , ek );
  91. entities.push_back( entities2[k] );
  92. }
  93. printf("--> added objects\n");
  94. // END STEP 2
  95. // generate a mesh in each box
  96. index_t N = 5;
  97. CKF_Triangulation mesh1( {N,N,N} );
  98. CKF_Triangulation mesh2( {N,N,N} );
  99. // squish the z-coordinates
  100. avro_assert( mesh1.points().nb() == mesh2.points().nb() );
  101. for (index_t k = 0; k < mesh1.points().nb(); k++) {
  102. mesh1.points()[k][2] *= 0.5;
  103. mesh2.points()[k][2] = mesh1.points()[k][2] + 0.5;
  104. }
  105. // attach the mesh points to the bodies (again, this is only for the demo)
  106. // if you created your own meshes to start
  107. // (say from TetGen, then you should retain which ego each vertex is on)
  108. mesh1.points().attach( b1 );
  109. mesh2.points().attach( b2 );
  110. // map duplicate entities (which should be at z = 0.5)
  111. // there should be 1 face, 4 edges and 4 nodes which are duplicates
  112. // (see the topological number printed below)
  113. std::map<Entity*,Entity*> entity_map;
  114. std::map<ego,ego> ego_map;
  115. for (index_t i = 0; i < entities1.size(); i++)
  116. for (index_t j = 0; j < entities2.size(); j++) {
  117. ego ei = static_cast<EGADS::Object*>(entities1[i])->object();
  118. ego ej = static_cast<EGADS::Object*>(entities2[j])->object();
  119. int same = EG_isSame(ei,ej);
  120. if (same == EGADS_SUCCESS)
  121. printf("number = %u, i = %lu, j = %lu are the same!\n",entities1[i]->number(),i,j);
  122. else
  123. continue;
  124. // we will only keep entities1[i]
  125. entity_map.insert( {entities2[j],entities1[i]} );
  126. ego_map.insert( {ej,ei} );
  127. // mark the entity as interior
  128. entities1[i]->set_interior(true);
  129. // add the unique entity as a child of the duplicate entity's parents
  130. std::shared_ptr<Entity> ep = b1.lookup(ei); // retrieve the entity that we want to keep
  131. avro_assert( ep != nullptr );
  132. for (index_t k = 0; k < entities2[j]->nb_parents(); k++) {
  133. entities2[j]->parents(k)->add_child(ep);
  134. }
  135. }
  136. printf("--> number of duplicate entities = %lu\n",entity_map.size());
  137. // STEP 3: set the interior entities for the egg
  138. for (index_t k = 0; k < entities1.size(); k++) {
  139. if (!entities1[k]->interior()) continue;
  140. ego e = static_cast<EGADS::Object*>(entities1[k])->object();
  141. egg.set_interior(e);
  142. }
  143. // END STEP 3
  144. // STEP 4: add the ego parent-child relationships
  145. // add the adjacency relationships
  146. for (index_t k = 0; k < entities.size(); k++) {
  147. add_children(egg,entities[k],ego_map);
  148. }
  149. printf("--> adjacencies added\n");
  150. // END STEP 4
  151. // STEP 5: finalize the customized geometry and pass it to the context
  152. egg.finalize();
  153. adapter.define_geometry(egg);
  154. // END STEP 5
  155. // STEP 6: generate your mesh from the geometry
  156. // (in general you might use TetGen and retain the ego's each vertex is on)
  157. // create one final mesh
  158. Points points(dim);
  159. Topology<Simplex> topology(points,number);
  160. // copy the points
  161. mesh1.points().copy( points );
  162. std::map<index_t,index_t> point_map;
  163. for (index_t k = 0; k < mesh2.points().nb(); k++) {
  164. Entity* ek = mesh2.points().entity(k);
  165. // check if the point is not a duplicate
  166. if (ek == nullptr || entity_map.find(ek) == entity_map.end()) {
  167. index_t idx = points.nb();
  168. point_map.insert( {k,idx} );
  169. points.create( mesh2.points()[k] );
  170. points.set_entity( idx , ek );
  171. points.set_param( idx , mesh2.points().u(k) );
  172. continue;
  173. }
  174. // determine which point in mesh1 this is closest to
  175. int idx = -1;
  176. for (index_t i = 0; i < mesh1.points().nb(); i++) {
  177. if (mesh1.points().entity(i) == nullptr) continue;
  178. if (!mesh1.points().entity(i)->interior()) continue; // reduce the loop down to O(# duplicate points)
  179. real_t d = numerics::distance( mesh1.points()[i] , mesh2.points()[k] , dim );
  180. if (d < 1e-12) {
  181. idx = i;
  182. break;
  183. }
  184. }
  185. avro_assert( idx >= 0 );
  186. point_map.insert( {k,idx} );
  187. }
  188. printf("--> number of duplicate points = %lu\n",
  189. mesh1.points().nb() + mesh2.points().nb() - points.nb());
  190. UT_ASSERT_EQUALS( mesh1.points().nb() + mesh2.points().nb() - points.nb() , N*N );
  191. // compute the parameter coordinates (which may have changed when we adjusted for duplicates)
  192. for (index_t k = 0; k < points.nb(); k++) {
  193. Entity* e = points.entity(k);
  194. if (e == nullptr) continue;
  195. std::vector<real_t> X( points[k] , points[k] + dim );
  196. std::vector<real_t> U( dim-1 );
  197. e->inverse( X , U );
  198. points.set_param( k , U.data() );
  199. }
  200. // END STEP 6
  201. // check the number of points on geometry Nodes is 12 (8 + 8 - the 4 duplicates)
  202. index_t nb_nodes = 0;
  203. for (index_t k = 0; k < points.nb(); k++) {
  204. if (points.entity(k) == nullptr) continue;
  205. if (points.entity(k)->number() == 0) nb_nodes++;
  206. }
  207. UT_ASSERT_EQUALS( nb_nodes , 12 );
  208. // copy the simplices
  209. for (index_t k = 0; k < mesh1.nb(); k++)
  210. topology.add( mesh1(k) , mesh1.nv(k) );
  211. for (index_t k = 0; k < mesh2.nb(); k++) {
  212. std::vector<index_t> elem( mesh2(k) , mesh2(k) + mesh2.nv(k) );
  213. for (index_t i = 0; i < elem.size(); i++)
  214. elem[i] = point_map.at( elem[i] );
  215. topology.add( elem.data() , elem.size() );
  216. }
  217. // STEP 7: retrieve the association between ego's and the context's integer labels
  218. std::map<ego,int> ids;
  219. adapter.get_ego_ids(ids);
  220. printf("--> nb geometry ids = %lu\n",ids.size());
  221. // determine the geometry (integer) label for every vertex
  222. std::vector<real_t> coordinates( dim*points.nb() );
  223. std::vector<int> geometry( points.nb() , -1 );
  224. std::vector<real_t> parameters( points.nb() * (dim-1) , unset_value );
  225. for (index_t k = 0; k < points.nb(); k++) {
  226. for (coord_t d = 0; d < dim; d++)
  227. coordinates[k*dim+d] = points[k][d];
  228. Entity* entity = points.entity(k);
  229. if (entity == nullptr) continue;
  230. parameters[2*k] = points.u(k)[0];
  231. parameters[2*k+1] = points.u(k)[1];
  232. ego e = static_cast<EGADS::Object*>(entity)->object();
  233. geometry[k] = ids.at(e);
  234. }
  235. // END STEP 7, then proceed to use the context as usual
  236. // send the mesh and geometry information to the context
  237. adapter.load_mesh( coordinates , topology.data() );
  238. adapter.load_geometry( geometry , parameters );
  239. index_t nb_points = points.nb();
  240. index_t nb_rank = number*(number+1)/2;
  241. std::vector<real_t> metrics( nb_points * nb_rank , 0.0 );
  242. // constant diagonal metric requesting a size of h
  243. real_t h = 0.125;
  244. for (index_t k = 0; k < nb_points; k++) {
  245. index_t idx = k*nb_rank;
  246. for (index_t i = 0; i < number; i++) {
  247. metrics[idx] = 1./(h*h);
  248. idx += number - i;
  249. }
  250. }
  251. //adapter.parameters().set( "output redirect" , "avro-output.txt" );
  252. adapter.parameters().set( "curved" , false );
  253. // perform the adaptation
  254. adapter.adapt(metrics);
  255. // retrieve the mesh from the context
  256. std::vector<index_t> elements;
  257. adapter.retrieve_mesh( coordinates , elements );
  258. nb_points = coordinates.size() / dim;
  259. index_t nb_elements = elements.size() / (number+1);
  260. printf("adapted mesh has %lu points and %lu elements\n",nb_points,nb_elements);
  261. // retrieve the geometry metadata
  262. std::vector<real_t> param;
  263. std::vector<int> entity_idx;
  264. adapter.retrieve_geometry( entity_idx , param );
  265. assert( param.size() == udim*nb_points );
  266. assert( entity_idx.size() == nb_points );
  267. for (index_t k = 0; k < nb_points; k++) {
  268. printf("\tpoint [%lu] (%g,%g,%g) on entity %d: u = ( ",k,coordinates[3*k],coordinates[3*k+1],coordinates[3*k+2],entity_idx[k]);
  269. for (index_t j = 0; j < udim; j++)
  270. printf("%g ",param[k*udim+j]);
  271. printf(")\n");
  272. }
  273. // retrieve the boundary facets
  274. std::vector<std::vector<index_t>> facets;
  275. // either of the following two functions will work (the first uses the current mesh stored in the context, the second uses the geometry information of each vertex along with the mesh elements to infer the boundary)
  276. // note that vertex coordinates are not required since this is a purely topological operation, which is why the second method does not require vertex coordinates
  277. //context.retrieve_boundary( facets , geometry );
  278. adapter.retrieve_boundary( entity_idx , elements , facets , geometry , true ); // true because we have interior entities
  279. assert( facets.size() == geometry.size() );
  280. std::vector<const real_t*> x(number);
  281. real_t area = 0.0;
  282. for (index_t bc = 0; bc < facets.size(); bc++) {
  283. const std::vector<index_t>& facets_on_bc = facets[bc];
  284. index_t nb_facets = facets_on_bc.size() / number;
  285. printf("there are %lu facets on bc %lu with geometry id %d\n",nb_facets,bc,geometry[bc]);
  286. for (index_t k = 0; k < nb_facets; k++) {
  287. // calculate the area of the facet
  288. for (index_t j = 0; j < number; j++)
  289. x[j] = &coordinates[3*facets_on_bc[k*number+j]];
  290. area += numerics::volume_nd( x , dim );
  291. }
  292. }
  293. // we have 2 external 1x1 faces, 8 external 0.5x1 faces, 1 internal 1x1 face
  294. printf("area = %g\n",area);
  295. UT_ASSERT_NEAR( area , 7.0 , 1e-12 );
  296. }
  297. UT_TEST_CASE_END(test1)
  298. #endif // AVRO_NO_ESP
  299. UT_TEST_SUITE_END(api_custom_geometry_test_suite)