SVGと物理エンジンをマッピングするn個の方法

最近SVGにハマっている。ふとした思いつきで、SVGのcircle要素と物理エンジンをマッピングできたら、簡単にパーティクルが作れるのでは、と思ったのでやってみる。

1. PhysicsJS + Snap.svg(雑な描画マッピング)
2. PhysicsJS + Snap.svg + 自力の差分管理
3. PhysicsJS + React


作るのは非常に雑な、こういうやつだ
![](http://i.gyazo.com/0f0f2224bc369d7b903cef4220f810f0.gif)

- クリックしたらcircle要素が出現する
- 物理エンジンによって座標計算されて右方向に飛んで行く
- 50px 右に移動したら消える

物理エンジン的に面白い挙動とかはあとで考えることにする。


## 物理エンジンの世界を作る

今回はPhysicsJSを使うことにした。box2dはC++由来のAPIが古臭いし使いたくない。matter.js と PhysicsJSがメンテされててモダンな雰囲気がして試してみたのだけど、matter.jsは何かとおせっかいがすぎる + デザインパターンが特殊すぎるので、素直なPhysicJSを使うことにする。


ボールが一つあって、速度を持ってる世界を作る。

```coffee
world = Physics
  integrator: 'verlet'
  maxIPF: 16
  timestep: 1000.0 / 300

ball = Physics.body 'circle',
  x: 100
  y: 100
  vx: 0.2
  vy: 0.01
  radius: 10

world.add(ball)

Physics.util.ticker.on (time)->
  world.step(time)

Physics.util.ticker.start()
```

この ticker.on の中身はフレームごとに呼ばれてる。`world.step(time)` で状態を更新する。
この時点ではまだ何も描画していない。


## SVGで描画する

SVGの circle 要素をマッピングする。

```coffee
world = Physics
  integrator: 'verlet'
  maxIPF: 16
  timestep: 1000.0 / 300

ball = Physics.body 'circle',
  x: 100
  y: 100
  vx: 0.2
  vy: 0.01
  radius: 10

world.add(ball)

$ =>
  $('body').html """
    <svg width=640 height=320 style="background-color: white;"></svg>
  """
  paper = Snap document.querySelector 'svg'
  circle = paper.circle r: 10

  Physics.util.ticker.on (time)->
    for body in world.getBodies()
      circle.attr
        cx: body.state.pos.x
        cy: body.state.pos.y
    world.step(time)

  Physics.util.ticker.start()
```

非常に雑なコード。何が雑かって、要素が一つだけであることに依存している。


## SVG要素を動的に生成する

クリックしたらボールを追加したい。追加したら描画側で勝手に要素を追加してほしい。
設計方針として、MVC でいう M と V を切り離すことを目標にする。

```coffee
class Renderer
  constructor: (@world, @paper) ->
    @_elementMappings = {} # PhysicsId: Shape

  render: ->
    for body in @world.getBodies()
      circle = @_elementMappings[body.uid]
      unless circle
        circle = @paper.circle r: 10
        @_elementMappings[body.uid] = circle

      circle.attr
        cx: body.state.pos.x
        cy: body.state.pos.y

$ =>
  $('body').html """
    <svg width=640 height=320 style="background-color: white;"></svg>
  """
  paper = Snap 'svg'

  world = Physics
    integrator: 'verlet'
    maxIPF: 16
    timestep: 1000.0 / 300

  renderer = new Renderer world, paper
  Physics.util.ticker.on (time) ->
    world.step(time)
    renderer.render()

  paper.click ->
    ball = Physics.body 'circle',
      x: 100
      y: 100
      vx: 0.2
      vy: 0.01
      radius: 10

    world.add(ball)

  Physics.util.ticker.start()
```

マウスをクリックする度に生成する。`_elementMappings` にbodyのuidをキーに、Snapのshapeを保存する。
この方式にも欠点がある。PhisicsJS側で要素を削除した時に、消されたことを検知できない。

## 差分管理

とりあえずxが150を超えたら削除するようにする。このときRenderer側も自動で変更がかからないものを検知して削除する。

```coffee
class Renderer
  constructor: (@world, @paper) ->
    @_elementMappings = {} # PhysicsId: Shape

  render: ->
    updatedList = []
    for body in @world.getBodies()
      updatedList.push body.uid
      circle = @_elementMappings[body.uid]
      unless circle
        circle = @paper.circle r: 10
        @_elementMappings[body.uid] = circle

      circle.attr
        cx: body.state.pos.x
        cy: body.state.pos.y

    unstagedList = _.difference Object.keys(@_elementMappings), updatedList.map((i) -> i.toString())
    for key in unstagedList
      @_elementMappings[key]?.remove()
      delete @_elementMappings[key]

$ =>
  $('body').html """
    <svg width=640 height=320 style="background-color: white;"></svg>
  """
  paper = Snap 'svg'

  world = Physics
    integrator: 'verlet'
    maxIPF: 16
    timestep: 1000.0 / 300

  renderer = new Renderer world, paper
  Physics.util.ticker.on (time) ->
    world.step(time)
    bodies = world.getBodies()
    bodies.map (body) ->
      if body.state.pos.x > 150
        world.remove body

    renderer.render()

  paper.click ->
    ball = Physics.body 'circle',
      x: 100
      y: 100
      vx: 0.2
      vy: 0.01
      radius: 10

    world.add(ball)

  Physics.util.ticker.start()

```

配列と配列の差を比較するのに `_.difference` を使っている。
差分管理が人間がやるには厳しくなってきた


# React で差分描画を行う

Reactは差分描画を行ってくれる。

```coffee
{DOM} = React
Renderer = React.createClass
  getInitialState: ->
    bodies: []

  onClick: ->
    ball = Physics.body 'circle',
      x: 100
      y: 100
      vx: 0.2
      vy: 0.01
      radius: 10

    @props.world.add(ball)

  render: ->
    DOM.svg
      className: 'content'
      width: 640
      height: 480
      style:
        backgroundColor: 'white'
      onClick: @onClick
    , @state.bodies.map (body) =>
      DOM.circle
        key: body.uid
        cx: body.state.pos.x
        cy: body.state.pos.y
        r: 10

$ =>
  world = Physics
    integrator: 'verlet'
    maxIPF: 16
    timestep: 1000.0 / 300

  renderer = React.renderComponent (Renderer {world}), document.body

  Physics.util.ticker.on (time) ->
    world.step(time)
    bodies = world.getBodies()
    bodies.forEach (body) ->
      if body.state.pos.x > 150
        world.remove body
    renderer.setState bodies: world.getBodies()

  Physics.util.ticker.start()
```

renderでまるごと描画しているように見えるが、実際は差分だけをReact側で管理してDOMを挿入してくれてる。賢い。
ただこの差分管理の計算コストが発生しているので、上の自前計算の方が速いかもしれない。


次はrequestAnimationFrameを内部的に使ってるClojure/Omを使ったやつも書いて、パーティクルらしいパーティクルを書いて、ベンチをとってみようと思う