geometry_utils.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. /******************************************************************************
  2. *
  3. * Copyright (C) 2020 by
  4. * The Salk Institute for Biological Studies
  5. *
  6. * Use of this source code is governed by an MIT-style
  7. * license that can be found in the LICENSE file or at
  8. * https://opensource.org/licenses/MIT.
  9. *
  10. ******************************************************************************/
  11. #include "generated/gen_geometry_utils.h"
  12. #include "api/geometry_object.h"
  13. #include "api/model.h"
  14. #include "world.h"
  15. #include "geometry.h"
  16. #include "partition.h"
  17. #include <vtkPlatonicSolidSource.h>
  18. #include <vtkPolyData.h>
  19. #include <vtkSmartPointer.h>
  20. using namespace std;
  21. namespace MCell {
  22. namespace API {
  23. namespace geometry_utils {
  24. std::shared_ptr<GeometryObject> create_box(
  25. const std::string& name,
  26. const double edge_dimension,
  27. const std::vector<double> xyz_dimensions) {
  28. string err_msg = S("Exactly one ") + NAME_EDGE_DIMENSION + " or individual x, y, z dimensions can be set.";
  29. double x, y, z;
  30. if (is_set(edge_dimension)) {
  31. if (is_set(xyz_dimensions)) {
  32. throw ValueError(err_msg);
  33. }
  34. x = edge_dimension / 2;
  35. y = edge_dimension / 2;
  36. z = edge_dimension / 2;
  37. }
  38. else {
  39. if (xyz_dimensions.size() != 3) {
  40. throw ValueError(err_msg);
  41. }
  42. x = xyz_dimensions[0] / 2;
  43. y = xyz_dimensions[1] / 2;
  44. z = xyz_dimensions[2] / 2;
  45. }
  46. // using the same ordering of vertices and faces as what is generated by
  47. // CellBlender, changing this impacts surface molecule releases
  48. vector<vector<double>> vertex_list {
  49. {-x, -y, -z},
  50. {-x, -y, z},
  51. {-x, y, -z},
  52. {-x, y, z},
  53. { x, -y, -z},
  54. { x, -y, z},
  55. { x, y, -z},
  56. { x, y, z}
  57. };
  58. vector<vector<int>> wall_list {
  59. {1, 2, 0},
  60. {3, 6, 2},
  61. {7, 4, 6},
  62. {5, 0, 4},
  63. {6, 0, 2},
  64. {3, 5, 7},
  65. {1, 3, 2},
  66. {3, 7, 6},
  67. {7, 5, 4},
  68. {5, 1, 0},
  69. {6, 4, 0},
  70. {3, 1, 5}
  71. };
  72. auto res = make_shared<GeometryObject>(
  73. name,
  74. vertex_list,
  75. wall_list
  76. );
  77. return res;
  78. }
  79. // used code from the link below due to absence of any reasonable library
  80. // https://github.com/caosdoar/spheres/blob/master/src/spheres.cpp
  81. // published under MIT license
  82. struct Edge
  83. {
  84. uint32_t v0;
  85. uint32_t v1;
  86. Edge(uint32_t v0, uint32_t v1)
  87. : v0(v0 < v1 ? v0 : v1)
  88. , v1(v0 < v1 ? v1 : v0)
  89. {
  90. }
  91. bool operator <(const Edge &rhs) const
  92. {
  93. return v0 < rhs.v0 || (v0 == rhs.v0 && v1 < rhs.v1);
  94. }
  95. };
  96. static Vec3 normalize(const Vec3 &a)
  97. {
  98. const double lrcp = 1.0 / std::sqrt(dot(a, a));
  99. return Vec3(a.x * lrcp, a.y * lrcp, a.z * lrcp);
  100. }
  101. static void init_icosphere(
  102. vector<Vec3>& vertex_list,
  103. vector<vector<int>>& wall_list) {
  104. vertex_list.clear();
  105. wall_list.clear();
  106. const double t = (1.0 + std::sqrt(5.0)) / 2.0;
  107. // Vertices
  108. vertex_list.push_back(normalize(Vec3(-1.0, t, 0.0)));
  109. vertex_list.push_back(normalize(Vec3( 1.0, t, 0.0)));
  110. vertex_list.push_back(normalize(Vec3(-1.0, -t, 0.0)));
  111. vertex_list.push_back(normalize(Vec3( 1.0, -t, 0.0)));
  112. vertex_list.push_back(normalize(Vec3(0.0, -1.0, t)));
  113. vertex_list.push_back(normalize(Vec3(0.0, 1.0, t)));
  114. vertex_list.push_back(normalize(Vec3(0.0, -1.0, -t)));
  115. vertex_list.push_back(normalize(Vec3(0.0, 1.0, -t)));
  116. vertex_list.push_back(normalize(Vec3( t, 0.0, -1.0)));
  117. vertex_list.push_back(normalize(Vec3( t, 0.0, 1.0)));
  118. vertex_list.push_back(normalize(Vec3(-t, 0.0, -1.0)));
  119. vertex_list.push_back(normalize(Vec3(-t, 0.0, 1.0)));
  120. // Faces
  121. wall_list.push_back(vector<int>{0, 11, 5});
  122. wall_list.push_back(vector<int>{0, 5, 1});
  123. wall_list.push_back(vector<int>{0, 1, 7});
  124. wall_list.push_back(vector<int>{0, 7, 10});
  125. wall_list.push_back(vector<int>{0, 10, 11});
  126. wall_list.push_back(vector<int>{1, 5, 9});
  127. wall_list.push_back(vector<int>{5, 11, 4});
  128. wall_list.push_back(vector<int>{11, 10, 2});
  129. wall_list.push_back(vector<int>{10, 7, 6});
  130. wall_list.push_back(vector<int>{7, 1, 8});
  131. wall_list.push_back(vector<int>{3, 9, 4});
  132. wall_list.push_back(vector<int>{3, 4, 2});
  133. wall_list.push_back(vector<int>{3, 2, 6});
  134. wall_list.push_back(vector<int>{3, 6, 8});
  135. wall_list.push_back(vector<int>{3, 8, 9});
  136. wall_list.push_back(vector<int>{4, 9, 5});
  137. wall_list.push_back(vector<int>{2, 4, 11});
  138. wall_list.push_back(vector<int>{6, 2, 10});
  139. wall_list.push_back(vector<int>{8, 6, 7});
  140. wall_list.push_back(vector<int>{9, 8, 1});
  141. }
  142. int subdivide_icosphere_edge(
  143. int f0, int f1,
  144. const Vec3& v0, const Vec3& v1,
  145. vector<Vec3>& vertex_list_out,
  146. std::map<Edge, uint32_t> &io_divisions
  147. )
  148. {
  149. // required to make the mesh watertight
  150. const Edge edge(f0, f1);
  151. auto it = io_divisions.find(edge);
  152. if (it != io_divisions.end()) {
  153. return it->second;
  154. }
  155. const Vec3 v = normalize(Vec3(0.5) * (v0 + v1));
  156. const uint32_t f = vertex_list_out.size();
  157. vertex_list_out.push_back(v);
  158. io_divisions[edge] = f;
  159. return f;
  160. }
  161. static void subdivide_icosphere_mesh(
  162. const vector<Vec3>& vertex_list_in,
  163. const vector<vector<int>>& wall_list_in,
  164. vector<Vec3>& vertex_list_out,
  165. vector<vector<int>>& wall_list_out) {
  166. vertex_list_out = vertex_list_in;
  167. std::map<Edge, uint32_t> divisions; // Edge -> new vertex
  168. for (size_t i = 0; i < wall_list_in.size(); ++i)
  169. {
  170. const int f0 = wall_list_in[i][0];
  171. const int f1 = wall_list_in[i][1];
  172. const int f2 = wall_list_in[i][2];
  173. const Vec3& v0 = vertex_list_in[f0];
  174. const Vec3& v1 = vertex_list_in[f1];
  175. const Vec3& v2 = vertex_list_in[f2];
  176. const int f3 = subdivide_icosphere_edge(f0, f1, v0, v1, vertex_list_out, divisions);
  177. const int f4 = subdivide_icosphere_edge(f1, f2, v1, v2, vertex_list_out, divisions);
  178. const int f5 = subdivide_icosphere_edge(f2, f0, v2, v0, vertex_list_out, divisions);
  179. wall_list_out.push_back(vector<int>{f0, f3, f5});
  180. wall_list_out.push_back(vector<int>{f3, f1, f4});
  181. wall_list_out.push_back(vector<int>{f4, f2, f5});
  182. wall_list_out.push_back(vector<int>{f3, f4, f5});
  183. }
  184. }
  185. std::shared_ptr<GeometryObject> create_icosphere(
  186. const std::string& name, const double radius, const int subdivisions) {
  187. if (subdivisions < 1) {
  188. throw ValueError(S("Value of parameter ") + NAME_SUBDIVISIONS + " must be greater or equal to 1.");
  189. }
  190. if (subdivisions > 8) {
  191. throw ValueError(S("Value of parameter ") + NAME_SUBDIVISIONS + " must be less or equal to 8.");
  192. }
  193. vector<Vec3> vertex_list;
  194. vector<vector<int>> wall_list;
  195. init_icosphere(vertex_list, wall_list);
  196. for (int i = 0; i < subdivisions - 1; i++) {
  197. vector<Vec3> vertex_list_new;
  198. vector<vector<int>> wall_list_new;
  199. subdivide_icosphere_mesh(vertex_list, wall_list, vertex_list_new, wall_list_new);
  200. vertex_list.swap(vertex_list_new);
  201. wall_list.swap(wall_list_new);
  202. }
  203. // convert vertices to the required format and scale
  204. vector<vector<double>> double_vertices;
  205. for (const Vec3& v: vertex_list) {
  206. double_vertices.push_back(vector<double>{v.x * radius, v.y * radius, v.z * radius});
  207. }
  208. auto res = make_shared<GeometryObject>(
  209. name,
  210. double_vertices,
  211. wall_list
  212. );
  213. return res;
  214. }
  215. #if 0
  216. // keeping this code as an example on how to transform vtkPolyData
  217. std::shared_ptr<GeometryObject> create_sphere(
  218. const std::string& name, const double radius, const int resolution) {
  219. vtkNew<vtkSphereSource> sphere;
  220. sphere->SetCenter(0.0, 0.0, 0.0);
  221. sphere->SetRadius(radius);
  222. sphere->SetPhiResolution(resolution);
  223. sphere->SetThetaResolution(resolution);
  224. sphere->Update();
  225. vtkSmartPointer<vtkPolyData> polydata = sphere->GetOutput();
  226. vtkPoints* points = polydata->GetPoints();
  227. vector<vector<double>> vertex_list;
  228. int num_vertices = points->GetNumberOfPoints();
  229. for (int i = 0; i < num_vertices; i++) {
  230. double pt[3];
  231. points->GetPoint(i, pt);
  232. vertex_list.push_back(vector<double>{pt[0], pt[1], pt[2]});
  233. }
  234. vector<vector<int>> wall_list;
  235. int num_walls = polydata->GetNumberOfCells();
  236. for (int i = 0; i < num_walls; i++) {
  237. vtkCell* cell = polydata->GetCell(i);
  238. vector<int> wall;
  239. for (int k = 0; k < 3; k++) {
  240. wall.push_back(cell->GetPointId(k));
  241. }
  242. wall_list.push_back(wall);
  243. }
  244. auto res = make_shared<GeometryObject>(
  245. name,
  246. vertex_list,
  247. wall_list
  248. );
  249. return res;
  250. }
  251. #endif
  252. void validate_volumetric_mesh(
  253. std::shared_ptr<Model> model,
  254. std::shared_ptr<GeometryObject> geometry_object) {
  255. if (!model->is_initialized()) {
  256. throw RuntimeError(S("Function ") + NAME_VALIDATE_VOLUMETRIC_MESH + " may be called only after model initialization.");
  257. }
  258. if (geometry_object->geometry_object_id == GEOMETRY_OBJECT_ID_INVALID) {
  259. throw RuntimeError(S("Function ") + NAME_VALIDATE_VOLUMETRIC_MESH + " may be called on " + NAME_CLASS_GEOMETRY_OBJECT + " used during model initialization.");
  260. }
  261. const Partition& p = model->get_world()->get_partition(PARTITION_ID_INITIAL);
  262. const MCell::GeometryObject& obj = p.get_geometry_object(geometry_object->geometry_object_id);
  263. string msg = obj.validate_volumetric_mesh(p);
  264. if (msg != "") {
  265. throw RuntimeError("Validation of volumetric mesh failed with following error:\n" + msg);
  266. }
  267. }
  268. } // namespace geometry_utils
  269. } // namespace API
  270. } // namespace MCell