Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/data/GSOC examples/Koch snowflake
blob: c60d300ccba177594b5054c423fac8c435f40ca0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# This example draws a shape called the Koch snowflake. 

import pippy, pygame, sys
from pygame.locals import *
from random import *
import math
import gtk

# always need to init first thing
pygame.init()

# XO screen is 1200x900
size = width, height = gtk.gdk.screen_width(), gtk.gdk.screen_height()

# create the window and keep track of the surface
# for drawing into
screen = pygame.display.set_mode(size)

# turn off the cursor
pygame.mouse.set_visible(False)


# Set some variables.
# Snowflakes are white!
color = (255,255,255)

# the center point of the screen
# We need it because we'll place out triangle around it.
center = width / 2, height / 2

# Side of an equilateral triangle (the one we start with).
# Here it is defined relative to the resolution of the screen.
# Practice: We could play with other values, absolute (like a = 100) or 
# relative (a = height / 2, a = width / 3, etc). What looks best for you?
a = height/1.4  

# Height of that triangle.
# Just a simple geometry formula, nothing Python-special about it.
h = int(a * math.sqrt(3) / 2) 

# These will be the vertices for the starting triangle.
# The triangle vertices are named like this:
#
#    C
#   / \
#  A _ B
#
A = center[0]- a/2, center[1] + h/3
B = A[0] + a, A[1]
# To find the third point, we need slightly more advanced math.
# If you with to understand it, you could try finding some material about trigonometry.
# We use the coordinates of vertice A to calculate where point C will be.
C = A[0] + math.cos(math.pi/3) * a, A[1] - math.sin(math.pi/3) * a

# This class will allow us to store data about a line.
class Line(object):
    def __init__(self, a = A, b = B):
        # This is how a line object will remember its points and length.
        self.A = a
        self.B = b
        self.points = [self.A, self.B]

        # We use the Pythagorean theorem to calculate the length of a line
        # from the coordinates of its points.
        self.length = math.sqrt( (a[0]-b[0])**2 + (a[1]-b[1])**2 ) 
        
        # Projection of the line on the x axis.
        # We use it to figure out the angle which the line closes with the x axis.
        projection_length = b[0] - a[0]

        # If the line is descending, the angle is negative. Trigonometry, again. 
        if b[1] > a[1]:
            self.angle = -math.acos(projection_length / self.length)
        else:
            self.angle = math.acos(projection_length / self.length)

    # To draw new shapes, an old line must be split into its thirds.
    def split(self):
        third = self.length / 3.0
        self.D = self.A[0] + math.cos(self.angle) * third, \
                 self.A[1] - math.sin(self.angle) * third
        self.E = self.A[0] + math.cos(self.angle) * 2*third, \
                 self.A[1] - math.sin(self.angle) * 2*third
        self.points.append(self.D)
        self.points.append(self.E)

# Give a line (from which we'll need the coordinates of its starting point) and
# an angle, calculate the position of a new point - the vertex of out snowflake.
# The length of its sides should be 1/3 of the length of the line from which
# it is made. 
def calculate_new_point(line, angle):
    p = line.D[0] + math.cos(angle + line.angle) * line.length / 3, \
        line.D[1] - math.sin(angle + line.angle) * line.length / 3
    return p


# The following function from a single line, like this: 
# 
# A___B
# 
# creates four lines, like this:
#
#     F
# A_D/ \E_B
#
def transform_line(line):
    line.split()
    C = calculate_new_point(line, math.pi/3)
    line1 = Line(line.A, line.D)
    line2 = Line(line.D, C)
    line3 = Line(C, line.E)
    line4 = Line(line.E, line. B)
    lines = [line1, line2, line3, line4]
    return lines

# For each line in starting_lines, call transform_line().
# Repeat "depth" times for each line created this way.
def produce_lines(starting_lines, depth):
    all_lines = starting_lines
    for i in range(depth):
        new_lines = []
        for line in all_lines:    
           new_lines += transform_line(line)
        # clearn the old lines first
        all_lines = [] 
        all_lines = new_lines
    return all_lines

# Write the lines on screen.
def draw_lines(lines):
    for line in lines:
        pygame.draw.line(screen, color, line.A, line.B)


# The color we'll use for the screen background (black).
bgcolor = (0,0,0)


# Lines for the initial triangle.
# Remember, the vertices are named like this:
#
#    C
#   / \
#  A _ B
#
# , could be changed to:
#
#    B
#   / \
#  A _ C
#
# if that felt more natural.
AC = Line(A, C)
CB = Line(C, B)
BA = Line(B, A) 
starting_lines = []
starting_lines.append(AC)
starting_lines.append(CB)
starting_lines.append(BA)

# The starting depth is 0; we just need the triangle.
depth = 0

# For displaying the instructions.
use = "Use left and right arrows"
font_size = 36
# Remember, colors are in RGB (Red, Green, Blue) system. 
font_colour = (10, 250, 20)
font = pygame.font.Font(None, font_size)
text = font.render(use, True, font_colour)
text_box = text.get_rect()
# Where the box will be placed.
text_box.top = height / 15 + 30 
text_box.left = width / 30

lines = starting_lines

while pippy.pygame.next_frame():
    for event in pygame.event.get():
        if event.type == QUIT:
            sys.exit()
        # When R arrow is pressed, go one step further.
        # This means creating new lines on each existing line.
        elif event.type == KEYDOWN and event.key == K_RIGHT and depth < 6:
            lines = produce_lines(lines, 1)
            depth = depth +1
        # When L arrow is pressed, go one step back, reducing the complexity of
        # the snowflake.
        # Basically, goes depth-1 steps forward from the starting lines.
        elif event.type == KEYDOWN and event.key == K_LEFT and depth > 0:
            depth = depth-1
            lines = produce_lines(starting_lines, depth)
        elif event.type == KEYDOWN and event.key == K_q:
            sys.exit()    
    screen.fill(bgcolor)
    # Display the current step.
    msg = "Step: " + str(depth) + "/6"
    text2 = font.render(msg , True, font_colour)
    text_box2 = text2.get_rect()
    text_box2.top = height / 15
    text_box2.left = width / 30
    # Write the instructions and the current step on the screen.
    screen.blit(text, text_box)
    screen.blit(text2, text_box2)
    # Draw the lines on the screen.
    draw_lines(lines)
    # Refresh the screen.
    pygame.display.flip()