particle_interaction.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. from DEVS import *
  2. from infinity import INFINITY
  3. import random, math, sys, getopt
  4. ''' The simulation does not end. '''
  5. def termination_condition(time, model, transitioned):
  6. time = time[0]
  7. return False
  8. class Canvas:
  9. def __init__(self, resolution):
  10. self.width = resolution[0]
  11. self.height = resolution[1]
  12. class ParticleState:
  13. def __init__(self, canvas, particle_id):
  14. self.r = random.uniform(5.0, 30.0)
  15. x = random.uniform(self.r, canvas.width - self.r)
  16. y = random.uniform(self.r, canvas.height - self.r)
  17. self.pos = (x, y)
  18. self.vel = {'x': random.uniform(-75.0, 75.0), 'y': random.uniform(-75.0, 75.0)}
  19. self.phase = "initializing" # phases: ('initializing', 'initialized', 'bouncing', 'spawning', 'selected', 'deleting', 'clicked')
  20. self.color = "red"
  21. self.prev_color = "red"
  22. self.collision_detected = False
  23. self.clicked = False
  24. self.frames_passed = 0
  25. self.frames_remaining = 0
  26. self.remaining = 0
  27. self.particle_id = particle_id
  28. class Particle(AtomicDEVS):
  29. def __init__(self, particle_id, canvas, framerate, spawn_chance, die_chance):
  30. AtomicDEVS.__init__(self, "Particle[%i]" % particle_id)
  31. self.particle_id = particle_id
  32. self.canvas = canvas
  33. self.framerate = framerate
  34. self.spawn_chance = spawn_chance
  35. self.die_chance = die_chance
  36. self.COLLISION_DETECT = self.addInPort("COLLISION_DETECT")
  37. self.POS_OUT = self.addOutPort("POS_OUT")
  38. self.SPAWNER_COMM = self.addOutPort("SPAWNER_COMM")
  39. self.COLOR_OUT = self.addOutPort("COLOR_OUT")
  40. self.state = ParticleState(canvas, particle_id)
  41. def timeAdvance(self):
  42. return self.state.remaining
  43. def outputFnc(self):
  44. output = {}
  45. if self.state.phase in ("bouncing", "spawning", "selected") or self.state.clicked:
  46. output[self.POS_OUT] = (self.particle_id, self.state.pos, self.state.r, self.state.vel)
  47. if self.state.collision_detected and self.state.phase != "spawning" and random.random() < self.spawn_chance:
  48. output[self.SPAWNER_COMM] = ["spawn_particle"]
  49. if self.state.phase == "selected" and self.state.prev_color == self.state.color:
  50. output[self.POS_OUT] = ("delete", self.particle_id)
  51. if self.state.color != self.state.prev_color:
  52. output[self.COLOR_OUT] = (self.particle_id, self.state.color)
  53. if self.state.phase == "initializing":
  54. output[self.POS_OUT] = ("created", self.particle_id, self.state.pos, self.state.r, self.state.vel)
  55. return output
  56. def intTransition(self):
  57. if self.state.phase == "initializing":
  58. self.state.phase = "initialized"
  59. self.state.remaining = 0
  60. elif self.state.phase == "initialized":
  61. self.state.phase = "bouncing"
  62. self.state.remaining = 1
  63. elif self.state.phase == "selected":
  64. if self.state.prev_color == self.state.color:
  65. self.state.phase = "deleting"
  66. self.state.remaining = INFINITY
  67. else:
  68. self.state.prev_color = self.state.color
  69. self.state.remaining = int(2.5 * self.framerate)
  70. elif self.state.prev_color != self.state.color:
  71. self.state.prev_color = self.state.color
  72. self.state.remaining = 0
  73. elif self.state.phase == "clicked":
  74. self.state.remaining = INFINITY
  75. elif random.random() < self.die_chance:
  76. self.state.vel = {'x': 0, 'y': 0}
  77. self.state.color = "yellow"
  78. self.state.phase = "selected"
  79. self.state.remaining = 0
  80. else:
  81. if self.state.frames_passed < self.framerate and self.state.phase in ("bouncing", "spawning") and self.state.color != "black":
  82. self.state.frames_passed += 1
  83. if self.state.frames_passed >= self.framerate and self.state.phase == "bouncing":
  84. self.state.color = "black"
  85. self.state.phase = "bouncing"
  86. self.state.frames_passed = 0
  87. if self.state.frames_passed >= 1 * self.framerate and self.state.phase == "spawning":
  88. self.state.color = "black"
  89. self.state.phase = "bouncing"
  90. self.state.frames_passed = 0
  91. x = self.state.pos[0]
  92. y = self.state.pos[1]
  93. if (x - self.state.r) <= 0 or x + self.state.r >= self.canvas.width:
  94. self.state.vel['x'] = -self.state.vel['x']
  95. if (y - self.state.r) <= 0 or y + self.state.r >= self.canvas.height:
  96. self.state.vel['y'] = -self.state.vel['y']
  97. self.state.pos = (self.state.pos[0] + (self.state.vel['x'] / self.framerate), self.state.pos[1] + (self.state.vel['y'] / self.framerate))
  98. if self.state.collision_detected:
  99. self.state.phase = "spawning"
  100. self.state.color = "blue"
  101. elif self.state.clicked:
  102. self.state.vel = {'x': 0, 'y': 0}
  103. self.state.phase = "clicked"
  104. self.state.color = "orange"
  105. self.state.clicked = False
  106. self.state.remaining = 1
  107. self.state.collision_detected = False
  108. return self.state
  109. def extTransition(self, inputs):
  110. if self.COLLISION_DETECT in inputs:
  111. if inputs[self.COLLISION_DETECT] == "delete_if_selected":
  112. if self.state.phase == "clicked":
  113. self.state.phase = "selected"
  114. self.state.remaining = 0
  115. elif inputs[self.COLLISION_DETECT] == "clicked":
  116. if self.state.phase == "bouncing":
  117. self.state.clicked = True
  118. self.state.remaining = 0
  119. elif self.state.phase in ("bouncing", "clicked", "deleting"):
  120. self.state.phase = "bouncing"
  121. self.state.collision_detected = True
  122. new_speed = inputs[self.COLLISION_DETECT][1]
  123. self.state.vel = {'x': new_speed[0], 'y': new_speed[1]}
  124. self.state.remaining = 0
  125. #else:
  126. #self.state.remaining -= self.elapsed
  127. return self.state
  128. def modelTransition(self, passed_values):
  129. if self.state.phase == "deleting":
  130. passed_values['to_delete'] = self
  131. return True
  132. elif self.state.phase == "initialized":
  133. passed_values['connect_particle'] = (self.particle_id, self.COLLISION_DETECT)
  134. return True
  135. return False
  136. class ParticleSpawnerState:
  137. def __init__(self):
  138. self.id_ctr = 0
  139. self.new_particle = None
  140. self.remaining = 0
  141. class ParticleSpawner(AtomicDEVS):
  142. def __init__(self, canvas, framerate, spawn_chance, die_chance):
  143. AtomicDEVS.__init__(self, "ParticleSpawner")
  144. self.canvas = canvas
  145. self.framerate = framerate
  146. self.spawn_chance = spawn_chance
  147. self.die_chance = die_chance
  148. self.REQUEST = self.addInPort("REQUEST")
  149. self.state = ParticleSpawnerState()
  150. def timeAdvance(self):
  151. return self.state.remaining
  152. def intTransition(self):
  153. self.state.new_particle = Particle(self.state.id_ctr, self.canvas, self.framerate, self.spawn_chance, self.die_chance)
  154. self.state.id_ctr += 1
  155. self.state.remaining = self.framerate
  156. return self.state
  157. def extTransition(self, inputs):
  158. self.state.remaining -= self.elapsed
  159. if self.REQUEST in inputs:
  160. self.state.new_particle = Particle(self.state.id_ctr, self.canvas, self.framerate, self.spawn_chance, self.die_chance)
  161. self.state.id_ctr += 1
  162. return self.state
  163. def modelTransition(self, passed_values):
  164. if self.state.new_particle:
  165. passed_values['new_particle'] = self.state.new_particle
  166. return True
  167. return False
  168. class PositionManagerState:
  169. def __init__(self):
  170. self.positions = {}
  171. self.collisions = set([])
  172. self.phase = "detecting"
  173. self.clicked = None
  174. self.delete_selected = False
  175. self.frames = 0
  176. self.remaining = 0
  177. def __str__(self):
  178. return str((self.phase, self.frames, self.remaining))
  179. class PositionManager(AtomicDEVS):
  180. def __init__(self, framerate):
  181. AtomicDEVS.__init__(self, "PositionManager")
  182. self.framerate = framerate
  183. self.TIME_OUT = self.addOutPort("TIME_OUT")
  184. self.POS_IN = self.addInPort("POS_IN")
  185. self.INTERRUPT = self.addInPort("INTERRUPT")
  186. self.state = PositionManagerState()
  187. def timeAdvance(self):
  188. return self.state.remaining
  189. def outputFnc(self):
  190. output = {}
  191. if self.state.collisions:
  192. output.update({self.ports['PARTICLES_OUT[%i]' % particle_id]: ("collision_detected", deltap) for (particle_id, deltap) in self.state.collisions})
  193. if self.state.clicked is not None:
  194. portname = 'PARTICLES_OUT[%i]' % self.state.clicked
  195. if portname in self.ports:
  196. output[self.ports[portname]] = "clicked"
  197. if self.state.delete_selected:
  198. output.update({self.ports['PARTICLES_OUT[%i]' % particle_id]: "delete_if_selected" for particle_id in self.state.positions})
  199. output[self.TIME_OUT] = self.state.frames
  200. return output
  201. def intTransition(self):
  202. self.state.frames += self.timeAdvance()
  203. if self.state.clicked is not None:
  204. self.state.clicked = None
  205. self.state.remaining = 1
  206. if self.state.delete_selected:
  207. self.state.delete_selected = False
  208. self.state.remaining = 1
  209. elif self.state.phase == "detecting":
  210. for k1, v1 in self.state.positions.iteritems():
  211. for k2, v2 in self.state.positions.iteritems():
  212. if k1 > k2:
  213. dx = v2[0][0] - v1[0][0]
  214. dy = v2[0][1] - v1[0][1]
  215. distance = math.sqrt(dx**2 + dy**2)
  216. if (distance < (v1[1] + v2[1])) and (k1 not in self.state.collisions or k2 not in self.state.collisions):
  217. new_speed_1 = (v2[2]['x'], v2[2]['y'])
  218. new_speed_2 = (v1[2]['x'], v1[2]['y'])
  219. self.state.collisions.add((k1, new_speed_1))
  220. self.state.collisions.add((k2, new_speed_2))
  221. if self.state.collisions:
  222. self.state.phase = "detected"
  223. self.state.remaining = 0
  224. else:
  225. self.state.phase = "detecting"
  226. self.state.remaining = 1
  227. elif self.state.phase == "detected":
  228. self.state.collisions = set([])
  229. self.state.phase = "detecting"
  230. self.state.remaining = 1
  231. return self.state
  232. def extTransition(self, inputs):
  233. self.state.remaining -= self.elapsed
  234. self.state.frames += self.elapsed
  235. if self.POS_IN in inputs:
  236. msg = inputs[self.POS_IN]
  237. if msg[0] == "created":
  238. new_particle_id = msg[1]
  239. self.addOutPort("PARTICLES_OUT[%s]" % new_particle_id)
  240. self.state.positions[new_particle_id] = (msg[2], msg[3], msg[4])
  241. elif msg[0] == "delete":
  242. self.removePort(self.ports["PARTICLES_OUT[%s]" % msg[1]])
  243. del self.state.positions[msg[1]]
  244. else:
  245. particle_id = msg[0]
  246. r = msg[2]
  247. self.state.positions[particle_id] = (msg[1], msg[2], msg[3])
  248. # TODO: Fix this, needs to be rounded to integer.
  249. if self.INTERRUPT in inputs:
  250. msg = inputs[self.INTERRUPT]
  251. if msg == "delete_selected":
  252. self.state.delete_selected = True
  253. else:
  254. self.state.clicked = int(msg)
  255. return self.state
  256. class Root(CoupledDEVS):
  257. def __init__(self, canvas=Canvas((800, 600)), framerate=30.0, spawn_chance=0.2, die_chance=0.01):
  258. CoupledDEVS.__init__(self, "Field")
  259. self.INTERRUPT = self.addInPort("INTERRUPT")
  260. self.POS_OUT = self.addOutPort("POS_OUT")
  261. self.COLOR_OUT = self.addOutPort("COLOR_OUT")
  262. self.TIME_OUT = self.addOutPort("TIME_OUT")
  263. self.particle_spawner = self.addSubModel(ParticleSpawner(canvas, framerate, spawn_chance, die_chance))
  264. self.position_manager = self.addSubModel(PositionManager(framerate))
  265. self.connectPorts(self.INTERRUPT, self.position_manager.INTERRUPT)
  266. self.connectPorts(self.position_manager.TIME_OUT, self.TIME_OUT)
  267. def modelTransition(self, passed_values):
  268. if 'new_particle' in passed_values:
  269. new_particle = passed_values['new_particle']
  270. self.addSubModel(new_particle)
  271. self.connectPorts(new_particle.POS_OUT, self.position_manager.POS_IN)
  272. self.connectPorts(new_particle.POS_OUT, self.POS_OUT)
  273. self.connectPorts(new_particle.COLOR_OUT, self.COLOR_OUT)
  274. self.connectPorts(new_particle.SPAWNER_COMM, self.particle_spawner.REQUEST)
  275. del passed_values['new_particle']
  276. if 'to_delete' in passed_values:
  277. self.removeSubModel(passed_values['to_delete'])
  278. del passed_values['to_delete']
  279. if 'connect_particle' in passed_values:
  280. particle_id, COLLISION_DETECT = passed_values['connect_particle']
  281. self.connectPorts(self.position_manager.ports['PARTICLES_OUT[%s]' % particle_id], COLLISION_DETECT)
  282. del passed_values['connect_particle']
  283. return False
  284. def select(self, imm_children):
  285. pos_mgr, particle_spawner = [None] * 2
  286. for c in imm_children:
  287. if c.getModelName() == "ParticleSpawner":
  288. particle_spawner = c
  289. if particle_spawner:
  290. return particle_spawner
  291. else:
  292. return imm_children[0]