SHOW:
|
|
- or go back to the newest paste.
1 | import math | |
2 | ||
3 | class Circle: | |
4 | def __init__(self, radius): | |
5 | self.radius = radius | |
6 | self.contents = [] # [Placement] | |
7 | ||
8 | def add(self, circle, pos): | |
9 | self.contents.append(Placement(circle, pos)) | |
10 | ||
11 | class Placement: | |
12 | def __init__(self, circle, pos): | |
13 | self.circle = circle | |
14 | self.pos = pos | |
15 | ||
16 | # make 0 and 1 | |
17 | cache = {0: Circle(.5), 1: Circle(1.)} | |
18 | cache[1].add(cache[0], (-.5, 0)) | |
19 | ||
20 | def make(n): | |
21 | if n in cache: | |
22 | return cache[n] | |
23 | ||
24 | a = make(n - 1) | |
25 | b = make(n - 2) | |
26 | z = Circle(a.radius + b.radius) | |
27 | z.add(a, (-b.radius, 0)) | |
28 | z.add(b, (a.radius, 0)) | |
29 | ||
30 | if n - 3 >= 0: | |
31 | c = make(n - 3) | |
32 | p = getnestpos(a, b, c) | |
33 | z.add(c, p) | |
34 | ||
35 | nest(z, n - 4, b, a) | |
36 | - | |
36 | + | |
37 | cache[n] = z | |
38 | return z | |
39 | ||
40 | def nest(z, n, a, b, recurse=True): | |
41 | if n >= 0: | |
42 | ap = bp = None | |
43 | for q in z.contents: | |
44 | if q.circle is a: | |
45 | ap = q | |
46 | if q.circle is b: | |
47 | bp = q | |
48 | assert ap and bp, 'failed to find ap and bp' | |
49 | ||
50 | c = make(n) | |
51 | p = getnestpos(a, b, c, ap, bp) | |
52 | z.add(c, p) | |
53 | ||
54 | if recurse: | |
55 | nest(z, n - 1, a, c, recurse=False) | |
56 | nest(z, n - 2, c, b) | |
57 | ||
58 | def getnestpos(a, b, c, ap=None, bp=None): | |
59 | # use law of cosines to get angle between a->b and a->c | |
60 | p = a.radius ** 2 + a.radius * b.radius + a.radius * c.radius | |
61 | angle = math.acos((p - b.radius * c.radius) / (p + b.radius * c.radius)) | |
62 | # find the vector a->c | |
63 | x = (a.radius + c.radius) * math.cos(angle) - b.radius | |
64 | y = (a.radius + c.radius) * math.sin(angle) | |
65 | - | |
65 | + | |
66 | if ap and bp: | |
67 | # place the vector correctly | |
68 | vx = bp.pos[0] - ap.pos[0] | |
69 | vy = bp.pos[1] - ap.pos[1] | |
70 | angle = math.atan2(vy, vx) | |
71 | vmag = math.hypot(vx, vy) | |
72 | vx -= a.radius * vx / vmag | |
73 | vy -= a.radius * vy / vmag | |
74 | - | |
74 | + | |
75 | x, y = ( | |
76 | ap.pos[0] + vx + x * math.cos(angle) - y * math.sin(angle), | |
77 | ap.pos[1] + vy + x * math.sin(angle) + y * math.cos(angle) | |
78 | ) | |
79 | - | |
79 | + | |
80 | return (x, y) | |
81 | ||
82 | - | def printsvg(c, pos): |
82 | + | def getcolor(v,t): |
83 | - | print '<circle cx="%f" cy="%f" r="%f" fill="white" stroke="black" />' % (zoom * pos[0], zoom * pos[1], zoom * c.radius) |
83 | + | p = float(v)/float(t) |
84 | i = int(p*150)+50 | |
85 | - | printsvg(p.circle, (pos[0] + p.pos[0], pos[1] + p.pos[1])) |
85 | + | return "rgb(%d,%d,%d)" % (i,i,i) |
86 | ||
87 | def printsvg(c, pos,m): | |
88 | - | c = make(10) |
88 | + | print '<circle cx="%f" cy="%f" r="%f" fill="%s" />' % (zoom * pos[0], zoom * pos[1], zoom * c.radius, getcolor(len(c.contents),m)) |
89 | for p in c.contents: | |
90 | printsvg(p.circle, (pos[0] + p.pos[0], pos[1] + p.pos[1]),m) | |
91 | ||
92 | - | printsvg(c, (c.radius, c.radius)) |
92 | + | |
93 | total = 15 | |
94 | c = make(total) | |
95 | ||
96 | size = zoom * c.radius * 2 | |
97 | print '<svg xmlns="http://www.w3.org/2000/svg" width="%f" height="%f">' % (size, size) | |
98 | printsvg(c, (c.radius, c.radius),total) | |
99 | print '</svg>' |