pilas.fisica

Package pilas :: Module fisica |
[hide private]
[frames] | no frames]

# Source Code for Module pilas.fisica

      1  # -*- encoding: utf-8 -*-
      2  # pilas engine - a video game framework.
      3  #
      4  # copyright 2010 - hugo ruscitti
      5  # license: lgplv3 (see http://www.gnu.org/licenses/lgpl.html)
      6  #
      7  # website - http://www.pilas-engine.com.ar
      8
      9  PPM = 30
     10
     11  import math
     12  import pilas
     13
     14  try:
     15      import Box2D as box2d
     16      contact_listener = box2d.b2ContactListener
     17      __enabled__ = True
     18  except ImportError:
     19      __enabled__ = False
     20 -    class Tmp:
     21          pass
     22      contact_listener = Tmp
     23
     24
     25 -def convertir_a_metros(valor):
     26      """Convierte una magnitid de pixels a metros."""
     27      return valor / float(PPM)
     28
     29 -def convertir_a_pixels(valor):
     30      """Convierte una magnitud de metros a pixels."""
     31      return valor * PPM
     32
     33
     34 -def crear_motor_fisica(area, gravedad):
     35      """Genera el motor de física Box2D.
     36
     37      :param area: El area de juego.
     38      :param gravedad: La gravedad del escenario.
     39      """
     40      if __enabled__:
     41          if obtener_version().startswith('2.0'):
     42              print "Los siento, el soporte para Box2D version 2.0 se ha eliminado."
     43              print "Por favor actualice Box2D a la version 2.1 (ver http://www.pilas-engine.com.ar)."
     44              return FisicaDeshabilitada(area, gravedad)
     45          else:
     46              return Fisica(area, gravedad)
     47      else:
     48          print "No se pudo iniciar Box2D, se deshabilita el soporte de Fisica."
     49          return FisicaDeshabilitada(area, gravedad)
     50
     51 -def obtener_version():
     52      """Obtiene la versión de la biblioteca Box2D"""
     53      return box2d.__version__
     54
     55
     56 -class Fisica(object):
     57      """Representa un simulador de mundo fisico, usando la biblioteca Box2D (version 2.1)."""
     58
     59 -    def __init__(self, area, gravedad):
     60          """Inicializa el motor de física.
     61
     62          :param area: El area del escenario, en forma de tupla.
     63          :param gravedad: La aceleración del escenario.
     64          """
     65          self.mundo = box2d.b2World(gravedad, False)
     66          self.objetosContactListener = ObjetosContactListener()
     67          self.mundo.contactListener = self.objetosContactListener
     68          self.mundo.continuousPhysics = False
     69
     70          self.area = area
     71          self.figuras_a_eliminar = []
     72
     73          self.constante_mouse = None
     74          self.crear_bordes_del_escenario()
     75
     76          self.velocidad = 1.0
     77          self.timeStep = self.velocidad/120.0
     78
     79 -    def crear_bordes_del_escenario(self):
     80          """Genera las paredes, el techo y el suelo."""
     81          self.crear_techo(self.area)
     82          self.crear_suelo(self.area)
     83          self.crear_paredes(self.area)
     84
     85 -    def reiniciar(self):
     86          """Elimina todos los objetos físicos y vuelve a crear el entorno."""
     87          lista = list(self.mundo.bodies)
     88
     89          for x in lista:
     90              self.mundo.DestroyBody(x)
     91
     92          self.crear_bordes_del_escenario()
     93
     94 -    def capturar_figura_con_el_mouse(self, figura):
     95          """Comienza a capturar una figura con el mouse.
     96
     97          :param figura: La figura a controlar con el mouse.
     98          """
     99          if self.constante_mouse:
    100              self.cuando_suelta_el_mouse()
    101
    102          self.constante_mouse = ConstanteDeMovimiento(figura)
    103
    104 -    def cuando_mueve_el_mouse(self, x, y):
    105          """Gestiona el evento de movimiento del mouse.
    106
    107          :param x: Coordenada horizontal del mouse.
    108          :param y: Coordenada vertical del mouse.
    109          """
    110          if self.constante_mouse:
    111              self.constante_mouse.mover(x, y)
    112
    113 -    def cuando_suelta_el_mouse(self):
    114          """Se ejecuta cuando se suelta el botón de mouse."""
    115          if self.constante_mouse:
    116              self.constante_mouse.eliminar()
    117              self.constante_mouse = None
    118
    119 -    def actualizar(self, velocidad=1.0):
    120          """Realiza la actualización lógica del escenario.
    121          """
    122          # TODO: eliminar el arguemnto velocidad que no se utiliza.
    123          if self.mundo:
    124              self.mundo.Step(self.timeStep, 6, 3)
    125              self._procesar_figuras_a_eliminar()
    126              self.mundo.ClearForces()
    127
    128 -    def pausar_mundo(self):
    129          """Detiene la simulación física."""
    130          if self.mundo:
    131              self.timeStep = 0
    132
    133 -    def reanudar_mundo(self):
    134          """Restaura la simulación física."""
    135          if self.mundo:
    136              self.timeStep = self.velocidad/120.0
    137
    138 -    def _procesar_figuras_a_eliminar(self):
    139          "Elimina las figuras que han sido marcadas para quitar."
    140          if self.figuras_a_eliminar:
    141              for x in self.figuras_a_eliminar:
    142                  # Solo elimina las figuras que actualmente existen.
    143                  if x in self.mundo.bodies:
    144                      self.mundo.DestroyBody(x)
    145              self.figuras_a_eliminar = []
    146
    147 -    def dibujar_figuras_sobre_lienzo(self, motor, lienzo, grosor=1):
    148          """Dibuja todas las figuras en una pizarra. Indicado para depuracion.
    149
    150          :param motor: Referencia al motor de pilas.
    151          :param lienzo: Un actor lienzo sobre el que se dibujará.
    152          :param grosor: El grosor de la linea medida en pixels.
    153          """
    154
    155          cuerpos = self.mundo.bodies
    156
    157          for cuerpo in cuerpos:
    158
    159              for fixture in cuerpo:
    160
    161                  # cuerpo.type == 0 → estatico
    162                  # cuerpo.type == 1 → kinematico
    163                  # cuerpo.type == 2 → dinamico
    164
    165                  shape = fixture.shape
    166
    167                  if isinstance(shape, box2d.b2PolygonShape):
    168                      vertices = [cuerpo.transform * v * PPM for v in shape.vertices]
    169                      vertices = [pilas.escena_actual().camara.desplazar(v) for v in vertices]
    170                      lienzo.poligono(motor, vertices, color=pilas.colores.blanco, grosor=grosor, cerrado=True)
    171                  elif isinstance(shape, box2d.b2CircleShape):
    172                      (x, y) = pilas.escena_actual().camara.desplazar(cuerpo.transform * shape.pos * PPM)
    173
    174                      lienzo.circulo(motor, x, y, shape.radius * PPM, pilas.colores.blanco, grosor=grosor)
    175                  else:
    176                      # TODO: implementar las figuras de tipo "edge" y "loop".
    177                      raise Exception("No puedo identificar el tipo de figura.")
    178
    179
    180 -    def crear_cuerpo(self, definicion_de_cuerpo):
    181          """Genera un Body de box2d.
    182
    183          :param definicion_de_cuerpo: Los parámetros de configuración de un cuerpo para Box2d.
    184          """
    185          return self.mundo.CreateBody(definicion_de_cuerpo)
    186
    187 -    def crear_suelo(self, (ancho, alto), restitucion=0):
    188          """Genera un suelo sólido para el escenario.
    189
    190          :param ancho: El ancho del suelo.
    191          :param alto: Alto del suelo.
    192          :param restitucion: El grado de conservación de energía ante una colisión.
    193          """
    194          self.suelo = Rectangulo(0, -alto/2, ancho, 2, dinamica=False, fisica=self, restitucion=restitucion)
    195
    196 -    def crear_techo(self, (ancho, alto), restitucion=0):
    197          """Genera un techo sólido para el escenario.
    198
    199          :param ancho: El ancho del techo.
    200          :param alto: Alto del techo.
    201          :param restitucion: El grado de conservación de energía ante una colisión.
    202          """
    203          self.techo = Rectangulo(0, alto/2, ancho, 2, dinamica=False, fisica=self, restitucion=restitucion)
    204
    205 -    def crear_paredes(self, (ancho, alto), restitucion=0):
    206          """Genera dos paredes para el escenario.
    207
    208          :param ancho: El ancho de las paredes.
    209          :param alto: El alto de las paredes.
    210          :param restitucion: El grado de conservación de energía ante una colisión.
    211          """
    212          self.pared_izquierda = Rectangulo(-ancho/2, 0, 2, alto, dinamica=False, fisica=self, restitucion=restitucion)
    213          self.pared_derecha = Rectangulo(ancho/2, 0, 2, alto, dinamica=False, fisica=self, restitucion=restitucion)
    214
    215 -    def eliminar_suelo(self):
    216          "Elimina el suelo del escenario."
    217          if self.suelo:
    218              self.suelo.eliminar()
    219              self.suelo = None
    220
    221 -    def eliminar_techo(self):
    222          "Elimina el techo del escenario."
    223          if self.techo:
    224              self.techo.eliminar()
    225              self.techo = None
    226
    227 -    def eliminar_paredes(self):
    228          "Elimina las dos paredes del escenario."
    229          if self.pared_izquierda:
    230              self.pared_derecha.eliminar()
    231              self.pared_izquierda.eliminar()
    232              self.pared_derecha = None
    233              self.pared_izquierda = None
    234
    235 -    def eliminar_figura(self, figura):
    236          """Elimina una figura del escenario.
    237
    238          :param figura: Figura a eliminar.
    239          """
    240          self.figuras_a_eliminar.append(figura)
    241
    242 -    def obtener_distancia_al_suelo(self, x, y, dy):
    243          """Obtiene la distancia hacia abajo desde el punto (x,y).
    244
    245          El valor de 'dy' tiene que ser positivo.
    246
    247          Si la funcion no encuentra obstaculos retornara
    248          dy, pero en paso contrario retornara un valor menor
    249          a dy.
    250
    251          :param x: posición horizontal del punto a analizar.
    252          :param y: posición vertical del punto a analizar.
    253          """
    254
    255          if dy < 0:
    256              raise Exception("El valor de 'dy' debe ser positivo, ahora vale '%f'." %(dy))
    257
    258          delta = 0
    259
    260          while delta < dy:
    261
    262              if self.obtener_cuerpos_en(x, y-delta):
    263                  return delta
    264
    265              delta += 1
    266
    267          return delta
    268
    269 -    def obtener_cuerpos_en(self, x, y):
    270          """Retorna una lista de cuerpos que se encuentran en la posicion (x, y) o retorna una lista vacia [].
    271
    272          :param x: posición horizontal del punto a analizar.
    273          :param y: posición vertical del punto a analizar.
    274          """
    275
    276          AABB = box2d.b2AABB()
    277          f = 1
    278          AABB.lowerBound = (x-f, y-f)
    279          AABB.upperBound = (x+f, y+f)
    280
    281          cuantos, cuerpos = self.mundo.Query(AABB, 2)
    282
    283          if cuantos == 0:
    284              return []
    285
    286          lista_de_cuerpos = []
    287
    288          for s in cuerpos:
    289              cuerpo = s.GetBody()
    290
    291              if s.TestPoint(cuerpo.GetXForm(), (x, y)):
    292                  lista_de_cuerpos.append(cuerpo)
    293
    294          return lista_de_cuerpos
    295
    296 -    def definir_gravedad(self, x, y):
    297          """Define la gravedad del motor de física.
    298
    299          :param x: Aceleración horizontal.
    300          :param y: Aceleración vertical.
    301          """
    302          pilas.fisica.definir_gravedad(x, y)
    303
    304 -class FisicaDeshabilitada(object):
    305      """Representa a un motor de física que no realiza acciones, y solo se habilita si box2d
    306      no funciona en el equipo.
    307      """
    308
    309 -    def __init__(self, area, gravedad=None):
    310          pass
    311
    312 -    def actualizar(self):
    313          pass
    314
    315 -    def dibujar_figuras_sobre_lienzo(self, motor, lienzo, grosor=1):
    316          pass
    317 -    def crear_bordes_del_escenario(self):
    318          pass
    319
    320 -    def reiniciar(self):
    321          pass
    322
    323 -    def capturar_figura_con_el_mouse(self, figura):
    324          pass
    325
    326 -    def cuando_mueve_el_mouse(self, x, y):
    327          pass
    328
    329 -    def cuando_suelta_el_mouse(self):
    330          pass
    331
    332 -    def pausar_mundo(self):
    333          pass
    334
    335 -    def reanudar_mundo(self):
    336          pass
    337
    338 -    def dibujar_figuras_sobre_lienzo(self, motor, lienzo, grosor=1):
    339          pass
    340
    341 -    def crear_cuerpo(self, definicion_de_cuerpo):
    342          pass
    343
    344 -    def crear_suelo(self, (ancho, alto), restitucion=0):
    345          pass
    346
    347 -    def crear_techo(self, (ancho, alto), restitucion=0):
    348          pass
    349
    350 -    def crear_paredes(self, (ancho, alto), restitucion=0):
    351          pass
    352
    353 -    def eliminar_suelo(self):
    354          pass
    355
    356 -    def eliminar_techo(self):
    357          pass
    358
    359 -    def eliminar_paredes(self):
    360          pass
    361
    362 -    def obtener_distancia_al_suelo(self, x, y, dy):
    363          pass
    364
    365 -    def eliminar_figura(self, figura):
    366          pass
    367
    368 -    def obtener_cuerpos_en(self, x, y):
    369          pass
    370
    371 -    def definir_gravedad(self, x, y):
    372          pass
    373
    374
    375 -class Figura(object):
    376      """Representa un figura que simula un cuerpo fisico.
    377
    378      Esta figura es abstracta, no está pensada para crear
    379      objetos a partir de ella. Se usa como base para el resto
    380      de las figuras cómo el Circulo o el Rectangulo simplemente."""
    381
    382 -    def __init__(self):
    383          self.id = pilas.utils.obtener_uuid()
    384
    385 -    def obtener_x(self):
    386          "Retorna la posición horizontal del cuerpo."
    387          return convertir_a_pixels(self._cuerpo.position.x)
    388
    389 -    def definir_x(self, x):
    390          """Define la posición horizontal del cuerpo.
    391
    392          :param x: El valor horizontal a definir.
    393          """
    394          self._cuerpo.position.x = convertir_a_metros(x)
    395
    396 -    def obtener_y(self):
    397          "Retorna la posición vertical del cuerpo."
    398          return convertir_a_pixels(self._cuerpo.position.y)
    399
    400 -    def definir_y(self, y):
    401          """Define la posición vertical del cuerpo.
    402
    403          :param y: El valor vertical a definir.
    404          """
    405          self._cuerpo.position.y = convertir_a_metros(y)
    406
    407 -    def definir_posicion(self, x, y):
    408          """Define la posición para el cuerpo.
    409
    410          :param x: Posición horizontal que se asignará al cuerpo.
    411          :param y: Posición vertical que se asignará al cuerpo.
    412          """
    413          self.definir_x(x)
    414          self.definir_y(y)
    415
    416 -    def obtener_rotacion(self):
    417          return - math.degrees(self._cuerpo.angle)
    418
    419 -    def definir_rotacion(self, angulo):
    420          # TODO: simplificar a la nueva api.
    421          self._cuerpo.SetXForm((self.x, self.y), math.radians(-angulo))
    422
    423 -    def impulsar(self, dx, dy):
    424          # TODO: convertir los valores dx y dy a metros.
    425          self._cuerpo.ApplyLinearImpulse((dx, dy), (0, 0))
    426
    427 -    def obtener_velocidad_lineal(self):
    428          # TODO: convertir a pixels
    429          velocidad = self._cuerpo.linearVelocity
    430          return (velocidad.x, velocidad.y)
    431
    432 -    def detener(self):
    433          """Hace que la figura regrese al reposo."""
    434          self.definir_velocidad_lineal(0, 0)
    435
    436 -    def definir_velocidad_lineal(self, dx=None, dy=None):
    437          # TODO: convertir a metros
    438          anterior_dx, anterior_dy = self.obtener_velocidad_lineal()
    439
    440          if dx is None:
    441              dx = anterior_dx
    442          if dy is None:
    443              dy = anterior_dy
    444
    445          b2vec = self._cuerpo.linearVelocity
    446          b2vec.x = dx
    447          b2vec.y = dy
    448
    449          # Añadimos eltry, porque aparece el siguiente error:
    450          # TypeError: in method 'b2Vec2___call__', argument 2 of type 'int32'
    451          try:
    452              self._cuerpo.linearVelocity(b2vec)
    453          except:
    454              pass
    455
    456 -    def empujar(self, dx=None, dy=None):
    457          # TODO: convertir a metros???
    458          self.definir_velocidad_lineal(dx, dy)
    459
    460 -    def eliminar(self):
    461          """Quita una figura de la simulación."""
    462          pilas.escena_actual().fisica.eliminar_figura(self._cuerpo)
    463
    464      x = property(obtener_x, definir_x, doc="define la posición horizontal.")
    465      y = property(obtener_y, definir_y, doc="define la posición vertical.")
    466      rotacion = property(obtener_rotacion, definir_rotacion, doc="define la rotacion.")
    467
    468 -class Circulo(Figura):
    469      """Representa un cuerpo de circulo.
    470
    471      Generalmente estas figuras se pueden construir independientes de un
    472      actor, y luego asociar.
    473
    474      Por ejemplo, podríamos crear un círculo:
    475
    476          >>> circulo_dinamico = pilas.fisica.Circulo(10, 200, 50)
    477
    478      y luego tomar un actor cualquiera, y decirle que se comporte
    479      cómo el circulo:
    480
    481          >>> mono = pilas.actores.Mono()
    482          >>> mono.imitar(circulo_dinamico)
    483      """
    484
    485 -    def __init__(self, x, y, radio, dinamica=True, densidad=1.0,
    486              restitucion=0.56, friccion=10.5, amortiguacion=0.1,
    487              fisica=None, sin_rotacion=False):
    488
    489          Figura.__init__(self)
    490
    491          x = convertir_a_metros(x)
    492          y = convertir_a_metros(y)
    493          radio = convertir_a_metros(radio)
    494
    495          if not fisica:
    496              fisica = pilas.escena_actual().fisica
    497
    498          if not dinamica:
    499              densidad = 0
    500
    501          fixture = box2d.b2FixtureDef(shape=box2d.b2CircleShape(radius=radio),
    502                                       density=densidad,
    503                                       linearDamping=amortiguacion,
    504                                       friction=friccion,
    505                                       restitution=restitucion)
    506
    507          # Agregamos un identificador para controlarlo posteriormente en las
    508          # colisiones.
    509          userData = { 'id' : self.id }
    510          fixture.userData = userData
    511
    512          if dinamica:
    513              self._cuerpo = fisica.mundo.CreateDynamicBody(position=(x, y), fixtures=fixture)
    514          else:
    515              self._cuerpo = fisica.mundo.CreateKinematicBody(position=(x, y), fixtures=fixture)
    516
    517          self._cuerpo.fixedRotation = sin_rotacion
    518
    519 -class Rectangulo(Figura):
    520      """Representa un rectángulo que puede colisionar con otras figuras.
    521
    522      Se puede crear un rectángulo independiente y luego asociarlo
    523      a un actor de la siguiente forma:
    524
    525          >>> rect = pilas.fisica.Rectangulo(50, 90, True)
    526          >>> actor = pilas.actores.Pingu()
    527          >>> actor.imitar(rect)
    528      """
    529
    530 -    def __init__(self, x, y, ancho, alto, dinamica=True, densidad=1.0,
    531              restitucion=0.5, friccion=.2, amortiguacion=0.1,
    532              fisica=None, sin_rotacion=False):
    533
    534          Figura.__init__(self)
    535
    536          x = convertir_a_metros(x)
    537          y = convertir_a_metros(y)
    538          ancho = convertir_a_metros(ancho)
    539          alto = convertir_a_metros(alto)
    540
    541          if not fisica:
    542              fisica = pilas.escena_actual().fisica
    543
    544          if not dinamica:
    545              densidad = 0
    546
    547          fixture = box2d.b2FixtureDef(shape=box2d.b2PolygonShape(box=(ancho/2, alto/2)),
    548                                       density=densidad,
    549                                       linearDamping=amortiguacion,
    550                                       friction=friccion,
    551                                       restitution=restitucion)
    552
    553          # Agregamos un identificador para controlarlo posteriormente en las
    554          # colisiones.
    555          userData = { 'id' : self.id }
    556          fixture.userData = userData
    557
    558          if dinamica:
    559              self._cuerpo = fisica.mundo.CreateDynamicBody(position=(x, y), fixtures=fixture)
    560          else:
    561              self._cuerpo = fisica.mundo.CreateKinematicBody(position=(x, y), fixtures=fixture)
    562
    563          self._cuerpo.fixedRotation = sin_rotacion
    564
    565
    566 -class Poligono(Figura):
    567      """Representa un cuerpo poligonal.
    568
    569      El poligono necesita al menos tres puntos para dibujarse, y cada
    570      uno de los puntos se tienen que ir dando en orden de las agujas
    571      del relog.
    572
    573      Por ejemplo:
    574
    575          >>> pilas.fisica.Poligono([(100, 2), (-50, 0), (-100, 100.0)])
    576
    577      """
    578
    579 -    def __init__(self, x, y, puntos, dinamica=True, densidad=1.0,
    580              restitucion=0.56, friccion=10.5, amortiguacion=0.1,
    581              fisica=None, sin_rotacion=False):
    582
    583          Figura.__init__(self)
    584
    585          if not fisica:
    586              fisica = pilas.escena_actual().fisica
    587
    588          vertices = [(convertir_a_metros(x1), convertir_a_metros(y1)) for (x1, y1) in puntos]
    589
    590          fixture = box2d.b2FixtureDef(shape=box2d.b2PolygonShape(vertices=vertices),
    591                                       density=densidad,
    592                                       linearDamping=amortiguacion,
    593                                       friction=friccion,
    594                                       restitution=restitucion)
    595
    596          userData = { 'id' : self.id }
    597          fixture.userData = userData
    598
    599          if dinamica:
    600              self._cuerpo = fisica.mundo.CreateDynamicBody(position=(0, 0), fixtures=fixture)
    601          else:
    602              self._cuerpo = fisica.mundo.CreateKinematicBody(position=(0, 0), fixtures=fixture)
    603
    604          self._cuerpo.fixedRotation = sin_rotacion
    605
    606
    607 -class ConstanteDeMovimiento():
    608      """Representa una constante de movimiento para el mouse."""
    609
    610 -    def __init__(self, figura):
    611          """Inicializa la constante.
    612
    613          :param figura: Figura a controlar desde el mouse.
    614          """
    615          mundo = pilas.escena_actual().fisica.mundo
    616          punto_captura = convertir_a_metros(figura.x), convertir_a_metros(figura.y)
    617          self.cuerpo_enlazado = mundo.CreateBody()
    618          self.figura_cuerpo = figura
    619          self.constante = mundo.CreateMouseJoint(bodyA=self.cuerpo_enlazado,
    620                  bodyB=figura._cuerpo,
    621                  target=punto_captura,
    622                  maxForce=1000.0*figura._cuerpo.mass)
    623
    624          figura._cuerpo.awake = True
    625
    626 -    def mover(self, x, y):
    627          """Realiza un movimiento de la figura.
    628
    629          :param x: Posición horizontal.
    630          :param y: Posición vertical.
    631          """
    632          self.constante.target = (convertir_a_metros(x), convertir_a_metros(y))
    633
    634 -    def eliminar(self):
    635          # Si se intenta destruir un Joint de un cuerpo que ya no existe, se cierra
    636          # la aplicación.
    637          #pilas.escena_actual().fisica.mundo.DestroyJoint(self.constante)
    638          pilas.escena_actual().fisica.mundo.DestroyBody(self.cuerpo_enlazado)
    639
    640 -class ConstanteDeDistancia():
    641      """Representa una distancia fija entre dos figuras.
    642
    643      Esta constante es útil para representar ejes o barras
    644      que sostienen dos cuerpos. Por ejemplo, un eje entre dos
    645      ruedas en un automóvil:
    646
    647          >>> circulo_1 = pilas.fisica.Circulo(-100, 0, 50)
    648          >>> circulo_2 = pilas.fisica.Circulo(100, 50, 50)
    649          >>> barra = pilas.fisica.ConstanteDeDistancia(circulo_1, circulo_2)
    650
    651      La distancia que tiene que respetarse en la misma que tienen
    652      las figuras en el momento en que se establece la constante.
    653      """
    654
    655 -    def __init__(self, figura_1, figura_2, fisica=None, con_colision=True):
    656          """Inicializa la constante.
    657
    658          :param figura_1: Una de las figuras a conectar por la constante.
    659          :param figura_2: La otra figura a conectar por la constante.
    660          :param fisica: Referencia al motor de física.
    661          :param con_colision: Indica si se permite colisión entre las dos figuras.
    662          """
    663          if not fisica:
    664              fisica = pilas.escena_actual().fisica
    665
    666          if not isinstance(figura_1, Figura) or not isinstance(figura_2, Figura):
    667              raise Exception("Las dos figuras tienen que ser objetos de la clase Figura.")
    668
    669          constante = box2d.b2DistanceJointDef()
    670          constante.Initialize(figura_1._cuerpo, figura_2._cuerpo, (0,0), (0,0))
    671          constante.collideConnected = con_colision
    672          self.constante = fisica.mundo.CreateJoint(constante)
    673
    674 -    def eliminar(self):
    675          pilas.escena_actual().fisica.mundo.DestroyJoint(self.constante_mouse)
    676
    677 -def definir_gravedad(x, y):
    678      """Define la gravedad del motor de física.
    679
    680      :param x: Aceleración horizontal.
    681      :param y: Aceleración vertical.
    682      """
    683      pilas.escena_actual().fisica.mundo.gravity = (x, y)
    684
    685 -class ObjetosContactListener(contact_listener):
    686      """Gestiona las colisiones de los objetos para ejecutar funcionés."""
    687
    688 -    def __init__(self):
    689          box2d.b2ContactListener.__init__(self)
    690
    691 -    def BeginContact(self, *args, **kwargs):
    692          objeto_colisionado_1 = args[0].fixtureA
    693          objeto_colisionado_2 = args[0].fixtureB
    694
    695          if (not objeto_colisionado_1.userData == None) and (not objeto_colisionado_2.userData == None):
    696              pilas.escena_actual().colisiones.verificar_colisiones_fisicas(objeto_colisionado_1.userData['id'],
    697                                                                            objeto_colisionado_2.userData['id'])
    698