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+ # <motzb@hotmail.com>. 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