1
+ #
2
+ # This script generates a 3D galaxy from a number of parameters and stores
3
+ # it in an array. You can modify this script to store the data in a database
4
+ # or whatever your purpose is. THIS script uses the data only to generate a
5
+ # PNG with a 2D view from top of the galaxy.
6
+ #
7
+ # The algorithm used to generate the galaxy is borrowed from Ben Motz
8
+ # <[email protected] >. The original C source code for DOS (including a 3D
9
+ # viewer) can be downloaded here:
10
+ #
11
+ # http://bits.bristol.ac.uk/motz/tep/galaxy.html
12
+ #
13
+ # Unfortunately, the original python code has been lost to time and a lack of wanting-to- search-through-several-hundred-webpages-for-one-webarchive-page. Sorry, original python guy.
14
+ #
15
+ # A fair portion of the revisions and code is from /u/_Foxtrot_ on reddit. They are much better with the python-fu than I!
16
+ #
17
+
18
+ from PIL import Image
19
+ from PIL import ImageDraw
20
+ import random
21
+ import math
22
+ import sys
23
+
24
+ # Generation parameters:
25
+
26
+ # raw_input the user's desired values
27
+ # Background color of the created PNG
28
+ PNGBGCOLOR = (0 , 0 , 0 )
29
+
30
+ # Quick Filename
31
+ RAND = random .randrange (0 , 108000000000 )
32
+
33
+ # ---------------------------------------------------------------------------
34
+ NAME = raw_input ('Galaxy Name:' )
35
+
36
+ HSB = int (raw_input ('Galaxy Size Bracket <0 = 1-100, 1 = 100-1000, 2 = 1000-100000, 3 = 100000-1000000, 4 = 1000000-2000000>:' ))
37
+
38
+ NUMC = (random .randint (0 ,12 ))
39
+
40
+ if HSB == 0 : NUMSTR = random .randrange (1 , 100 )
41
+ elif HSB == 1 : NUMSTR = random .randrange (100 , 1000 )
42
+ elif HSB == 2 : NUMSTR = random .randrange (1000 , 100000 )
43
+ elif HSB == 3 : NUMSTR = random .randrange (100000 , 1000000 )
44
+ elif HSB == 4 : NUMSTR = random .randrange (1000000 , 2000000 )
45
+
46
+ print NUMSTR
47
+
48
+ NUMCLUS = NUMSTR / 70
49
+
50
+ DISCLUS = NUMCLUS / 4
51
+
52
+ GALX = int (NUMSTR / (random .randrange (8 ,20 )))
53
+
54
+ GALY = int (NUMSTR / (random .randrange (6 ,28 )))
55
+
56
+ GALZ = int (NUMSTR / (random .randrange (10 ,40 )))
57
+
58
+ CLUSRAD = NUMCLUS / 5
59
+
60
+ DISCLRAD = CLUSRAD / 5
61
+
62
+ PNGSIZEA = GALX / 5
63
+
64
+ PNGFRAMEA = PNGSIZEA / 10
65
+
66
+ PNGSIZE = float (raw_input ('X and Y Size of PNG <Default:Bad Idea>:' ) or str (PNGSIZEA ))
67
+
68
+ PNGFRAME = float (raw_input ('PNG Frame Size <Default:Bad Idea>:' ) or str (PNGFRAMEA ))
69
+
70
+ stars = []
71
+ clusters = []
72
+
73
+ star_color_dict = {
74
+ 0 : (229 , 30 , 30 ),
75
+ 1 : (203 , 30 , 26 ),
76
+ 2 : (181 , 18 , 6 ),
77
+ 3 : (200 , 39 , 13 ),
78
+ 4 : (200 , 63 , 21 ),
79
+ 5 : (222 , 75 , 10 ),
80
+ 6 : (222 , 102 , 10 ),
81
+ 7 : (222 , 137 , 10 ),
82
+ 8 : (212 , 178 , 42 ),
83
+ 9 : (210 , 188 , 38 ),
84
+ 10 : (217 , 207 , 66 ),
85
+ 11 : (217 , 207 , 66 ),
86
+ 12 : (222 , 226 , 125 ),
87
+ 13 : (222 , 226 , 125 ),
88
+ 14 : (255 , 255 , 253 ),
89
+ 15 : (255 , 255 , 255 ),
90
+ 16 : (253 , 255 , 255 ),
91
+ 17 : (222 , 243 , 255 ),
92
+ 18 : (222 , 243 , 255 ),
93
+ 19 : (140 , 176 , 225 )
94
+ }
95
+
96
+ SGX = GALX * 0.1
97
+ SGY = GALY * 0.1
98
+ SCRAD = CLUSRAD * 0.06
99
+ NUMCLUSA = NUMCLUS - DISCLUS
100
+ NUMCLUSB = NUMCLUS + DISCLUS
101
+ CLUSRADA = CLUSRAD - DISCLRAD
102
+ CLUSRADB = CLUSRAD + DISCLRAD
103
+ NUMCB = NUMC + 1
104
+
105
+ def generateClusters ():
106
+ c = 0
107
+ cx = 0
108
+ cy = 0
109
+ cz = 0
110
+ rad = random .uniform (CLUSRADA , CLUSRADB )
111
+ num = random .uniform (NUMCLUSA , NUMCLUSB )
112
+ clusters .append ((cx , cy , cz , rad , num ))
113
+ c = 1
114
+ while c < NUMCB :
115
+ # random distance from centre
116
+ dist = random .uniform (CLUSRAD , GALX )
117
+ # any rotation- clusters can be anywhere
118
+ theta = random .random () * 360
119
+ cx = math .cos (theta * math .pi / 180.0 ) * dist
120
+ cy = math .sin (theta * math .pi / 180.0 ) * dist
121
+ cz = random .random () * GALZ * 2.0 - GALZ
122
+ rad = random .uniform (CLUSRADA , CLUSRADB )
123
+ num = random .uniform (NUMCLUSA , NUMCLUSB )
124
+ # add cluster to clusters array
125
+ clusters .append ((cx , cy , cz , rad , num ))
126
+ # process next
127
+ c = c + 1
128
+ sran = 0
129
+ cran = 0
130
+
131
+ def generateStars ():
132
+
133
+ # Now generate the Hub. This places a point on or under the curve
134
+ # maxHubZ - s d^2 where s is a scale factor calculated so that z = 0 is
135
+ # at maxHubR (s = maxHubZ / maxHubR^2) AND so that minimum hub Z is at
136
+ # maximum disk Z. (Avoids edge of hub being below edge of disk)
137
+
138
+ scale = GALZ / (GALX * GALY )
139
+ i = 0
140
+ while i < NUMSTR :
141
+
142
+ # Choose a random distance from center
143
+ distX = random .random () * GALX
144
+ distY = random .random () * GALY
145
+ distXb = distX + random .uniform (0 ,SGX )
146
+ distYb = distY + random .uniform (0 ,SGY )
147
+
148
+ # Any rotation (points are not on arms)
149
+ theta = random .random () * 360
150
+
151
+ # Convert to cartesian
152
+ x = math .cos (theta * math .pi / 180.0 ) * distXb
153
+ y = math .sin (theta * math .pi / 180.0 ) * distYb
154
+ z = (random .random () * 2 - 1 ) * (GALZ - scale * distXb * distYb )
155
+
156
+ # Replaces the if/elif logic with a simple lookup. Faster and
157
+ # and easier to read.
158
+ scol = star_color_dict [random .randrange (0 ,19 )]
159
+
160
+ # Add star to the stars array
161
+ stars .append ((x , y , z , scol ))
162
+
163
+ # Process next star
164
+ i = i + 1
165
+ sran = 0
166
+
167
+ c = 0
168
+ while c < NUMCB :
169
+ for (cx , cy , cz , rad , num ) in clusters :
170
+ scale = rad / (rad * rad )
171
+ i = 0
172
+ while i < num :
173
+ dist = random .uniform (- rad ,rad )
174
+ distb = dist + random .uniform (0 ,SCRAD )
175
+ theta = random .random () * 360
176
+ # Cartesian!
177
+ x = cx + (math .cos (theta * math .pi / 180 ) * distb )
178
+ y = cy + (math .sin (theta * math .pi / 180 ) * distb )
179
+ z = (random .random () * 2 - 1 ) * ((cz + rad ) - scale * distb * distb )
180
+ scol = star_color_dict [random .randrange (0 ,19 )]
181
+ stars .append ((x , y , z , scol ))
182
+ i = i + 1
183
+ sran = 0
184
+ c = c + 1
185
+
186
+
187
+
188
+
189
+ def drawToPNG (filename ):
190
+ image = Image .new ("RGB" , (int (PNGSIZE ), int (PNGSIZE )), PNGBGCOLOR )
191
+ draw = ImageDraw .Draw (image )
192
+
193
+ # Find maximal star distance
194
+ max = 0
195
+ for (x , y , z , scol ) in stars :
196
+ if abs (x ) > max : max = x
197
+ if abs (y ) > max : max = y
198
+ if abs (z ) > max : max = z
199
+
200
+ # Calculate zoom factor to fit the galaxy to the PNG size
201
+ factor = float (PNGSIZE - PNGFRAME * 2 ) / (max * 2 )
202
+ for (x , y , z , scol ) in stars :
203
+ sx = factor * x + PNGSIZE / 2
204
+ sy = factor * y + PNGSIZE / 2
205
+ draw .point ((sx , sy ), fill = scol )
206
+
207
+ # Save the PNG
208
+ image .save (filename )
209
+ print filename
210
+
211
+
212
+ # Generate the galaxy
213
+ generateClusters ()
214
+ generateStars ()
215
+
216
+ # Save the galaxy as PNG to galaxy.png
217
+ drawToPNG ("ellipticalgalaxy" + str (RAND ) + "-" + str (NAME ) + ".png" )
218
+
219
+ # Create the galaxy's data galaxy.txt
220
+ with open ("ellipticalgalaxy" + str (RAND ) + "-" + str (NAME ) + ".txt" , "w" ) as text_file :
221
+ text_file .write ("Galaxy Number: {}" .format (RAND ))
222
+ text_file .write ("Galaxy Name: {}" .format (NAME ))
223
+ text_file .write ("Number of Clusters: {}" .format (NUMC ))
224
+ text_file .write ("Stars: {}" .format (NUMSTR ))
225
+ text_file .write ("Number of Stars per Cluster {}" .format (NUMCLUS ))
226
+ text_file .write ("Star Number Distribution per Cluster {}" .format (DISCLUS ))
227
+ text_file .write ("Galaxy X Length: {}" .format (GALX ))
228
+ text_file .write ("Galaxy Y Length: {}" .format (GALY ))
229
+ text_file .write ("Galaxy Z Length: {}" .format (GALZ ))
230
+ text_file .write ("Cluster Radius: {}" .format (CLUSRAD ))
231
+ text_file .write ("Cluster Radius Distribution: {}" .format (DISCLRAD ))
232
+ text_file .write ("Image Size: {}" .format (PNGSIZE ))
233
+ text_file .write ("Frame Size: {}" .format (PNGFRAME ))
0 commit comments