【Playdate】Playdate用軽量版Box2DのPlaybox2dを見てみる

PlaydateはLuaの他にC言語での開発が可能です。
Ｃ言語は主にパフォーマンスが必要な場合に利用するのですが、今回紹介する物理演算エンジン[Playbox2D](https://github.com/mierau/playbox2d)はこれを利用しています。

https://devforum.play.date/t/playbox2d-port-of-box2d-lite-physics-engine-to-c-and-playdate-sdk/1656/2

## Playbox2D

Box2D-Liteをベースにしており、矩形リジッドボディやaddForce、摩擦、ジョイントなど制限されたBox2Dとなります。

サンプルを見ながら確認してみます。

https://github.com/mierau/playbox2d

main.luaのsetup関数でplaybox2dの設定を行い、draw関数で描画を行っています。

```main.lua
function setup()
  
  -- 略 --
  
  -- World
  world = playbox.world.new(0.0, 9.81, 10)
  world:setPixelScale(WORLD_PIXEL_SCALE)
  
  -- 地面
  floor = playbox.body.new(FLOOR_WIDTH, FLOOR_HEIGHT, 0)
  floor:setCenter(WORLD_WIDTH/2, WORLD_HEIGHT)
  floor:setFriction(FLOOR_FRICTION)
  floor:setRotation(0.1)
  world:addBody(floor)
  
  -- 天井
  ceiling = playbox.body.new(CEILING_WIDTH, CEILING_HEIGHT, 0)
  ceiling:setCenter(WORLD_WIDTH/2, CEILING_HEIGHT/2)
  ceiling:setFriction(CEILING_FRICTION)
  world:addBody(ceiling)
  
  -- 箱
  for i = 1, BOX_COUNT do
    local box_mass <const> = math.random(MASS_MIN, MASS_MAX)
    local box = playbox.body.new(math.random() * 0.3 + 0.2, math.random() * 0.3 + 0.2, box_mass)
    box:setCenter(math.random() * WORLD_WIDTH, CEILING_HEIGHT)
    box:setFriction(0.8)
    box:setRotation(math.random() * 0.8 + 0.1)
    world:addBody(box)
    boxes[#boxes + 1] = box
    
    -- 箱の模様は質量と連動。明るい模様 = 軽い
    box_patterns[#box_patterns + 1] = math.max(math.min(1.0 - box_mass/MASS_MAX, 0.9), 0.1)
  end
  
  -- 揺れる板
  swing_box = playbox.body.new(0.1, 1.0, 80)
  swing_box:setCenter(0, CEILING_HEIGHT)
  swing_box:setFriction(1.0)
  world:addBody(swing_box)
  
  -- 揺れる板と結ばれている紐
  swing_joint = playbox.joint.new(ceiling, swing_box, WORLD_WIDTH/4, 0)
  swing_joint:setBiasFactor(0.3)
  swing_joint:setSoftness(0.0)
  world:addJoint(swing_joint)
end
```

`playbox.world.new([X軸の重力加速度], [Y軸の重力加速度], [イテレーション])`でWorld生成し、
floor、ceiling、box、swing_boxは`playbox.body.new([横幅], [縦幅], [質量])`で生成しています。
`playbox.body`の質量を0にすると固定できます。
余談ですが、box同士がぶつかり合う際、この質量に大きい差があると、めり込みが起こってしまいます。
なぜそうなるのか理解していないので知見ある方教えていただけますとありがたいです🙏

ceilingとswing_boxをつなぐジョイントは`playbox.joint.new([対象A], [対象B], [worldからみたジョイントのX座標], [worldからみたジョイントのY座標])`となります。


```main.lua
function draw()
  
  -- 略 --
  
  -- 床描画
  local floor_polygon = geometry.polygon.new(floor:getPolygon())
  floor_polygon:close()
  graphics.fillPolygon(floor_polygon)
  
  -- 天井描画
  local ceiling_polygon = geometry.polygon.new(ceiling:getPolygon())
  ceiling_polygon:close()
  graphics.fillPolygon(ceiling_polygon)
  
  -- 箱描画
  graphics.setStrokeLocation(graphics.kStrokeInside)
  for i, box in ipairs(boxes) do
    local box_polygon = geometry.polygon.new(box:getPolygon())
    box_polygon:close()
    graphics.setDitherPattern(box_patterns[i])
    graphics.fillPolygon(box_polygon)
    graphics.setColor(graphics.kColorBlack)
    if i == selected_box then
      graphics.setLineWidth(3)
    else
      graphics.setLineWidth(1)
    end
    graphics.drawPolygon(box_polygon)
  end
  
  
  graphics.setLineWidth(1)
  graphics.setDitherPattern(0.5)
  
  -- 揺れる板描画
  local swing_box_polygon = geometry.polygon.new(swing_box:getPolygon())
  swing_box_polygon:close()
  graphics.fillPolygon(swing_box_polygon)
  
  -- 揺れる板と結ばれている紐描画
  graphics.setStrokeLocation(graphics.kStrokeCentered)
  local _, _, px1, py1, x2, y2, _, _ = swing_joint:getPoints()
  graphics.setDitherPattern(0.5)
  graphics.drawLine(x2, y2, px1, py1)
  
  -- 略 --

end
```

draw関数ではplaybox.body:getPolygon()でポリゴンを取得し、各boxを描画しています。
ジョイントについてはplaybox.joint:getPoints()で座標を取得し、drawLineで線を描画しています。