cymunkを使って雪を降らせる - Kivy Advent Calendar 2013


cymunkは2次元物理エンジンChipMunk (http://chipmunk-physics.net/) のCython実装で、KivyLauncerにもデフォルトで入っています。
https://cymunk.readthedocs.org/en/latest/
オブジェクトの重力、衝突、弾性の計算処理をやってくれるもので、cymunkのサンプルコードではカラフルなボールを弾ませています。
このサンプルコードを

  • カラーボールではなく小さい白い結晶にする
  • タッチしなくてもランダムで発生させる
  • 重力を弱くして、左右に揺れるようにする
  • 弾性はほぼゼロにする
  • 地面等に着いたら跡は残しつつ計算対象から外す

とすれば、雪が降り積もってるようにみえないかなあ…と思ったので実験してみました。

結果は…
なんか「地面等に着いたら」の処理をきちんとやっていないので、地面を雪玉が転がってたりして。
あまり雪っぽくないかな。



(/sdcard/kivy/letitsnow/)

android.txt お約束
flake.png 雪の結晶みたいな画像 (なければcircle.pngをリネームして)
main.py スクリプト本体

(android.txt)

title=letitsnow
author=cheeseshop
orientation=landscape

(main.py)

from kivy.clock import Clock
from kivy.app import App
from kivy.graphics import Color, Rectangle
from kivy.uix.widget import Widget
from kivy.properties import DictProperty, ListProperty
from kivy.core.image import Image
from cymunk import Space, Segment, Vec2d, Body, Circle
from random import random
from kivy.lang import Builder
from os.path import dirname, join

class Snow(Widget):

    cbounds = ListProperty()
    cmap = DictProperty({})
    flist = ListProperty()

    def __init__(self, **kwargs):
        super(Snow, self).__init__(**kwargs)
        self.init_space()
        self.bind(size=self.update_bounds, pos=self.update_bounds)
        self.texture = Image(join(dirname(__file__), 'flake.png')).texture
        Clock.schedule_interval(self.step, 1 / 30.)

    def init_space(self):
        self.space = space = Space()
        space.iterations = 30
        space.gravity = (0, -300)
        space.sleep_time_threshold = 0.5
        space.collision_slop = 0.5

        for x in xrange(4):
            seg = Segment(space.static_body,
                          Vec2d(0, 0), Vec2d(0, 0), 0)
            seg.elasticity = 0.0
            self.cbounds.append(seg)
            space.add_static(seg)
        self.update_bounds()

    def update_bounds(self, *largs):
        a, b, c, d = self.cbounds
        x0, y0 = self.pos
        x1 = self.right
        y1 = self.top

        self.space.remove_static(a)
        self.space.remove_static(b)
        self.space.remove_static(c)
        self.space.remove_static(d)
        a.a = (x0, y0); a.b = (x1, y0)
        b.a = (x1, y0); b.b = (x1, y1)
        c.a = (x1, y1); c.b = (x0, y1)
        d.a = (x0, y1); d.b = (x0, y0)
        self.space.add_static(a)
        self.space.add_static(b)
        self.space.add_static(c)
        self.space.add_static(d)

    def step(self, dt):
        if random() < 0.4:
            self.add_flake(random() * self.right, self.top - 20, 5)
        if random() < 0.2:
            self.space.gravity = (100-random()*200, -50*random()-50)
        self.space.step(1 / 30.)
        self.update_objects()

    def update_objects(self):
        for body, obj in self.cmap.iteritems():
            p = body.position
            radius, color, rect = obj
            rect.pos = p.x - radius, p.y - radius
            rect.size = radius * 2, radius * 2

    def add_flake(self, x, y, radius):
        body = Body(100, 1e9)
        body.position = x, y
        circle = Circle(body, radius)
        circle.elasticity = 0.1
        self.space.add(body, circle)

        with self.canvas.before:
            color = Color(1, 1, 1)
            rect = Rectangle(
                texture=self.texture,
                pos=(self.x - radius, self.y - radius),
                size=(radius * 2, radius * 2))
        self.cmap[body] = (radius, color, rect)

        self.flist.append((body, circle) )
        if len(self.flist) > 300:
            body, circle = self.flist.pop(0)
            self.space.remove(body)
            self.space.remove(circle)

class SnowApp(App):
    def build(self):
        return Snow()

if __name__ == '__main__':
    SnowApp().run()