class World(Listener, collections.MutableSequence): """ Combines physical simulation with display. """ # Simulation properties background = colorproperty('background', 'white') gravity = delegate_to('_simulation') damping = delegate_to('_simulation') adamping = delegate_to('_simulation') time = delegate_to('_simulation', readonly=True) # Special properties @lazy def add(self): return ObjectFactory(self) @lazy def track(self): return Tracker(self) @lazy def _mainloop(self): return conf.get_mainloop() @lazy def _input(self): return conf.get_input() _last_instances = [] def __init__(self, background=None, gravity=None, damping=0, adamping=0, restitution=1, friction=0, bounds=None, max_speed=None, simulation=None): self.background = background self._render_tree = RenderTree() self._objects = [] if simulation: self._simulation = simulation else: self._simulation = Simulation( gravity=gravity, damping=damping, adamping=adamping, restitution=restitution, friction=friction, max_speed=max_speed, bounds=bounds) self.is_paused = False # Populate world with user-customizable init self.init() # Saves instance self._last_instances.append(weakref.ref(self)) # Connect signals self.autoconnect() def __len__(self): return len(self._objects) def __iter__(self): return iter(self._objects) def __getitem__(self, i): return self._objects[i] def __delitem__(self, i): self.remove(self._objects[i]) def __setitem__(self, key, value): raise IndexError('cannot replace objects in world') def _add(self, obj, layer=0): """ Adds object to the world. """ if isinstance(obj, (tuple, list)): for obj in obj: self.add(obj, layer=layer) else: self._render_tree.add(obj, layer) if isinstance(obj, Body): self._simulation.add(obj) obj.world = self self._objects.append(obj) def insert(self, idx, obj): raise IndexError('cannot insert objects at specific positions. ' 'Please user world.add()') def append(self, obj, layer=0): """ Alias to World.add() """ self.add(obj, layer) def remove(self, obj): """ Remove object from the world. """ if getattr(obj, 'world', None) is self: if obj in self._render_tree: self._render_tree.remove(obj) self._simulation.remove(obj) obj.world = None else: self._render_tree.remove(obj) self._objects.remove(obj) def init(self): """ Executed after initialization. Should be overridden by sub-classes in order to populate the world with default objects during its creation. The default implementation does nothing. """ def pause(self): """ Pause physics simulation. """ self.is_paused = True def resume(self): """ Resume paused physics simulation. """ self.is_paused = False def toggle_pause(self): """ Toggles paused state. """ self.is_paused = not self.is_paused def update(self, dt): """ Main update routine. """ if not self.is_paused: self._simulation.update(dt) def run(self, timeout=None, **kwds): """ Runs simulation until the given timeout expires. Args: timeout (float): Maximum duration of simulation (in seconds). Leave None to run the simulation indefinitely. """ conf.init() conf.show_screen() self._mainloop.run(self, timeout=timeout, **kwds) def start(self, **kwds): """ Non-blocking version of World.run(). Starts simulation in a separate thread. This must be supported by the backend (pygame, for instance, does). """ if hasattr(self, '_thread'): try: self._thread.join(0) except TimeoutError: pass self._thread = threading.Thread(target=self.run, kwargs=kwds) self._thread.start() def stop(self): """ Stops simulation. """ # Forces thread to stop try: self._thread.join(0) del self._thread except (AttributeError, TimeoutError): pass self._mainloop.stop() def render_tree(self): """ Return the render tree. """ return self._render_tree
class World(EventDispatcher): '''Classe Mundo: coordena todos os objetos com uma física definida e resolve a interação entre eles. ''' def __init__(self, background=None, gravity=None, damping=0, adamping=0, rest_coeff=1, sfriction=0, dfriction=0, stop_velocity=1e-6, simulation=None): self.background = background self._render_tree = RenderTree() if simulation: self.simulation = simulation else: self.simulation = Simulation(gravity=gravity, damping=damping, adamping=adamping, rest_coeff=rest_coeff, sfriction=sfriction, dfriction=dfriction, stop_velocity=stop_velocity) # Controle de callbacks self.is_paused = False super(World, self).__init__() background = color_property('background', 'white') # Propriedades do objeto Simulation ####################################### @property def gravity(self): return self.simulation.gravity @gravity.setter def gravity(self, value): self.simulation.gravity = value @property def damping(self): return self.simulation.damping @damping.setter def damping(self, value): self.simulation.damping = value @property def adamping(self): return self.simulation.adamping @adamping.setter def adamping(self, value): self.simulation.adamping = value @property def time(self): return self.simulation.time # Gerenciamento de objetos ################################################ def add(self, obj, layer=0): '''Adiciona um novo objeto ao mundo. Exemplos -------- >>> obj = AABB((-10, 10, -10, 10)) >>> world = World() >>> world.add(obj, layer=1) ''' # Verifica se trata-se de uma lista de objetos if isinstance(obj, (tuple, list)): for obj in obj: self.add(obj, layer=layer) return # Adiciona na lista de renderização if getattr(obj, 'is_drawable', False): self._render_tree.add(obj, layer) else: self._render_tree.add(obj.visualization, layer) self.simulation.add(obj) def remove(self, obj): '''Descarta um objeto do mundo''' if getattr(obj, 'is_drawable', False): drawable = obj.visualization self._render_tree.remove(obj) self.simulation.remove(obj) else: self._render_tree.remove(obj) # Controle de eventos ##################################################### # Delegações long_press = signal('long-press', 'key', delegate='simulation') key_up = signal('key-up', 'key', delegate='simulation') key_down = signal('key-down', 'key', delegate='simulation') mouse_motion = signal('mouse-motion', delegate='simulation') mouse_click = signal('mouse-click', 'button', delegate='simulation') # Eventos privados frame_enter = signal('frame-enter') frame_skip = signal('frame-skip', num_args=1) collision = signal('collision', num_args=1) # TODO: collision_pair = signal('collision-pair', 'obj1', 'obj2', # num_args=1) # Simulação de Física ##################################################### def pause(self): '''Pausa a simulação de física''' self.is_paused = True def unpause(self): '''Resume a simulação de física''' self.is_paused = False def toggle_pause(self): '''Alterna o estado de pausa da simulação''' self.is_paused = not self.is_paused def update(self, dt): '''Rotina principal da simulação de física.''' self.trigger('frame-enter') if self.is_paused: return self.simulation.update(dt) self._render_tree.update(dt) return self.simulation.time # Laço principal ########################################################## def run(self, timeout=None, real_time=True): '''Roda a simulação de física durante o tempo 'timeout' especificado. O parâmetro `real_time` especifica se o tempo considerado consiste no tempo real ou no tempo de simulação.''' conf._mainloop_object.run(self, timeout=timeout) def stop(self): '''Finaliza o laço principal de simulação''' conf._mainloop_object.stop() def set_next_state(self, value): '''Passa a simulação para o próximo estado''' pass def get_render_tree(self): return self._render_tree ########################################################################### # Criação de objetos especiais ########################################################################### def set_bounds(self, *args, **kwds): '''Cria contorno''' # Processa argumentos hard = kwds.get('hard', True) delta = kwds.get('delta', 10000) use_poly = kwds.get('use_poly', False) color = kwds.get('color', 'black') if len(args) == 4: xmin, xmax, ymin, ymax = args elif len(args) == 1: xmin, xmax, ymin, ymax = args[0] elif not args: if 'width' not in kwds: raise TypeError('not enougth parameters to set boundaries') W, H = conf.get_window_shape() value = kwds.pop('width') try: N = len(value) if N == 2: dx, dy = value xmin, xmax = dx, W - dx ymin, ymax = dy, H - dy elif N == 4: dx, dy, dx1, dy1 = value xmin, xmax = dx, W - dx1 ymin, ymax = dy, H - dy1 else: raise ValueError('width can have 1, 2 or 4 values') except TypeError: dx = dy = value xmin, xmax = dx, W - dx ymin, ymax = dy, H - dy else: raise TypeError('invalid number of positional arguments') assert xmin < xmax and ymin < ymax, 'invalid bounds' maker = Rectangle if use_poly else AABB up = maker(bbox=(xmin - delta, xmax + delta, ymax, ymax + delta)) down = maker(bbox=(xmin - delta, xmax + delta, ymin - delta, ymin)) left = maker(bbox=(xmin - delta, xmin, ymin, ymax)) right = maker(bbox=(xmax, xmax + delta, ymin, ymax)) for box in [up, down, left, right]: box.make_static() self.add(down) self.add(up) self.add(left) self.add(right) self._bounds = (left, right, up, down) self._hard_bounds = hard
class World(EventDispatcher): '''Classe Mundo: coordena todos os objetos com uma física definida e resolve a interação entre eles. ''' def __init__(self, background=None, gravity=None, damping=0, adamping=0, restitution=1, sfriction=0, dfriction=0, bounds=None, max_speed=None, simulation=None): self.background = background self._render_tree = RenderTree() self._input = conf.get_input() if simulation: self._simulation = simulation else: self._simulation = Simulation( gravity=gravity, damping=damping, adamping=adamping, restitution=restitution, sfriction=sfriction, dfriction=dfriction, max_speed=max_speed, bounds=bounds) self.is_paused = False super(World, self).__init__() background = color_property('background', 'white') # Propriedades do objeto Simulation ####################################### gravity = delegate_to('_simulation.gravity') damping = delegate_to('_simulation.damping') adamping = delegate_to('_simulation.adamping') time = delegate_to('_simulation.time', read_only=True) # Gerenciamento de objetos ################################################ def add(self, obj, layer=0): '''Adiciona um novo objeto ao mundo. Exemplos -------- >>> obj = AABB(-10, 10, -10, 10) >>> world = World() >>> world.add(obj, layer=1) >>> obj in world True Os objetos podem ser removidos com o método remove() >>> world.remove(obj) ''' # Verifica se trata-se de uma lista de objetos if isinstance(obj, (tuple, list)): for obj in obj: self.add(obj, layer=layer) return # Adiciona na lista de renderização self._render_tree.add(obj, layer) if isinstance(obj, Body): self._simulation.add(obj) def remove(self, obj): '''Descarta um objeto do mundo''' self._render_tree.remove(obj) if isinstance(obj, Body): self._simulation.remove(obj) def __contains__(self, obj): return obj in self._render_tree or obj in self._simulation # Controle de eventos ##################################################### # Delegações long_press = signal('long-press', 'key', delegate_to='_input') key_up = signal('key-up', 'key', delegate_to='_input') key_down = signal('key-down', 'key', delegate_to='_input') mouse_motion = signal('mouse-motion', delegate_to='_input') mouse_button_up = \ signal('mouse-button-up', 'button', delegate_to='_input') mouse_button_down = \ signal('mouse-button-down', 'button', delegate_to='_input') mouse_long_press = \ signal('mouse-long-press', 'button', delegate_to='_input') # Eventos privados frame_enter = signal('frame-enter') frame_skip = signal('frame-skip', num_args=1) collision = signal('collision', num_args=1) # TODO: collision_pair = signal('collision-pair', 'obj1', 'obj2', # num_args=1) # Simulação de Física ##################################################### def pause(self): '''Pausa a simulação de física''' self.is_paused = True def unpause(self): '''Resume a simulação de física''' self.is_paused = False def toggle_pause(self): '''Alterna o estado de pausa da simulação''' self.is_paused = not self.is_paused def update(self, dt): '''Rotina principal da simulação de física.''' self.trigger('frame-enter') if self.is_paused: return self._simulation.update(dt) return self._simulation.time # Laço principal ########################################################## def run(self, timeout=None, real_time=True, **kwds): '''Roda a simulação de física durante o tempo 'timeout' especificado. O parâmetro `real_time` especifica se o tempo considerado consiste no tempo real ou no tempo de simulação.''' conf.get_mainloop().run(self, timeout=timeout, **kwds) def stop(self): '''Finaliza o laço principal de simulação''' conf.get_mainloop().stop() def set_next_state(self, value): '''Passa a simulação para o próximo estado''' pass def get_render_tree(self): return self._render_tree ########################################################################### # Criação de objetos especiais ########################################################################### def add_bounds(self, *args, **kwds): '''Cria um conjunto de AABB's que representa uma região fechada. Parameters ---------- ''' # Processa argumentos hard = kwds.get('hard', True) delta = kwds.get('delta', 10000) use_poly = kwds.get('use_poly', False) color = kwds.get('color', 'black') if len(args) == 4: xmin, xmax, ymin, ymax = args elif len(args) == 1: xmin, xmax, ymin, ymax = args[0] elif not args: if 'width' not in kwds: raise TypeError('not enougth parameters to set boundaries') W, H = conf.get_window_shape() value = kwds.pop('width') try: N = len(value) if N == 2: dx, dy = value xmin, xmax = dx, W - dx ymin, ymax = dy, H - dy elif N == 4: dx, dy, dx1, dy1 = value xmin, xmax = dx, W - dx1 ymin, ymax = dy, H - dy1 else: raise ValueError('width can have 1, 2 or 4 values') except TypeError: dx = dy = value xmin, xmax = dx, W - dx ymin, ymax = dy, H - dy else: raise TypeError('invalid number of positional arguments') assert xmin < xmax and ymin < ymax, 'invalid bounds' maker = Rectangle if use_poly else AABB up = maker(bbox=(xmin - delta, xmax + delta, ymax, ymax + delta)) down = maker(bbox=(xmin - delta, xmax + delta, ymin - delta, ymin)) left = maker(bbox=(xmin - delta, xmin, ymin, ymax)) right = maker(bbox=(xmax, xmax + delta, ymin, ymax)) for box in [up, down, left, right]: box.make_static() assert box._invmass == 0.0 self.add([up, down, left, right]) self._bounds = (left, right, up, down) self._hard_bounds = hard ########################################################################### # Funções úteis ########################################################################### def register_energy_tracker(self, auto_connect=True, ratio=True): '''Retorna uma função que rastreia a energia total do mundo a cada frame e imprime sempre que houver mudança na energia total. O comportamento padrão é imprimir a razão entre a energia inicial e a atual. Caso ``ratio=False`` imprime o valor da energia em notação científica. A função resultante é conectada automaticamente ao evento "frame-enter", a não ser que ``auto_conect=False``. ''' last = [None] if ratio: def energy_tracker(): total = self._simulation.energy_ratio() if (last[0] is None) or (abs(total - last[0]) > 1e-6): last[0] = total print('Energia total / energia inicial:', total) else: def energy_tracker(): raise NotImplementedError if auto_connect: self.listen('frame-enter', energy_tracker) return energy_tracker
class World(Listener, collections.MutableSequence): """ Combines physical simulation with display. """ # Simulation properties background = colorproperty('background', 'white') gravity = delegate_to('_simulation') damping = delegate_to('_simulation') adamping = delegate_to('_simulation') time = delegate_to('_simulation', readonly=True) # Special properties @lazy def add(self): return ObjectFactory(self) @lazy def track(self): return Tracker(self) @lazy def _mainloop(self): return conf.get_mainloop() @lazy def _input(self): return conf.get_input() _last_instances = [] def __init__(self, background=None, gravity=None, damping=0, adamping=0, restitution=1, friction=0, bounds=None, max_speed=None, simulation=None): self.background = background self._render_tree = RenderTree() self._objects = [] if simulation: self._simulation = simulation else: self._simulation = Simulation(gravity=gravity, damping=damping, adamping=adamping, restitution=restitution, friction=friction, max_speed=max_speed, bounds=bounds) self.is_paused = False # Populate world with user-customizable init self.init() # Saves instance self._last_instances.append(weakref.ref(self)) # Connect signals self.autoconnect() def __len__(self): return len(self._objects) def __iter__(self): return iter(self._objects) def __getitem__(self, i): return self._objects[i] def __delitem__(self, i): self.remove(self._objects[i]) def __setitem__(self, key, value): raise IndexError('cannot replace objects in world') def _add(self, obj, layer=0): """ Adds object to the world. """ if isinstance(obj, (tuple, list)): for obj in obj: self.add(obj, layer=layer) else: self._render_tree.add(obj, layer) if isinstance(obj, Body): self._simulation.add(obj) obj.world = self self._objects.append(obj) def insert(self, idx, obj): raise IndexError('cannot insert objects at specific positions. ' 'Please user world.add()') def append(self, obj, layer=0): """ Alias to World.add() """ self.add(obj, layer) def remove(self, obj): """ Remove object from the world. """ if getattr(obj, 'world', None) is self: if obj in self._render_tree: self._render_tree.remove(obj) self._simulation.remove(obj) obj.world = None else: self._render_tree.remove(obj) self._objects.remove(obj) def init(self): """ Executed after initialization. Should be overridden by sub-classes in order to populate the world with default objects during its creation. The default implementation does nothing. """ def pause(self): """ Pause physics simulation. """ self.is_paused = True def resume(self): """ Resume paused physics simulation. """ self.is_paused = False def toggle_pause(self): """ Toggles paused state. """ self.is_paused = not self.is_paused def update(self, dt): """ Main update routine. """ if not self.is_paused: self._simulation.update(dt) def run(self, timeout=None, **kwds): """ Runs simulation until the given timeout expires. Args: timeout (float): Maximum duration of simulation (in seconds). Leave None to run the simulation indefinitely. """ conf.init() conf.show_screen() self._mainloop.run(self, timeout=timeout, **kwds) def start(self, **kwds): """ Non-blocking version of World.run(). Starts simulation in a separate thread. This must be supported by the backend (pygame, for instance, does). """ if hasattr(self, '_thread'): try: self._thread.join(0) except TimeoutError: pass self._thread = threading.Thread(target=self.run, kwargs=kwds) self._thread.start() def stop(self): """ Stops simulation. """ # Forces thread to stop try: self._thread.join(0) del self._thread except (AttributeError, TimeoutError): pass self._mainloop.stop() def render_tree(self): """ Return the render tree. """ return self._render_tree
class World(EventDispatcher): '''Classe Mundo: coordena todos os objetos com uma física definida e resolve a interação entre eles. ''' def __init__(self, background=None, gravity=None, damping=0, adamping=0, rest_coeff=1, sfriction=0, dfriction=0, bounds=None, max_speed=None, simulation=None): self.background = background self._render_tree = RenderTree() self._input = conf.get_input() if simulation: self._simulation = simulation else: self._simulation = Simulation( gravity=gravity, damping=damping, adamping=adamping, rest_coeff=rest_coeff, sfriction=sfriction, dfriction=dfriction, max_speed=max_speed, bounds=bounds) self.is_paused = False super(World, self).__init__() background = color_property('background', 'white') # Propriedades do objeto Simulation ####################################### gravity = delegate_to('_simulation.gravity') damping = delegate_to('_simulation.damping') adamping = delegate_to('_simulation.adamping') time = delegate_to('_simulation.time', read_only=True) # Gerenciamento de objetos ################################################ def add(self, obj, layer=0): '''Adiciona um novo objeto ao mundo. Exemplos -------- >>> obj = AABB(-10, 10, -10, 10) >>> world = World() >>> world.add(obj, layer=1) ''' # Verifica se trata-se de uma lista de objetos if isinstance(obj, (tuple, list)): for obj in obj: self.add(obj, layer=layer) return # Adiciona na lista de renderização self._render_tree.add(obj.visualization, layer) if isinstance(obj, Dynamic): self._simulation.add(obj) def remove(self, obj): '''Descarta um objeto do mundo''' if getattr(obj, 'is_drawable', False): drawable = obj.visualization self._render_tree.remove(obj) self._simulation.remove(obj) else: self._render_tree.remove(obj) # Controle de eventos ##################################################### # Delegações long_press = signal('long-press', 'key', delegate_to='_input') key_up = signal('key-up', 'key', delegate_to='_input') key_down = signal('key-down', 'key', delegate_to='_input') mouse_motion = signal('mouse-motion', delegate_to='_input') mouse_click = signal('mouse-click', 'button', delegate_to='_input') # Eventos privados frame_enter = signal('frame-enter') frame_skip = signal('frame-skip', num_args=1) collision = signal('collision', num_args=1) # TODO: collision_pair = signal('collision-pair', 'obj1', 'obj2', # num_args=1) # Simulação de Física ##################################################### def pause(self): '''Pausa a simulação de física''' self.is_paused = True def unpause(self): '''Resume a simulação de física''' self.is_paused = False def toggle_pause(self): '''Alterna o estado de pausa da simulação''' self.is_paused = not self.is_paused def update(self, dt): '''Rotina principal da simulação de física.''' self.trigger('frame-enter') if self.is_paused: return self._simulation.update(dt) self._render_tree.update(dt) return self._simulation.time # Laço principal ########################################################## def run(self, timeout=None, real_time=True): '''Roda a simulação de física durante o tempo 'timeout' especificado. O parâmetro `real_time` especifica se o tempo considerado consiste no tempo real ou no tempo de simulação.''' conf.get_mainloop().run(self, timeout=timeout) def stop(self): '''Finaliza o laço principal de simulação''' conf.get_mainloop().stop() def set_next_state(self, value): '''Passa a simulação para o próximo estado''' pass def get_render_tree(self): return self._render_tree ########################################################################### # Criação de objetos especiais ########################################################################### def set_bounds(self, *args, **kwds): '''Cria contorno''' # Processa argumentos hard = kwds.get('hard', True) delta = kwds.get('delta', 10000) use_poly = kwds.get('use_poly', False) color = kwds.get('color', 'black') if len(args) == 4: xmin, xmax, ymin, ymax = args elif len(args) == 1: xmin, xmax, ymin, ymax = args[0] elif not args: if 'width' not in kwds: raise TypeError('not enougth parameters to set boundaries') W, H = conf.get_window_shape() value = kwds.pop('width') try: N = len(value) if N == 2: dx, dy = value xmin, xmax = dx, W - dx ymin, ymax = dy, H - dy elif N == 4: dx, dy, dx1, dy1 = value xmin, xmax = dx, W - dx1 ymin, ymax = dy, H - dy1 else: raise ValueError('width can have 1, 2 or 4 values') except TypeError: dx = dy = value xmin, xmax = dx, W - dx ymin, ymax = dy, H - dy else: raise TypeError('invalid number of positional arguments') assert xmin < xmax and ymin < ymax, 'invalid bounds' maker = Rectangle if use_poly else AABB up = maker(bbox=(xmin - delta, xmax + delta, ymax, ymax + delta)) down = maker(bbox=(xmin - delta, xmax + delta, ymin - delta, ymin)) left = maker(bbox=(xmin - delta, xmin, ymin, ymax)) right = maker(bbox=(xmax, xmax + delta, ymin, ymax)) for box in [up, down, left, right]: box.make_static() self.add(down) self.add(up) self.add(left) self.add(right) self._bounds = (left, right, up, down) self._hard_bounds = hard