Advertisement
Guest User

Untitled

a guest
Jul 1st, 2015
187
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.10 KB | None | 0 0
  1. #rect.py
  2.  
  3. from geometry import Point
  4. import random
  5.  
  6. class Rect:
  7. def __init__(self, *args):
  8. if len(args) == 4: #scalars
  9. self.left, self.top, self.right, self.bottom = args
  10. elif len(args) == 2: #Points
  11. self.__init__(*(args[0].tuple() + args[1].tuple()))
  12. else:
  13. raise Exception("Need 4 scalars or 2 points, got {}".format(len(args)))
  14.  
  15. @property
  16. def height(self):
  17. return self.bottom - self.top
  18. @property
  19. def width(self):
  20. return self.right - self.left
  21.  
  22. def contains(self, p):
  23. return self.left <= p.x < self.right and self.top <= p.y <= self.bottom
  24.  
  25. def overlaps(self, other):
  26. #returns true if line segment AB shares a common segment with line segment CD.
  27. def intersects(a,b,c,d):
  28. #returns true if x lies between a and b.
  29. def lies_between(x,a,b):
  30. return a <= x < b
  31. return lies_between(a, c, d) or lies_between(b, c, d) or lies_between(c, a, b) or lies_between(d, a, b)
  32. return intersects(self.left, self.right, other.left, other.right) and intersects(self.top, self.bottom, other.top, other.bottom)
  33.  
  34. def copy(self, **d):
  35. return Rect(*[d.get(attr, getattr(self, attr)) for attr in "left top right bottom".split()])
  36.  
  37. def corners(self):
  38. return (Point(self.left, self.top), Point(self.right, self.bottom))
  39. def tuple(self):
  40. return (self.left, self.top, self.right, self.bottom)
  41.  
  42. def map(self, f):
  43. new_corners = [p.map(f) for p in self.corners()]
  44. return Rect(*new_corners)
  45. def pmap(self, f):
  46. new_corners = [f(p) for p in self.corners()]
  47. return Rect(*new_corners)
  48.  
  49. def random_contained_point(self):
  50. return Point(random.uniform(self.left, self.right), random.uniform(self.top, self.bottom))
  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. #kdtree.py
  83.  
  84. def bisect(rect, p):
  85. if rect.height > rect.width:
  86. return [rect.copy(bottom=p.y), rect.copy(top=p.y)]
  87. else:
  88. return [rect.copy(left=p.x), rect.copy(right=p.x)]
  89.  
  90.  
  91. class KD_Tree:
  92. def __init__(self, rect):
  93. self.rect = rect
  94. self.p = None
  95. self.children = []
  96. def add(self, p):
  97. assert self.rect.contains(p)
  98. if self.p is None:
  99. self.p = p
  100. else:
  101. if not self.children:
  102. self.children = [KD_Tree(rect) for rect in bisect(self.rect, self.p)]
  103. for child in self.children:
  104. if child.rect.contains(p):
  105. child.add(p)
  106. return
  107. raise Exception("could not find branch for {}".format(p))
  108.  
  109. @staticmethod
  110. def populated(rect, points):
  111. ret = KD_Tree(rect)
  112. for p in points:
  113. ret.add(p)
  114. return ret
  115.  
  116. def iter(self):
  117. if self.p is not None:
  118. yield self.p
  119. for child in self.children:
  120. for item in child.iter():
  121. yield item
  122. def iter_in_range(self, rect):
  123. if self.p is None:
  124. return
  125. if rect.contains(self.p):
  126. yield self.p
  127. for child in self.children:
  128. if rect.overlaps(child.rect):
  129. for item in child.iter_in_range(rect):
  130. yield item
  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. #rendering.py
  172.  
  173. from geometry import Point
  174. from rect import Rect
  175. from kdtree import KD_Tree
  176. from PIL import Image, ImageDraw
  177.  
  178. def render(tree, target_size=500, margin=10):
  179. scale = target_size / float(max(tree.rect.width, tree.rect.height))
  180.  
  181. width = int(tree.rect.width * scale) + margin*2
  182. height = int(tree.rect.height * scale) + margin*2
  183. dx = tree.rect.left
  184. dy = tree.rect.top
  185. delta = Point(dx,dy)
  186.  
  187. img = Image.new("RGB", (width, height), "white")
  188. draw = ImageDraw.Draw(img)
  189.  
  190. def logical_to_screen(p):
  191. return (((p - delta) * scale) + Point(margin, margin)).map(int)
  192.  
  193. def dot(p, radius, **options):
  194. a = p - Point(radius, radius)
  195. b = p + Point(radius, radius)
  196. draw.chord(a.tuple() + b.tuple(), 0, 359, **options)
  197.  
  198. def box(rect, **options):
  199. draw.rectangle(rect.tuple(), **options)
  200.  
  201. def render_node(node):
  202. if node.p is not None:
  203. dot(logical_to_screen(node.p), 2, fill="black")
  204. for child in node.children:
  205. box(child.rect.pmap(logical_to_screen), outline="black")
  206. render_node(child)
  207.  
  208. render_node(tree)
  209. radius = 1
  210. search_vector = Point(radius, radius)
  211. for p in tree.iter():
  212. area = Rect(p - search_vector, p + search_vector)
  213. for neighbor in tree.iter_in_range(area):
  214. draw.line(logical_to_screen(p).tuple() + logical_to_screen(neighbor).tuple(), fill="red")
  215.  
  216. return img
  217.  
  218.  
  219.  
  220.  
  221.  
  222.  
  223.  
  224.  
  225.  
  226.  
  227.  
  228.  
  229.  
  230.  
  231.  
  232.  
  233.  
  234.  
  235.  
  236.  
  237.  
  238.  
  239.  
  240.  
  241.  
  242.  
  243.  
  244.  
  245.  
  246.  
  247.  
  248.  
  249. #animation.py
  250. #prerequisites: ImageMagick (http://www.imagemagick.org/script/index.php)
  251. import itertools
  252. import os
  253. import os.path
  254. import subprocess
  255. import shutil
  256. import math
  257.  
  258. def generate_unused_folder_name():
  259. base = "temp_{}"
  260. for i in itertools.count():
  261. name = base.format(i)
  262. if not os.path.exists(name):
  263. return name
  264.  
  265. def make_gif(imgs, **kwargs):
  266. """creates a gif in the current directory, composed of the given images.
  267. parameters:
  268. - imgs: a list of PIL image objects.
  269. optional parameters:
  270. - name: the name of the gif file.
  271. - default: "output.gif"
  272. - delay: the number of 'ticks' between frames. 1 tick == 10 ms == 0.01 seconds. anything smaller than 2 will cause display problems.
  273. - default: 2
  274. - delete_temp_files: True if the temporary directory containing each individual frame should be deleted, False otherwise.
  275. - default: True
  276. """
  277. name = kwargs.get("name", "output.gif")
  278. delay = kwargs.get("delay", 2)
  279. dir_name = generate_unused_folder_name()
  280. #create directory and move into it
  281. os.mkdir(dir_name)
  282. os.chdir(dir_name)
  283.  
  284.  
  285. #create images. Use leading zeroes to ensure lexicographic order.
  286. num_digits = max(1, int(math.log(len(imgs))))
  287. for i, img in enumerate(imgs):
  288. img.save("img_{:0{padding}}.png".format(i, padding=num_digits))
  289.  
  290. #create gif
  291. #cmd = "imgconvert -delay {} img_*.png -layers optimize output.gif".format(delay)
  292. cmd = ["imgconvert", "-delay", str(delay), "img_*.png", "-layers", "optimize", "output.gif"]
  293. subprocess.call(cmd)
  294.  
  295. #move gif out of temp directory
  296. shutil.copyfile("output.gif", "../{}".format(name))
  297.  
  298. #return to original directory, and delete temp
  299. os.chdir("..")
  300. if kwargs.get("delete_temp_files", True):
  301. shutil.rmtree(dir_name)
  302.  
  303.  
  304.  
  305.  
  306.  
  307.  
  308.  
  309.  
  310.  
  311.  
  312.  
  313.  
  314.  
  315.  
  316.  
  317.  
  318.  
  319.  
  320.  
  321.  
  322.  
  323.  
  324.  
  325.  
  326.  
  327.  
  328.  
  329.  
  330.  
  331.  
  332.  
  333.  
  334.  
  335.  
  336.  
  337.  
  338.  
  339.  
  340.  
  341. #main.py
  342. import math
  343. import random
  344. from kdtree import KD_Tree
  345. from rect import Rect
  346. from rendering import render
  347. from animation import make_gif
  348. from geometry import distance
  349.  
  350. def linear_interpolate(start, end, t):
  351. return start * (1-t) + end * t
  352.  
  353. def sinusoidal_interpolate(start, end, t):
  354. t = math.sin(t * math.pi / 2)
  355. return linear_interpolate(start, end, t)
  356.  
  357. def create_path(rand_p_func, frames):
  358. def first(cond, iterable):
  359. for item in iterable:
  360. if cond(item):
  361. return item
  362. raise Exception("no items in iterable satisfied condition")
  363.  
  364. num_waypoints = random.randint(2, 5)
  365. waypoints = [rand_p_func() for i in range(num_waypoints)]
  366. #close the loop by making the last waypoint the same as the first
  367. waypoints.append(waypoints[0])
  368.  
  369. path_lengths = [distance(a,b) for a,b in zip(waypoints, waypoints[1:])]
  370. total_length = sum(path_lengths)
  371.  
  372. waypoint_times = [sum(path_lengths[:i])/total_length for i in range(len(path_lengths))] + [1]
  373.  
  374. #waypoint_times = [0] + sorted([random.random() for i in range(num_waypoints-2)]) + [1]
  375. assert len(waypoints) == len(waypoint_times)
  376. for i in range(frames):
  377. t = float(i) / (frames-1)
  378. waypoint_idx = first(lambda idx: waypoint_times[idx+1] >= t, range(len(waypoint_times)-1))
  379. start_point, end_point = waypoints[waypoint_idx], waypoints[waypoint_idx+1]
  380. start_t = waypoint_times[waypoint_idx]
  381. end_t = waypoint_times[waypoint_idx+1]
  382. assert start_t <= t <= end_t
  383. leg_length = end_t - start_t
  384. leg_t = float(t - start_t) / leg_length
  385. yield sinusoidal_interpolate(start_point, end_point, leg_t)
  386.  
  387. def transpose(matrix):
  388. return zip(*matrix)
  389.  
  390.  
  391. def main():
  392. field = Rect(0,0,10,10)
  393. frames = 200
  394. size = 200
  395. points = 50
  396. print "generating paths..."
  397. paths = [create_path(field.random_contained_point, frames) for i in range(points)]
  398. print "done."
  399. point_frames = transpose(paths)
  400. print "rendering..."
  401. images = [render(KD_Tree.populated(field, points), size) for points in point_frames]
  402. print "done. Composing..."
  403. make_gif(images, delay=4, delete_temp_files=False)
  404. print "done."
  405.  
  406. main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement