mtgeodesicdome 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mt/geodesicdome/__init__.py +20 -0
- mt/geodesicdome/grid/__init__.py +3 -0
- mt/geodesicdome/grid/geodesicdome.py +563 -0
- mt/geodesicdome/grid/plane.py +276 -0
- mt/geodesicdome/interactive/__init__.py +30 -0
- mt/geodesicdome/interactive/mesh.py +27 -0
- mt/geodesicdome/interactive/rotation.py +57 -0
- mt/geodesicdome/interactive/sphere_map.py +218 -0
- mt/geodesicdome/interactive/viewer.py +411 -0
- mt/geodesicdome/manifold.py +91 -0
- mt/geodesicdome/projection/__init__.py +0 -0
- mt/geodesicdome/projection/equal_earth.py +29 -0
- mt/geodesicdome/projection/kavrayskiy.py +17 -0
- mt/geodesicdome/projection/projection.py +78 -0
- mt/geodesicdome/projection/wagner.py +35 -0
- mt/geodesicdome/py.typed +0 -0
- mt/geodesicdome/util.py +88 -0
- mt/geodesicdome/vertex.py +30 -0
- mtgeodesicdome-1.2.0.dist-info/METADATA +448 -0
- mtgeodesicdome-1.2.0.dist-info/RECORD +24 -0
- mtgeodesicdome-1.2.0.dist-info/WHEEL +5 -0
- mtgeodesicdome-1.2.0.dist-info/licenses/LICENSE +661 -0
- mtgeodesicdome-1.2.0.dist-info/licenses/NOTICE +60 -0
- mtgeodesicdome-1.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
# Copyright (C) 2022-2026 Masahiro Takatsuka. See the NOTICE file for attribution terms.
|
|
3
|
+
"""
|
|
4
|
+
mt.geodesicdome -- geodesic domes, grids and map projections.
|
|
5
|
+
|
|
6
|
+
Subpackages
|
|
7
|
+
mt.geodesicdome.grid GeodesicDome (icosahedron-based sphere) and Plane grids
|
|
8
|
+
mt.geodesicdome.projection map projections (Equal Earth, Kavrayskiy VII, Wagner VI/III)
|
|
9
|
+
mt.geodesicdome.interactive rotatable interactive map viewer (needs matplotlib)
|
|
10
|
+
|
|
11
|
+
The indexed geodesic data structure is described in
|
|
12
|
+
Y. Wu and M. Takatsuka, "Spherical self-organizing map using efficient indexed
|
|
13
|
+
geodesic data structure", Neural Networks 19(6-7):900-910, 2006.
|
|
14
|
+
doi:10.1016/j.neunet.2006.05.021
|
|
15
|
+
|
|
16
|
+
Note: `mt` itself is a namespace package (no __init__.py), so other
|
|
17
|
+
mt.* distributions can be installed alongside this one.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
__version__ = '1.2.0'
|
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
# Copyright (C) 2022-2026 Masahiro Takatsuka. See the NOTICE file for attribution terms.
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from numpy import array
|
|
6
|
+
|
|
7
|
+
from mt.geodesicdome import util
|
|
8
|
+
from mt.geodesicdome.manifold import Manifold
|
|
9
|
+
from mt.geodesicdome.vertex import Vertex
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class IGeodesicDome:
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GeodesicVertex(Vertex):
|
|
17
|
+
def __init__(self, latitude=None, longitude=None, coord=None, x=None, y=None, frequency=1):
|
|
18
|
+
super().__init__(x, y)
|
|
19
|
+
self.vertices: list[list[GeodesicVertex]] = [[]]
|
|
20
|
+
self.same_vertices: list[GeodesicVertex] = None
|
|
21
|
+
self.triangles: list[GeodesicVertex] = []
|
|
22
|
+
self.frequency = frequency
|
|
23
|
+
self.coord: array
|
|
24
|
+
self.projected_coord: array
|
|
25
|
+
self.latlon_coord: array
|
|
26
|
+
if (latitude is not None) and (longitude is not None):
|
|
27
|
+
self.coord = util.spherical_to_xyz(latitude, longitude)
|
|
28
|
+
self.latlon_coord = np.array([latitude, longitude])
|
|
29
|
+
elif coord.any():
|
|
30
|
+
self.coord = coord
|
|
31
|
+
self.latlon_coord = util.xyz_to_spherical(coord)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _mark_same_vertices(v: GeodesicVertex, visit_or_not: bool) -> None:
|
|
35
|
+
"""
|
|
36
|
+
This function sets the same visited status to all 'same' vertices.
|
|
37
|
+
|
|
38
|
+
:type v: GeodesicVertex
|
|
39
|
+
:param v: The vertex whose registered same vertices will have the specified visited status.
|
|
40
|
+
:type visit_or_not: bool
|
|
41
|
+
:param visit_or_not: the new visited or not status.
|
|
42
|
+
:return: None
|
|
43
|
+
"""
|
|
44
|
+
if v.same_vertices is not None and v.same_vertices:
|
|
45
|
+
for vSame in v.same_vertices:
|
|
46
|
+
vSame.visited = visit_or_not
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _partition(frequency: int, v1: GeodesicVertex, v2: GeodesicVertex) -> list[GeodesicVertex]:
|
|
50
|
+
"""
|
|
51
|
+
This function will generate (frequency - 1) new vertices between v1 and v2 so that
|
|
52
|
+
the edge (v1, v2) are equally divided.
|
|
53
|
+
|
|
54
|
+
:param frequency: The frequency of the Geodesicdome.
|
|
55
|
+
For example, frequency 2 will divide an edge into two edges by inserting one vertex.
|
|
56
|
+
:param v1: One of two vertices defining an edge.
|
|
57
|
+
:param v2: One of two vertices defining an edge.
|
|
58
|
+
:return: A list of newly created (incerted) vertices.
|
|
59
|
+
"""
|
|
60
|
+
dx = (v2.coord[0] - v1.coord[0]) / frequency
|
|
61
|
+
dy = (v2.coord[1] - v1.coord[1]) / frequency
|
|
62
|
+
dz = (v2.coord[2] - v1.coord[2]) / frequency
|
|
63
|
+
new_vertices: list[GeodesicVertex] = [None] * (frequency - 1) # GeodesicVertex[frequency - 1];
|
|
64
|
+
dxIndex = int((v2.x - v1.x) / frequency)
|
|
65
|
+
dyIndex = int((v2.y - v1.y) / frequency)
|
|
66
|
+
# print(f'(dxIndex, dyIndex): {dxIndex}, {dyIndex}')
|
|
67
|
+
for j in range(1, frequency):
|
|
68
|
+
new_coord = np.array([v1.coord[0] + j * dx, v1.coord[1] + j * dy, v1.coord[2] + j * dz])
|
|
69
|
+
new_coord = new_coord / np.linalg.norm(new_coord)
|
|
70
|
+
x = v1.x + dxIndex * j
|
|
71
|
+
y = v1.y + dyIndex * j
|
|
72
|
+
# print(f'(x, y): {x}, {y}')
|
|
73
|
+
new_vertices[j - 1] = GeodesicVertex(coord=new_coord, x=x, y=y, frequency=frequency)
|
|
74
|
+
|
|
75
|
+
return new_vertices
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _split_x_vertices(vertices_x: list[GeodesicVertex], frequency: int) -> list[GeodesicVertex]:
|
|
79
|
+
# new vector containing the new higher frequency vertex
|
|
80
|
+
vNum = len(vertices_x) + (frequency - 1) * (len(vertices_x) - 1)
|
|
81
|
+
new_vertices_x: list[GeodesicVertex] = [None] * vNum
|
|
82
|
+
v1: GeodesicVertex = vertices_x[0]
|
|
83
|
+
|
|
84
|
+
# these will shift x and y coordinates to make room for new vertices.
|
|
85
|
+
v1.x *= frequency
|
|
86
|
+
v1.y *= frequency
|
|
87
|
+
offset = v1.y
|
|
88
|
+
|
|
89
|
+
for j in range(1, len(vertices_x)):
|
|
90
|
+
# create the higher frequency vertex
|
|
91
|
+
v2: GeodesicVertex = vertices_x[j]
|
|
92
|
+
v2.x *= frequency
|
|
93
|
+
v2.y *= frequency
|
|
94
|
+
|
|
95
|
+
created: list[GeodesicVertex] = _partition(frequency, v1, v2)
|
|
96
|
+
new_vertices_x[v1.y - offset] = v1
|
|
97
|
+
for k in range(len(created)):
|
|
98
|
+
new_vertices_x[v1.y + k + 1 - offset] = created[k]
|
|
99
|
+
new_vertices_x[v1.y + frequency - offset] = v2
|
|
100
|
+
v1 = v2
|
|
101
|
+
|
|
102
|
+
return new_vertices_x
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class GeodesicDome(IGeodesicDome, Manifold):
|
|
106
|
+
"""
|
|
107
|
+
A Geodesicdome based on the Icosahedron (22 vertices and 20 triangles)
|
|
108
|
+
The base Icosahedron's vertices are arranged on a rectilinear grid as shown below.
|
|
109
|
+
This is to use an indexing scheme to quickly search vertex's i-level neighbours.
|
|
110
|
+
|
|
111
|
+
# v17 v20 v22
|
|
112
|
+
# v13 v16 v19 v21
|
|
113
|
+
# v9 v12 v15 v18
|
|
114
|
+
# v5 v8 v11 v14
|
|
115
|
+
# v2 v4 v7 v10
|
|
116
|
+
# v1 v3 v6
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
"""
|
|
120
|
+
Icosahedron's approximated average arc length
|
|
121
|
+
"""
|
|
122
|
+
_arc_length = 1.106588
|
|
123
|
+
|
|
124
|
+
def __init__(self, frequency=1):
|
|
125
|
+
super().__init__()
|
|
126
|
+
self.arcLength = GeodesicDome._arc_length # approximate the average arc length
|
|
127
|
+
# start from the base icosahedron (frequency 1); split() below multiplies this
|
|
128
|
+
self.frequency = 1
|
|
129
|
+
self.x_max = 6
|
|
130
|
+
self.y_max = 5
|
|
131
|
+
self.vertices: list[list[GeodesicVertex]] = [[]] * (self.x_max + 1)
|
|
132
|
+
|
|
133
|
+
# initialize the icosahedron, calculate all the vertex coordinates
|
|
134
|
+
dAzimuth = 2 * np.pi / 5
|
|
135
|
+
dLatitude = ((90 - 26.565) / 180) * np.pi
|
|
136
|
+
|
|
137
|
+
# set 22 vertices
|
|
138
|
+
v1 = GeodesicVertex(coord=util.spherical_to_xyz(dLatitude, 0), x=0, y=0, frequency=1)
|
|
139
|
+
v2 = GeodesicVertex(coord=util.spherical_to_xyz(0, 0), x=0, y=1, frequency=1)
|
|
140
|
+
vList1 = [v1, v2]
|
|
141
|
+
self.vertices[0] = vList1
|
|
142
|
+
|
|
143
|
+
v3 = GeodesicVertex(coord=util.spherical_to_xyz(np.pi - dLatitude, dAzimuth * 0.5), x=1, y=0, frequency=1)
|
|
144
|
+
v4 = GeodesicVertex(coord=util.spherical_to_xyz(dLatitude, dAzimuth), x=1, y=1, frequency=1)
|
|
145
|
+
v5 = GeodesicVertex(coord=util.spherical_to_xyz(0, 0), x=1, y=2, frequency=1)
|
|
146
|
+
vList2 = [v3, v4, v5]
|
|
147
|
+
self.vertices[1] = vList2
|
|
148
|
+
|
|
149
|
+
v6 = GeodesicVertex(coord=util.spherical_to_xyz(np.pi, 0), x=2, y=0, frequency=1)
|
|
150
|
+
v7 = GeodesicVertex(coord=util.spherical_to_xyz(np.pi - dLatitude, dAzimuth * 1.5), x=2, y=1, frequency=1)
|
|
151
|
+
v8 = GeodesicVertex(coord=util.spherical_to_xyz(dLatitude, dAzimuth * 2), x=2, y=2, frequency=1)
|
|
152
|
+
v9 = GeodesicVertex(coord=util.spherical_to_xyz(0, 0), x=2, y=3, frequency=1)
|
|
153
|
+
vList3 = [v6, v7, v8, v9]
|
|
154
|
+
self.vertices[2] = vList3
|
|
155
|
+
|
|
156
|
+
v10 = GeodesicVertex(coord=util.spherical_to_xyz(np.pi, 0), x=3, y=1, frequency=1)
|
|
157
|
+
v11 = GeodesicVertex(coord=util.spherical_to_xyz(np.pi - dLatitude, dAzimuth * 2.5), x=3, y=2, frequency=1)
|
|
158
|
+
v12 = GeodesicVertex(coord=util.spherical_to_xyz(dLatitude, dAzimuth * 3), x=3, y=3, frequency=1)
|
|
159
|
+
v13 = GeodesicVertex(coord=util.spherical_to_xyz(0, 0), x=3, y=4, frequency=1)
|
|
160
|
+
vList4 = [v10, v11, v12, v13]
|
|
161
|
+
self.vertices[3] = vList4
|
|
162
|
+
|
|
163
|
+
v14 = GeodesicVertex(coord=util.spherical_to_xyz(np.pi, 0), x=4, y=2, frequency=1)
|
|
164
|
+
v15 = GeodesicVertex(coord=util.spherical_to_xyz(np.pi - dLatitude, dAzimuth * 3.5), x=4, y=3, frequency=1)
|
|
165
|
+
v16 = GeodesicVertex(coord=util.spherical_to_xyz(dLatitude, dAzimuth * 4), x=4, y=4, frequency=1)
|
|
166
|
+
v17 = GeodesicVertex(coord=util.spherical_to_xyz(0, 0), x=4, y=5, frequency=1)
|
|
167
|
+
vList5 = [v14, v15, v16, v17]
|
|
168
|
+
self.vertices[4] = vList5
|
|
169
|
+
|
|
170
|
+
v18 = GeodesicVertex(coord=util.spherical_to_xyz(np.pi, 0), x=5, y=3, frequency=1)
|
|
171
|
+
v19 = GeodesicVertex(coord=util.spherical_to_xyz(np.pi - dLatitude, dAzimuth * 4.5), x=5, y=4, frequency=1)
|
|
172
|
+
v20 = GeodesicVertex(coord=util.spherical_to_xyz(dLatitude, 0), x=5, y=5, frequency=1)
|
|
173
|
+
vList6 = [v18, v19, v20]
|
|
174
|
+
self.vertices[5] = vList6
|
|
175
|
+
|
|
176
|
+
v21 = GeodesicVertex(coord=util.spherical_to_xyz(np.pi, 0), x=6, y=4, frequency=1)
|
|
177
|
+
v22 = GeodesicVertex(coord=util.spherical_to_xyz(np.pi - dLatitude, dAzimuth * 0.5), x=6, y=5, frequency=1)
|
|
178
|
+
vList7 = [v21, v22]
|
|
179
|
+
self.vertices[6] = vList7
|
|
180
|
+
|
|
181
|
+
# initialize same vertices list for vertices
|
|
182
|
+
# v1
|
|
183
|
+
v1.same_vertices = [v20]
|
|
184
|
+
|
|
185
|
+
# v2
|
|
186
|
+
v2.same_vertices = [v5, v9, v13, v17]
|
|
187
|
+
|
|
188
|
+
# v3
|
|
189
|
+
v3.same_vertices = [v22]
|
|
190
|
+
|
|
191
|
+
# v5
|
|
192
|
+
v5.same_vertices = [v2, v9, v13, v17]
|
|
193
|
+
|
|
194
|
+
# v6
|
|
195
|
+
v6.same_vertices = [v10, v14, v18, v21]
|
|
196
|
+
|
|
197
|
+
# v9
|
|
198
|
+
v9.same_vertices = [v2, v5, v13, v17]
|
|
199
|
+
|
|
200
|
+
# v10
|
|
201
|
+
v10.same_vertices = [v6, v14, v18, v21]
|
|
202
|
+
|
|
203
|
+
# v13
|
|
204
|
+
v13.same_vertices = [v2, v5, v9, v17]
|
|
205
|
+
|
|
206
|
+
# v14
|
|
207
|
+
v14.same_vertices = [v6, v10, v18, v21]
|
|
208
|
+
|
|
209
|
+
# v17
|
|
210
|
+
v17.same_vertices = [v2, v5, v9, v13]
|
|
211
|
+
|
|
212
|
+
# v18
|
|
213
|
+
v18.same_vertices = [v6, v10, v14, v21]
|
|
214
|
+
|
|
215
|
+
# v20
|
|
216
|
+
v20.same_vertices = [v1]
|
|
217
|
+
|
|
218
|
+
# v21
|
|
219
|
+
v21.same_vertices = [v6, v10, v14, v18]
|
|
220
|
+
|
|
221
|
+
# v22
|
|
222
|
+
v22.same_vertices = [v3]
|
|
223
|
+
|
|
224
|
+
if frequency > 1:
|
|
225
|
+
self.split(frequency)
|
|
226
|
+
|
|
227
|
+
def __repr__(self) -> str:
|
|
228
|
+
return f'GeodesicDome(frequency={self.frequency})'
|
|
229
|
+
|
|
230
|
+
def _find_same_vertices(self):
|
|
231
|
+
"""
|
|
232
|
+
used after split, find the same vertices for the new generated vertices
|
|
233
|
+
*
|
|
234
|
+
/ |
|
|
235
|
+
/ /*
|
|
236
|
+
/ / |
|
|
237
|
+
^ *--*--*
|
|
238
|
+
| |
|
|
239
|
+
| *
|
|
240
|
+
|
|
|
241
|
+
"""
|
|
242
|
+
first_x: list[GeodesicVertex] = self.vertices[0]
|
|
243
|
+
top_current: GeodesicVertex = first_x[len(first_x) - 1]
|
|
244
|
+
top_next: GeodesicVertex = None
|
|
245
|
+
# stage1, travel through the top vertices that has 4 same vertices points
|
|
246
|
+
# through v2, v5, v9, v13 and v17
|
|
247
|
+
i = 0
|
|
248
|
+
while i < len(top_current.same_vertices):
|
|
249
|
+
top_next = top_current.same_vertices[i]
|
|
250
|
+
x1 = top_current.x
|
|
251
|
+
y1 = top_current.y
|
|
252
|
+
x2 = top_next.x
|
|
253
|
+
y2 = top_next.y
|
|
254
|
+
top_middle: GeodesicVertex = self.get_vertex_at(x1 + 1, y1)
|
|
255
|
+
while (x1 + 1) != x2:
|
|
256
|
+
if top_middle.same_vertices is not None and top_middle.same_vertices:
|
|
257
|
+
x1 += 1
|
|
258
|
+
y2 -= 1
|
|
259
|
+
top_middle = self.get_vertex_at(x1 + 1, y1)
|
|
260
|
+
continue
|
|
261
|
+
|
|
262
|
+
top_middle_match = self.get_vertex_at(x2, y2 - 1)
|
|
263
|
+
top_middle.same_vertices = [top_middle_match]
|
|
264
|
+
top_middle_match.same_vertices = [top_middle]
|
|
265
|
+
|
|
266
|
+
x1 += 1
|
|
267
|
+
y2 -= 1
|
|
268
|
+
top_middle = self.get_vertex_at(x1 + 1, y1)
|
|
269
|
+
|
|
270
|
+
top_current = top_next
|
|
271
|
+
i += 1
|
|
272
|
+
|
|
273
|
+
# stage 2, travel through the flat top vertex
|
|
274
|
+
# through v20, v21
|
|
275
|
+
v17_x = top_next.x # v17
|
|
276
|
+
v17_y = top_next.y
|
|
277
|
+
v2 = self.vertices[0][len(self.vertices[0]) - 1]
|
|
278
|
+
for i in range(v17_x + 1, self.x_max):
|
|
279
|
+
vHigh = self.get_vertex_at(i, v17_y)
|
|
280
|
+
if vHigh.same_vertices is not None and vHigh.same_vertices:
|
|
281
|
+
continue
|
|
282
|
+
|
|
283
|
+
xdiff = vHigh.x - v17_x
|
|
284
|
+
vLow = self.get_vertex_at(0, v2.y - xdiff) if (xdiff < v2.y) else self.get_vertex_at(xdiff - v2.y, 0)
|
|
285
|
+
|
|
286
|
+
vHigh.same_vertices = [vLow]
|
|
287
|
+
vLow.same_vertices = [vHigh]
|
|
288
|
+
|
|
289
|
+
# through v21 to v22
|
|
290
|
+
last_x = self.vertices[self.x_max]
|
|
291
|
+
v22 = last_x[len(last_x) - 1]
|
|
292
|
+
v3 = v22.same_vertices[0]
|
|
293
|
+
for i in range(len(last_x) - 1 - 1, 0, -1):
|
|
294
|
+
test = last_x[i]
|
|
295
|
+
if test.same_vertices is not None and test.same_vertices:
|
|
296
|
+
continue
|
|
297
|
+
match = self.get_vertex_at(v3.x + (v22.y - test.y), 0)
|
|
298
|
+
match.same_vertices = [test]
|
|
299
|
+
test.same_vertices = [match]
|
|
300
|
+
|
|
301
|
+
# stage 3, travel through the boottom vertice that has 4 same vertices
|
|
302
|
+
# through v21, v18, v14, v10, v6
|
|
303
|
+
v21 = last_x[0]
|
|
304
|
+
v1 = v21
|
|
305
|
+
i = len(v1.same_vertices) - 1
|
|
306
|
+
while i >= 0:
|
|
307
|
+
v2 = v1.same_vertices[i] # v18
|
|
308
|
+
x1 = v1.x
|
|
309
|
+
y1 = v1.y
|
|
310
|
+
x2 = v2.x
|
|
311
|
+
y2 = v2.y
|
|
312
|
+
v3: GeodesicVertex = self.get_vertex_at(x1 - 1, y1)
|
|
313
|
+
while (x1 - 1) != x2:
|
|
314
|
+
# print(f'x1, x2, y1, y2, v2, v3 = {x1, x2, y1, y2, v2, v3}')
|
|
315
|
+
if v3.same_vertices is not None and v3.same_vertices:
|
|
316
|
+
x1 -= 1
|
|
317
|
+
y2 += 1
|
|
318
|
+
v3 = self.get_vertex_at(x1 - 1, y1)
|
|
319
|
+
continue
|
|
320
|
+
|
|
321
|
+
v4 = self.get_vertex_at(x2, y2 + 1)
|
|
322
|
+
v3.same_vertices = [v4]
|
|
323
|
+
v4.same_vertices = [v3]
|
|
324
|
+
|
|
325
|
+
x1 -= 1
|
|
326
|
+
y2 += 1
|
|
327
|
+
v3 = self.get_vertex_at(x1 - 1, y1)
|
|
328
|
+
|
|
329
|
+
v1 = v2
|
|
330
|
+
i -= 1
|
|
331
|
+
|
|
332
|
+
# increase frequency
|
|
333
|
+
def split(self, frequency):
|
|
334
|
+
"""
|
|
335
|
+
Subdivides every edge of the current dome into `frequency` segments.
|
|
336
|
+
Calls are cumulative: split(2) followed by split(3) gives frequency 6.
|
|
337
|
+
|
|
338
|
+
:param frequency: subdivision factor (an int >= 2)
|
|
339
|
+
:return: None
|
|
340
|
+
"""
|
|
341
|
+
self.frequency *= frequency
|
|
342
|
+
self.x_max *= frequency
|
|
343
|
+
self.y_max *= frequency
|
|
344
|
+
self.arcLength /= frequency
|
|
345
|
+
|
|
346
|
+
# temporary new vector for storing the vertices of the new frequency
|
|
347
|
+
new_vertices: list[list[GeodesicVertex]] = [[]] * (self.x_max + 1)
|
|
348
|
+
|
|
349
|
+
# split the first vector of x-vertices
|
|
350
|
+
new_x_vertices1: list[GeodesicVertex] = _split_x_vertices(self.vertices[0], frequency)
|
|
351
|
+
new_vertices[0] = new_x_vertices1
|
|
352
|
+
# print(f'# of columns : {len(self.vertices)}')
|
|
353
|
+
for i in range(len(self.vertices) - 1):
|
|
354
|
+
# print(f'processing index : {i}')
|
|
355
|
+
new_x_vertices2: list[GeodesicVertex] = _split_x_vertices(self.vertices[i + 1], frequency)
|
|
356
|
+
new_vertices[(i + 1) * frequency] = new_x_vertices2
|
|
357
|
+
|
|
358
|
+
# create the vertices between the 2 new vectors
|
|
359
|
+
RB = new_x_vertices2[0] # right bottom
|
|
360
|
+
LT = new_x_vertices1[len(new_x_vertices1) - 1] # left top
|
|
361
|
+
length = (LT.y - RB.y + 1)
|
|
362
|
+
|
|
363
|
+
# prepare (x-vertices * (frequency -1)) to be inserted
|
|
364
|
+
for k in range(frequency - 1):
|
|
365
|
+
index: int = i * frequency + k + 1
|
|
366
|
+
new_vertices[index] = [None] * length
|
|
367
|
+
|
|
368
|
+
# fill-in key horizontals.
|
|
369
|
+
horiindex = []
|
|
370
|
+
for n in range(len(new_x_vertices1) - 1, -1, -frequency):
|
|
371
|
+
left: GeodesicVertex = new_x_vertices1[n]
|
|
372
|
+
# print(f'left.y: {left.y}')
|
|
373
|
+
right: GeodesicVertex = None
|
|
374
|
+
right_y: int = 0
|
|
375
|
+
for m in range(len(new_x_vertices2) - 1, -1, -1):
|
|
376
|
+
testing: GeodesicVertex = new_x_vertices2[m]
|
|
377
|
+
# print(f'testing: {testing.y}')
|
|
378
|
+
if testing.y == left.y:
|
|
379
|
+
right = testing
|
|
380
|
+
right_y = m
|
|
381
|
+
horiindex.append([n, m])
|
|
382
|
+
break
|
|
383
|
+
if right is not None:
|
|
384
|
+
inserting: list[GeodesicVertex] = _partition(frequency, left, right)
|
|
385
|
+
for v in inserting:
|
|
386
|
+
new_vertices[v.x][right_y] = v
|
|
387
|
+
|
|
388
|
+
# fill-in diagonals
|
|
389
|
+
base_index = i * frequency
|
|
390
|
+
for hi in range(len(horiindex) - 1):
|
|
391
|
+
[h_index, m] = horiindex[hi]
|
|
392
|
+
for h_count in range(2, frequency + 1):
|
|
393
|
+
b1 = new_vertices[base_index][h_index - h_count]
|
|
394
|
+
t1h = len(new_vertices[base_index + h_count]) - 1 - hi * frequency if h_count < frequency else m
|
|
395
|
+
t1 = new_vertices[base_index + h_count][t1h]
|
|
396
|
+
new_points1 = _partition(h_count, b1, t1)
|
|
397
|
+
for ii in range(len(new_points1)):
|
|
398
|
+
new_v = new_points1[ii]
|
|
399
|
+
new_vertices[new_v.x][t1h - h_count + 1 + ii] = new_v
|
|
400
|
+
if h_count < frequency:
|
|
401
|
+
b2 = new_vertices[base_index + frequency - h_count][t1h - frequency]
|
|
402
|
+
t2 = new_vertices[base_index + frequency][m - frequency + h_count]
|
|
403
|
+
new_points2 = _partition(h_count, b2, t2)
|
|
404
|
+
for jj in range(len(new_points2)):
|
|
405
|
+
new_v = new_points2[jj]
|
|
406
|
+
new_vertices[new_v.x][t1h - frequency + 1 + jj] = new_v
|
|
407
|
+
|
|
408
|
+
# ready for the next iteration
|
|
409
|
+
new_x_vertices1 = new_x_vertices2
|
|
410
|
+
|
|
411
|
+
self.vertices = new_vertices
|
|
412
|
+
self._find_same_vertices()
|
|
413
|
+
|
|
414
|
+
def get_all_vertices(self) -> list[GeodesicVertex]:
|
|
415
|
+
all_vertices: list[GeodesicVertex] = []
|
|
416
|
+
for row in self.vertices:
|
|
417
|
+
all_vertices.extend(row)
|
|
418
|
+
return all_vertices
|
|
419
|
+
|
|
420
|
+
def get_vertex_at(self, x, y) -> GeodesicVertex:
|
|
421
|
+
if (x <= self.x_max) and (y <= self.y_max) and (x >= 0) and (y >= 0):
|
|
422
|
+
xVector: list[GeodesicVertex] = self.vertices[x]
|
|
423
|
+
|
|
424
|
+
# offset
|
|
425
|
+
offset = xVector[0].y
|
|
426
|
+
if y >= offset and y < (len(xVector) + offset):
|
|
427
|
+
return xVector[y - offset]
|
|
428
|
+
|
|
429
|
+
def get_number_of_vertices_per_face(self) -> int:
|
|
430
|
+
return 3
|
|
431
|
+
|
|
432
|
+
def _updateIDs(self) -> None:
|
|
433
|
+
serial_number = 0
|
|
434
|
+
for v in self.get_all_vertices():
|
|
435
|
+
v.id = serial_number
|
|
436
|
+
serial_number += 1
|
|
437
|
+
|
|
438
|
+
def _unmark_vertices(self) -> None:
|
|
439
|
+
for x_list in self.vertices:
|
|
440
|
+
for v in x_list:
|
|
441
|
+
v.visited = False
|
|
442
|
+
|
|
443
|
+
def _build_faces(self) -> list[GeodesicVertex]:
|
|
444
|
+
self._updateIDs()
|
|
445
|
+
vNum = 3 * 20 * self.frequency * self.frequency
|
|
446
|
+
self.triangles: list[GeodesicVertex] = [None] * vNum
|
|
447
|
+
index = 0
|
|
448
|
+
for i in range(len(self.vertices) - 1):
|
|
449
|
+
# print(f'builsing face at : {i}')
|
|
450
|
+
current_x: list[GeodesicVertex] = self.vertices[i]
|
|
451
|
+
next_x: list[GeodesicVertex] = self.vertices[i + 1]
|
|
452
|
+
next_bottom_y = next_x[0].y
|
|
453
|
+
for j in range(len(current_x) - 1):
|
|
454
|
+
if current_x[j].y == next_bottom_y: # found the starting point
|
|
455
|
+
for k in range(j, len(current_x) - 1):
|
|
456
|
+
# triangle 1
|
|
457
|
+
self.triangles[index] = current_x[k] # v1
|
|
458
|
+
self.triangles[index + 1] = next_x[k - j] # v2
|
|
459
|
+
self.triangles[index + 2] = next_x[k - j + 1] # v3
|
|
460
|
+
|
|
461
|
+
# triangle 2
|
|
462
|
+
self.triangles[index + 3] = self.triangles[index]
|
|
463
|
+
self.triangles[index + 4] = self.triangles[index + 2]
|
|
464
|
+
self.triangles[index + 5] = current_x[k + 1] # v4
|
|
465
|
+
index += 6
|
|
466
|
+
break
|
|
467
|
+
|
|
468
|
+
return self.triangles
|
|
469
|
+
|
|
470
|
+
def get_faces(self) -> list[GeodesicVertex]:
|
|
471
|
+
self._build_faces()
|
|
472
|
+
return self.triangles
|
|
473
|
+
|
|
474
|
+
def get_neighbours(self, v: GeodesicVertex, visit_same_vertex: bool) -> list[GeodesicVertex]:
|
|
475
|
+
v.visited = True
|
|
476
|
+
x = v.x
|
|
477
|
+
y = v.y
|
|
478
|
+
if v.same_vertices:
|
|
479
|
+
_mark_same_vertices(v, True)
|
|
480
|
+
|
|
481
|
+
# find the neighbors
|
|
482
|
+
neighbours: list[GeodesicVertex] = []
|
|
483
|
+
xArray1 = self.vertices[v.x]
|
|
484
|
+
offset1 = xArray1[0].y
|
|
485
|
+
|
|
486
|
+
# x, y + 1
|
|
487
|
+
if (y + 1) < (len(xArray1) + offset1):
|
|
488
|
+
n1 = xArray1[y + 1 - offset1]
|
|
489
|
+
if not n1.visited:
|
|
490
|
+
n1.visited = True
|
|
491
|
+
neighbours.append(n1)
|
|
492
|
+
if n1.same_vertices:
|
|
493
|
+
_mark_same_vertices(n1, True)
|
|
494
|
+
# x, y - 1
|
|
495
|
+
if (y - 1) >= offset1:
|
|
496
|
+
n4 = xArray1[y - 1 - offset1]
|
|
497
|
+
if not n4.visited:
|
|
498
|
+
n4.visited = True
|
|
499
|
+
neighbours.append(n4)
|
|
500
|
+
if n4.same_vertices:
|
|
501
|
+
_mark_same_vertices(n4, True)
|
|
502
|
+
|
|
503
|
+
if (x + 1) <= self.x_max:
|
|
504
|
+
xArray2 = self.vertices[v.x + 1]
|
|
505
|
+
offset2 = xArray2[0].y
|
|
506
|
+
|
|
507
|
+
# x+1, y
|
|
508
|
+
if y >= offset2 and y < (len(xArray2) + offset2):
|
|
509
|
+
n2 = xArray2[y - offset2]
|
|
510
|
+
if not n2.visited:
|
|
511
|
+
n2.visited = True
|
|
512
|
+
neighbours.append(n2)
|
|
513
|
+
if n2.same_vertices:
|
|
514
|
+
_mark_same_vertices(n2, True)
|
|
515
|
+
|
|
516
|
+
'''
|
|
517
|
+
x+1, y+1 is most probably v's same vertex point
|
|
518
|
+
if n3's is in v.sameVertics, markSameVertex(n3,frequency) would mark all
|
|
519
|
+
the v's same vertices, so that the program would not searching for v's same Vertices's
|
|
520
|
+
neighbor later
|
|
521
|
+
'''
|
|
522
|
+
if (y + 1) >= offset2 and (y + 1) < len(xArray2) + offset2:
|
|
523
|
+
n3 = xArray2[y + 1 - offset2]
|
|
524
|
+
if not n3.visited:
|
|
525
|
+
n3.visited = True
|
|
526
|
+
neighbours.append(n3)
|
|
527
|
+
if n3.same_vertices:
|
|
528
|
+
_mark_same_vertices(n3, True)
|
|
529
|
+
if (x - 1) >= 0:
|
|
530
|
+
xArray3 = self.vertices[v.x - 1]
|
|
531
|
+
offset3 = xArray3[0].y
|
|
532
|
+
'''
|
|
533
|
+
x - 1, y - 1 is most probably v's same vertex point
|
|
534
|
+
if n5's is in v.sameVerticsList, markSameVertex(n5,frequency) would mark all
|
|
535
|
+
the v's same vertices, so that the program would not searching for v's same Vertices's
|
|
536
|
+
neighbor later
|
|
537
|
+
'''
|
|
538
|
+
if (y - 1) < (len(xArray3) + offset3) and (y - 1) >= offset3:
|
|
539
|
+
n5 = xArray3[y - 1 - offset3]
|
|
540
|
+
if not n5.visited:
|
|
541
|
+
n5.visited = True
|
|
542
|
+
neighbours.append(n5)
|
|
543
|
+
if n5.same_vertices:
|
|
544
|
+
_mark_same_vertices(n5, True)
|
|
545
|
+
|
|
546
|
+
# x - 1, y
|
|
547
|
+
if y < (len(xArray3) + offset3) and y >= offset3:
|
|
548
|
+
n6: GeodesicVertex = xArray3[y - offset3]
|
|
549
|
+
if not n6.visited:
|
|
550
|
+
n6.visited = True
|
|
551
|
+
neighbours.append(n6)
|
|
552
|
+
if n6.same_vertices:
|
|
553
|
+
_mark_same_vertices(n6, True)
|
|
554
|
+
|
|
555
|
+
# find the neighbor of the same vertex;
|
|
556
|
+
if v.same_vertices is None or visit_same_vertex:
|
|
557
|
+
return neighbours
|
|
558
|
+
|
|
559
|
+
for vSame in v.same_vertices:
|
|
560
|
+
vSameNeighbor = self.get_neighbours(vSame, True)
|
|
561
|
+
neighbours.extend(vSameNeighbor)
|
|
562
|
+
|
|
563
|
+
return neighbours
|