Skip to content

Commit 214289b

Browse files
committed
Added method geometry_utils.create_icosphere
1 parent 0a2df5c commit 214289b

File tree

8 files changed

+281
-3
lines changed

8 files changed

+281
-3
lines changed

CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ include_directories(
165165
"${VTK_DIR}/Filters/Core/"
166166
"${CMAKE_SOURCE_DIR}/../VTK/Filters/Modeling/"
167167
"${VTK_DIR}/Filters/Modeling/"
168+
"${CMAKE_SOURCE_DIR}/../VTK/Filters/Sources/"
169+
"${VTK_DIR}/Filters/Sources/"
168170
"${CMAKE_SOURCE_DIR}/../VTK/Rendering/Core/"
169171
"${VTK_DIR}/Rendering/Core/"
170172
"${CMAKE_SOURCE_DIR}/../VTK/Rendering/Context2D/"

libmcell/api/geometry_utils.cpp

+221
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
#include "generated/gen_geometry_utils.h"
2424

2525
#include "api/geometry_object.h"
26+
#include <vtkPlatonicSolidSource.h>
27+
#include <vtkPolyData.h>
28+
#include <vtkSmartPointer.h>
2629

2730
using namespace std;
2831

@@ -72,6 +75,224 @@ std::shared_ptr<GeometryObject> create_box(const std::string& name, const double
7275
return res;
7376
}
7477

78+
79+
80+
// used code from the link below due to absence of any reasonable library
81+
// https://github.com/caosdoar/spheres/blob/master/src/spheres.cpp
82+
// published under MIT license
83+
84+
struct Edge
85+
{
86+
uint32_t v0;
87+
uint32_t v1;
88+
89+
Edge(uint32_t v0, uint32_t v1)
90+
: v0(v0 < v1 ? v0 : v1)
91+
, v1(v0 < v1 ? v1 : v0)
92+
{
93+
}
94+
95+
bool operator <(const Edge &rhs) const
96+
{
97+
return v0 < rhs.v0 || (v0 == rhs.v0 && v1 < rhs.v1);
98+
}
99+
};
100+
101+
102+
static Vec3 normalize(const Vec3 &a)
103+
{
104+
const double lrcp = 1.0 / std::sqrt(dot(a, a));
105+
return Vec3(a.x * lrcp, a.y * lrcp, a.z * lrcp);
106+
}
107+
108+
109+
static void init_icosphere(
110+
vector<Vec3>& vertex_list,
111+
vector<vector<int>>& wall_list) {
112+
113+
vertex_list.clear();
114+
wall_list.clear();
115+
116+
const double t = (1.0 + std::sqrt(5.0)) / 2.0;
117+
118+
// Vertices
119+
vertex_list.push_back(normalize(Vec3(-1.0, t, 0.0)));
120+
vertex_list.push_back(normalize(Vec3( 1.0, t, 0.0)));
121+
vertex_list.push_back(normalize(Vec3(-1.0, -t, 0.0)));
122+
vertex_list.push_back(normalize(Vec3( 1.0, -t, 0.0)));
123+
vertex_list.push_back(normalize(Vec3(0.0, -1.0, t)));
124+
vertex_list.push_back(normalize(Vec3(0.0, 1.0, t)));
125+
vertex_list.push_back(normalize(Vec3(0.0, -1.0, -t)));
126+
vertex_list.push_back(normalize(Vec3(0.0, 1.0, -t)));
127+
vertex_list.push_back(normalize(Vec3( t, 0.0, -1.0)));
128+
vertex_list.push_back(normalize(Vec3( t, 0.0, 1.0)));
129+
vertex_list.push_back(normalize(Vec3(-t, 0.0, -1.0)));
130+
vertex_list.push_back(normalize(Vec3(-t, 0.0, 1.0)));
131+
132+
// Faces
133+
wall_list.push_back(vector<int>{0, 11, 5});
134+
wall_list.push_back(vector<int>{0, 5, 1});
135+
wall_list.push_back(vector<int>{0, 1, 7});
136+
wall_list.push_back(vector<int>{0, 7, 10});
137+
wall_list.push_back(vector<int>{0, 10, 11});
138+
wall_list.push_back(vector<int>{1, 5, 9});
139+
wall_list.push_back(vector<int>{5, 11, 4});
140+
wall_list.push_back(vector<int>{11, 10, 2});
141+
wall_list.push_back(vector<int>{10, 7, 6});
142+
wall_list.push_back(vector<int>{7, 1, 8});
143+
wall_list.push_back(vector<int>{3, 9, 4});
144+
wall_list.push_back(vector<int>{3, 4, 2});
145+
wall_list.push_back(vector<int>{3, 2, 6});
146+
wall_list.push_back(vector<int>{3, 6, 8});
147+
wall_list.push_back(vector<int>{3, 8, 9});
148+
wall_list.push_back(vector<int>{4, 9, 5});
149+
wall_list.push_back(vector<int>{2, 4, 11});
150+
wall_list.push_back(vector<int>{6, 2, 10});
151+
wall_list.push_back(vector<int>{8, 6, 7});
152+
wall_list.push_back(vector<int>{9, 8, 1});
153+
}
154+
155+
156+
int subdivide_icosphere_edge(
157+
int f0, int f1,
158+
const Vec3& v0, const Vec3& v1,
159+
vector<Vec3>& vertex_list_out,
160+
std::map<Edge, uint32_t> &io_divisions
161+
)
162+
{
163+
// required to make the mesh watertight
164+
const Edge edge(f0, f1);
165+
auto it = io_divisions.find(edge);
166+
if (it != io_divisions.end()) {
167+
return it->second;
168+
}
169+
170+
const Vec3 v = normalize(Vec3(0.5) * (v0 + v1));
171+
const uint32_t f = vertex_list_out.size();
172+
vertex_list_out.push_back(v);
173+
io_divisions[edge] = f;
174+
return f;
175+
}
176+
177+
178+
static void subdivide_icosphere_mesh(
179+
const vector<Vec3>& vertex_list_in,
180+
const vector<vector<int>>& wall_list_in,
181+
vector<Vec3>& vertex_list_out,
182+
vector<vector<int>>& wall_list_out) {
183+
184+
vertex_list_out = vertex_list_in;
185+
186+
std::map<Edge, uint32_t> divisions; // Edge -> new vertex
187+
188+
for (size_t i = 0; i < wall_list_in.size(); ++i)
189+
{
190+
const int f0 = wall_list_in[i][0];
191+
const int f1 = wall_list_in[i][1];
192+
const int f2 = wall_list_in[i][2];
193+
194+
const Vec3& v0 = vertex_list_in[f0];
195+
const Vec3& v1 = vertex_list_in[f1];
196+
const Vec3& v2 = vertex_list_in[f2];
197+
198+
const int f3 = subdivide_icosphere_edge(f0, f1, v0, v1, vertex_list_out, divisions);
199+
const int f4 = subdivide_icosphere_edge(f1, f2, v1, v2, vertex_list_out, divisions);
200+
const int f5 = subdivide_icosphere_edge(f2, f0, v2, v0, vertex_list_out, divisions);
201+
202+
wall_list_out.push_back(vector<int>{f0, f3, f5});
203+
wall_list_out.push_back(vector<int>{f3, f1, f4});
204+
wall_list_out.push_back(vector<int>{f4, f2, f5});
205+
wall_list_out.push_back(vector<int>{f3, f4, f5});
206+
}
207+
}
208+
209+
210+
std::shared_ptr<GeometryObject> create_icosphere(
211+
const std::string& name, const double radius, const int subdivisions) {
212+
213+
if (subdivisions < 1) {
214+
throw ValueError(S("Value of parameter ") + NAME_SUBDIVISIONS + " must be greater or equal to 1.");
215+
}
216+
217+
if (subdivisions > 8) {
218+
throw ValueError(S("Value of parameter ") + NAME_SUBDIVISIONS + " must be less or equal to 8.");
219+
}
220+
221+
vector<Vec3> vertex_list;
222+
vector<vector<int>> wall_list;
223+
224+
init_icosphere(vertex_list, wall_list);
225+
226+
for (int i = 0; i < subdivisions - 1; i++) {
227+
vector<Vec3> vertex_list_new;
228+
vector<vector<int>> wall_list_new;
229+
subdivide_icosphere_mesh(vertex_list, wall_list, vertex_list_new, wall_list_new);
230+
231+
vertex_list.swap(vertex_list_new);
232+
wall_list.swap(wall_list_new);
233+
}
234+
235+
// convert vertices to the required format and scale
236+
vector<vector<double>> double_vertices;
237+
for (const Vec3& v: vertex_list) {
238+
double_vertices.push_back(vector<double>{v.x * radius, v.y * radius, v.z * radius});
239+
}
240+
241+
auto res = make_shared<GeometryObject>(
242+
name,
243+
double_vertices,
244+
wall_list
245+
);
246+
247+
return res;
248+
}
249+
250+
#if 0
251+
// keeping this code as an example on how to transform vtkPolyData
252+
std::shared_ptr<GeometryObject> create_sphere(
253+
const std::string& name, const double radius, const int resolution) {
254+
255+
vtkNew<vtkSphereSource> sphere;
256+
sphere->SetCenter(0.0, 0.0, 0.0);
257+
sphere->SetRadius(radius);
258+
sphere->SetPhiResolution(resolution);
259+
sphere->SetThetaResolution(resolution);
260+
sphere->Update();
261+
262+
vtkSmartPointer<vtkPolyData> polydata = sphere->GetOutput();
263+
264+
vtkPoints* points = polydata->GetPoints();
265+
266+
vector<vector<double>> vertex_list;
267+
int num_vertices = points->GetNumberOfPoints();
268+
for (int i = 0; i < num_vertices; i++) {
269+
double pt[3];
270+
points->GetPoint(i, pt);
271+
vertex_list.push_back(vector<double>{pt[0], pt[1], pt[2]});
272+
}
273+
274+
vector<vector<int>> wall_list;
275+
int num_walls = polydata->GetNumberOfCells();
276+
for (int i = 0; i < num_walls; i++) {
277+
vtkCell* cell = polydata->GetCell(i);
278+
279+
vector<int> wall;
280+
for (int k = 0; k < 3; k++) {
281+
wall.push_back(cell->GetPointId(k));
282+
}
283+
wall_list.push_back(wall);
284+
}
285+
286+
auto res = make_shared<GeometryObject>(
287+
name,
288+
vertex_list,
289+
wall_list
290+
);
291+
292+
return res;
293+
}
294+
#endif
295+
75296
} // namespace geometry_utils
76297

