1
2
3
4
5
6
7
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
22 contact_listener = Tmp
23
24
26 """Convierte una magnitid de pixels a metros."""
27 return valor / float(PPM)
28
30 """Convierte una magnitud de metros a pixels."""
31 return valor * PPM
32
33
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
52 """Obtiene la versión de la biblioteca Box2D"""
53 return box2d.__version__
54
55
57 """Representa un simulador de mundo fisico, usando la biblioteca Box2D (version 2.1)."""
58
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
84
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
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
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
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
127
129 """Detiene la simulación física."""
130 if self.mundo:
131 self.timeStep = 0
132
134 """Restaura la simulación física."""
135 if self.mundo:
136 self.timeStep = self.velocidad/120.0
137
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
143 if x in self.mundo.bodies:
144 self.mundo.DestroyBody(x)
145 self.figuras_a_eliminar = []
146
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
162
163
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
177 raise Exception("No puedo identificar el tipo de figura.")
178
179
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
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
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
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
216 "Elimina el suelo del escenario."
217 if self.suelo:
218 self.suelo.eliminar()
219 self.suelo = None
220
222 "Elimina el techo del escenario."
223 if self.techo:
224 self.techo.eliminar()
225 self.techo = None
226
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
236 """Elimina una figura del escenario.
237
238 :param figura: Figura a eliminar.
239 """
240 self.figuras_a_eliminar.append(figura)
241
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
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
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
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):
311
314
319
322
325
328
331
334
337
340
343
346
349
352
355
358
361
364
367
370
373
374
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
384
386 "Retorna la posición horizontal del cuerpo."
387 return convertir_a_pixels(self._cuerpo.position.x)
388
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
397 "Retorna la posición vertical del cuerpo."
398 return convertir_a_pixels(self._cuerpo.position.y)
399
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
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
417 return - math.degrees(self._cuerpo.angle)
418
420
421 self._cuerpo.SetXForm((self.x, self.y), math.radians(-angulo))
422
424
425 self._cuerpo.ApplyLinearImpulse((dx, dy), (0, 0))
426
428
429 velocidad = self._cuerpo.linearVelocity
430 return (velocidad.x, velocidad.y)
431
435
437
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
450
451 try:
452 self._cuerpo.linearVelocity(b2vec)
453 except:
454 pass
455
456 - def empujar(self, dx=None, dy=None):
459
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
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
508
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
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):
564
565
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
608 """Representa una constante de movimiento para el mouse."""
609
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
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
639
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
676
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
698