シェルスクリプトマガジン

Pythonあれこれ(Vol.99掲載)

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第29回では、物体の動きや力の作用など、現実世界の物理法則を数値的に計算してシミュレーションする「物理演算」にチャレンジします。

シェルスクリプトマガジン Vol.99は以下のリンク先でご購入できます。

図6 playground.pyファイルの最後の部分にあるコード

def main():
    demo = PhysicsDemo()
    demo.run()

if __name__ == "__main__":
    doprof = 0
    if not doprof:
        main()
    else:
        import cProfile
        import pstats

        prof = cProfile.run("main()", "profile.prof")
        stats = pstats.Stats("profile.prof")
        stats.strip_dirs()
        stats.sort_stats("cumulative", "time", "calls")
        stats.print_stats(30)

図7 PhysicsDemoクラスのrun()メソッドのコード

def run(self):
    while self.running:
        self.loop()

図8 PhysicsDemoクラスのloop()メソッドのコード

def loop(self):
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            self.running = False
(略)
            self.space.gravity = g.rotated_degrees(45)

    mpos = pygame.mouse.get_pos()
    if pygame.key.get_mods() & \
          pygame.KMOD_SHIFT and pygame.mouse.get_pressed()[2]:
        p = self.flipyv(Vec2d(*mpos))
        self.poly_points.append(p)
    hit = self.space.point_query_nearest(
        self.flipyv(Vec2d(*mpos)), 0, pm.ShapeFilter()
    )
    if hit != None:
        self.shape_to_remove = hit.shape
    else:
        self.shape_to_remove = None

    ### Update physics
    if self.run_physics:
        x = 1
        dt = 1.0 / 60.0 / x
        for x in range(x):
            self.space.step(dt)
            for ball in self.balls:
                # ball.body.reset_forces()
                pass
            for poly in self.polys:
                # poly.body.reset_forces()
                pass

    ### Draw stuff
    self.draw()

    ### Check for objects outside of the screen,
    #   we can remove those Balls
    xs = []
    for ball in self.balls:
        if (
            ball.body.position.x < -1000
            or ball.body.position.x > 1000
            or ball.body.position.y < -1000
            or ball.body.position.y > 1000
        ):
            xs.append(ball)
    for ball in xs:
        self.space.remove(ball, ball.body)
        self.balls.remove(ball)

    # Polys
(略)

    ### Tick clock and update fps in title
    self.clock.tick(50)
    pygame.display.set_caption("fps: " + str(self.clock.get_fps()))

図9 create_ball()メソッドのコード

def create_ball(self, point, mass=1.0, radius=15.0):

    moment = pm.moment_for_circle(mass, 0.0, radius)
    ball_body = pm.Body(mass, moment)
    ball_body.position = Vec2d(*point)

    ball_shape = pm.Circle(ball_body, radius)
    ball_shape.friction = 1.5
    ball_shape.collision_type = COLLTYPE_DEFAULT
    self.space.add(ball_body, ball_shape)
    return ball_shape

図10 PhysicsDemoクラスのコンストラクタのコード

def __init__(self):
    self.running = True
    ### Init pygame and create screen
    pygame.init()
    self.w, self.h = 600, 600
    self.screen = pygame.display.set_mode((self.w, self.h))
    self.clock = pygame.time.Clock()

    ### Init pymunk and create space
    self.space = pm.Space()
    self.space.gravity = (0.0, -900.0)

    ### Walls
    self.walls = []
    self.create_wall_segments([(100, 50), (500, 50)])

    ## Balls
    # balls = [createBall(space, (100,300))]
    self.balls = []

    ### Polys
    self.polys = []
    h = 10
    for y in range(1, h):
        # for x in range(1, y):
        x = 0
        s = 10
        p = Vec2d(300, 40) + Vec2d(0, y * s * 2)
        self.polys.append(self.create_box(p, size=s, mass=1))
 
    self.run_physics = True

    ### Wall under construction
    self.wall_points = []
    ### Poly under construction
    self.poly_points = []

    self.shape_to_remove = None
    self.mouse_contact = None

図11 playground.pyファイルの変更差分

133a134,135
>         pygame.draw.circle(self.screen, 
>             pygame.Color("skyblue"), p, int(r), 0)
140c142
<         pygame.draw.lines(self.screen, pygame.Color("lightgray"), False, [pv1, pv2])
---
>         pygame.draw.lines(self.screen, pygame.Color("gray"), False, [pv1, pv2], 3)
148c150,151
<             color = pygame.Color("green")
---
>             color = pygame.Color("forestgreen")
>             color2 = pygame.Color("lightgreen")
151c154,156
<         pygame.draw.lines(self.screen, color, False, ps)
---
>             color2 = pygame.Color("orange")
>         pygame.draw.polygon(self.screen, color2, ps, 0)
>         pygame.draw.polygon(self.screen, color, ps, 2)

※「&#91;」を「[」に、「&lt; 」を「<」に置き換えてください。