77298
} // namespace API

libmcell/definition/submodules.yaml

+22-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ geometry_utils:
66
type: submodule # default type is class
77
methods:
88
- name: create_box
9-
doc: Creates a GeometryObject whose center is at (0, 0, 0).
9+
doc: Creates a GeometryObject in the shape of a cube whose center is at (0, 0, 0).
1010
return_type: GeometryObject*
1111
examples: tests/pymcell4/1400_rel_site_for_each_it/model.py
1212
params:
@@ -18,6 +18,27 @@ geometry_utils:
1818
type: float
1919
doc: Specifies length of each edge of the box in um.
2020

21+
- name: create_icosphere
22+
doc: Creates a GeometryObject in the shape of an icosphere whose center is at (0, 0, 0).
23+
return_type: GeometryObject*
24+
examples: tests/pymcell4/1110_point_release_w_create_icosphere/model.py
25+
params:
26+
- name: name
27+
type: str
28+
doc: Name of the created geometry object.
29+
30+
- name: radius
31+
type: float
32+
doc: Specifies radius of the sphere.
33+
34+
- name: subdivisions
35+
type: int
36+
min: 1
37+
max: 8
38+
doc: |
39+
Number of subdivisions from the initial icosphere.
40+
The higher this value will be the smoother the icosphere will be.
41+
Allowed range is between 1 and 8.
2142
2243
bngl_utils:
2344
doc: |

libmcell/documentation/generated/submodules.rst

+22-1
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,32 @@ Methods:
4949
* | return type: GeometryObject
5050

5151

52-
| Creates a GeometryObject whose center is at (0, 0, 0).
52+
| Creates a GeometryObject in the shape of a cube whose center is at (0, 0, 0).
5353
5454
| Example: `1400_rel_site_for_each_it/model.py <https://github.com/mcellteam/mcell_tests/tree/mcell4_dev/tests/pymcell4/1400_rel_site_for_each_it/model.py>`_
5555
5656

57+
* | **create_icosphere**
58+
59+
* | name: str
60+
| Name of the created geometry object.
61+
62+
* | radius: float
63+
| Specifies radius of the sphere.
64+
65+
* | subdivisions: int
66+
| Number of subdivisions from the initial icosphere.
67+
| The higher this value will be the smoother the icosphere will be.
68+
| Allowed range is between 1 and 8.
69+
70+
* | return type: GeometryObject
71+
72+
73+
| Creates a GeometryObject in the shape of an icosphere whose center is at (0, 0, 0).
74+
75+
| Example: `1110_point_release_w_create_icosphere/model.py <https://github.com/mcellteam/mcell_tests/tree/mcell4_dev/tests/pymcell4/1110_point_release_w_create_icosphere/model.py>`_
76+
77+
5778

5879
run_utils
5980
=========

libmcell/generated/gen_geometry_utils.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ namespace API {
3131

3232
void define_pybinding_geometry_utils(py::module& m) {
3333
m.def_submodule("geometry_utils")
34-
.def("create_box", &geometry_utils::create_box, py::arg("name"), py::arg("edge_length"), "Creates a GeometryObject whose center is at (0, 0, 0).\n- name: Name of the created geometry object.\n\n- edge_length: Specifies length of each edge of the box in um.\n\n")
34+
.def("create_box", &geometry_utils::create_box, py::arg("name"), py::arg("edge_length"), "Creates a GeometryObject in the shape of a cube whose center is at (0, 0, 0).\n- name: Name of the created geometry object.\n\n- edge_length: Specifies length of each edge of the box in um.\n\n")
35+
.def("create_icosphere", &geometry_utils::create_icosphere, py::arg("name"), py::arg("radius"), py::arg("subdivisions"), "Creates a GeometryObject in the shape of an icosphere whose center is at (0, 0, 0).\n- name: Name of the created geometry object.\n\n- radius: Specifies radius of the sphere.\n\n- subdivisions: Number of subdivisions from the initial icosphere. \nThe higher this value will be the smoother the icosphere will be.\nAllowed range is between 1 and 8.\n\n\n")
3536
;
3637
}
3738

libmcell/generated/gen_geometry_utils.h

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class PythonExportContext;
3434
namespace geometry_utils {
3535

3636
std::shared_ptr<GeometryObject> create_box(const std::string& name, const double edge_length);
37+
std::shared_ptr<GeometryObject> create_icosphere(const std::string& name, const double radius, const int subdivisions);
3738

3839
} // namespace geometry_utils
3940

libmcell/generated/gen_names.h

+3
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ const char* const NAME_COUNT = "count";
114114
const char* const NAME_COUNT_EXPRESSION = "count_expression";
115115
const char* const NAME_COUNTS = "counts";
116116
const char* const NAME_CREATE_BOX = "create_box";
117+
const char* const NAME_CREATE_ICOSPHERE = "create_icosphere";
117118
const char* const NAME_CUSTOM_DIR = "custom_dir";
118119
const char* const NAME_CUSTOM_SPACE_STEP = "custom_space_step";
119120
const char* const NAME_CUSTOM_TIME_STEP = "custom_time_step";
@@ -224,6 +225,7 @@ const char* const NAME_PRODUCT_IDS = "product_ids";
224225
const char* const NAME_PRODUCTS = "products";
225226
const char* const NAME_PROPERTIES = "properties";
226227
const char* const NAME_R = "r";
228+
const char* const NAME_RADIUS = "radius";
227229
const char* const NAME_RANDCNT = "randcnt";
228230
const char* const NAME_RANDSLR = "randslr";
229231
const char* const NAME_REACTANT_IDS = "reactant_ids";
@@ -270,6 +272,7 @@ const char* const NAME_SPECIES_LIST = "species_list";
270272
const char* const NAME_SPECIES_PATTERN = "species_pattern";
271273
const char* const NAME_STATE = "state";
272274
const char* const NAME_STATES = "states";
275+
const char* const NAME_SUBDIVISIONS = "subdivisions";
273276
const char* const NAME_SUBPARTITION_DIMENSION = "subpartition_dimension";
274277
const char* const NAME_SUBSYSTEM = "subsystem";
275278
const char* const NAME_SURFACE_CLASS = "surface_class";

libmcell/generated/mcell.pyi

+8
Original file line numberDiff line numberDiff line change
@@ -1563,6 +1563,14 @@ class geometry_utils():
15631563
) -> 'GeometryObject':
15641564
pass
15651565

1566+
def create_icosphere(
1567+
self,
1568+
name : str,
1569+
radius : float,
1570+
subdivisions : int
1571+
) -> 'GeometryObject':
1572+
pass
1573+
15661574
class run_utils():
15671575
def __init__(
15681576
self,

0 commit comments

Comments
 (0)