[JUCE][box2D] JUCEで物理エンジンbox2Dを使いアニメーションを描く


#序

JUCEには物理エンジンライブラリ、box2Dが実装されているが、実際にどうやって使うの？と思っても、少なくともJUCEのリファレンスはないに等しく、サンプルも提供されておりません。

一方、box2D自体のサンプルは、OpenGLなどを使って結果を描写するというものはあっても、JUCEで結果をどう表示させるか？が見つからず、box2D未経験者としては困りました。

仕方なく、box2Dのサンプルを学び→JUCEでの実装を研究する、という手順を追いました。

![Screenshot 2021-05-19 at 02.22.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46509/211b0493-7120-f9e5-cae0-c781f71a238f.png)

マウスで画面をクリックすると四角いオブジェクトを生成し、それが重力に従って落下。
他のオブジェクトとぶつかりあって、あんなことやこんなことが起こるプログラムを作ろう...

#破

重力を表現する単純なサンプルを使います。（box2DのHello World的なサンプルです。）

JUCEでbox2Dを使用する際は、Projucerでプロジェクト生成時にbox2Dにチェックを入れる必要があります。

まずはお決まりの"MainComponent"に以下のメソッドを追加し、box2Dの物理ワールドを作ります。

```C++
void MainComponent::initBox()
{
    //重力を作ります。 (重力加速度 - 9.81)
    b2Vec2 gravity(0.0, -9.81); 
    //物理ワールド生成
    this->world = std::make_unique<b2World>(gravity);
       
    //地面を設定する
    b2BodyDef groundBodyDef;
    //地面の位置
    groundBodyDef.position.Set(0.0, -10);
    //地面を作る（world内に保管される）
    b2Body* groundBody = this->world->CreateBody(&groundBodyDef);
    
    //地面のfixtureを設定する
    b2PolygonShape groundBox;
    //横2000m　高20m (数値は倍になる)
    groundBox.SetAsBox(1000.0f, 10.f);
    //地面を作る（world内に保管される）
    groundBody->CreateFixture(&groundBox, 0.0f);// density
 
}
```
worldのCreateBodyで地面を生成することで、データはworldに保管されます。
これから、様々なオブジェクトを作って行きますが、全てworldに保管される形をとります。

このメソッドをコンストラクタで一度読んでください。

```C++
MainComponent::MainComponent()
{
    setSize (600, 400);

    initBox();
    
}
```

次に、どんどん追加していく四角いオブジェのクラスを作ります。

```C++
#include <JuceHeader.h>

class Box : public Component {
public:

    Box(float scale);
    ~Box();
    
    void init(b2World* world, Point<float> position, Point<float> dimensions);
    
    void paintBox(Graphics& g, int height);
    
    void updatePosition(int height);
    
    Point<float> getDimensions() { return this->dimensions; }
private:
    
    void paint(Graphics& g) override;
    void resized() override;
    
    float ratio = 1.0;
    b2Body* body = nullptr;
    b2Fixture* fixture = nullptr;
    Point<float>dimensions;
};

```

この中でbox2Dに関係する箇所は initメソッドです。

```C++
void Box::init(b2World* world, Point<float> position, Point<float> dimensions)
{
    this->dimensions = dimensions;
    
    float hX = dimensions.x / 2.0;
    float hY = dimensions.y / 2.0;
    
    //ボディを定義
    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;
    bodyDef.position.Set(position.x - hX, position.y + hY);
    
    //ボディ作る
    this->body = world->CreateBody(&bodyDef);
    
    //形を定義
    b2PolygonShape boxShape;
    boxShape.SetAsBox(hX, hY);
    
    //fixtureを定義
    b2FixtureDef fixtureDef;
    fixtureDef.shape = &boxShape;
    fixtureDef.density = 1.0f;
    fixtureDef.friction = 0.7f;
    this->fixture = body->CreateFixture(&fixtureDef);
}
```
これでbox2Dの設定はおしまいです。

#Q
次は、どうやって結果をJUCEで表示する？です。

実は、2通り試しました。

1. Componentの座標をコントロールし、Component自体を動かすケース
2. Graphicsで図形を描写し、その座標をコントロールするケース

個人的に、2の方が綺麗に動いているように思いました。
しかしながら、1の方が一つ一つのオブジェクトの挙動などを制御したり、マウスイベントなどを活用したりしやすく、どちらも捨てがたいように思います。

ここでは両方紹介したいと思います。

以下はGraphicsでペイントするケース

```C++

void Box::paintBox(Graphics& g, int height)
{
    
    auto b = this->body->GetPosition();
    g.setColour(Colours::white);
    g.fillRect(b.x * scale,
               (float)height - b.y * scale,
               this->dimensions.getX() * scale,
               this->dimensions.getY() * scale);

}

```

注意点としては、JUCEの座標系はトップが0、ボトムに向かうにつれて値が大きくなります。
box2Dは逆で、高い位置の値が大きく、地面方面に向かって値が小さくなります。
ですので、Componentの高さheightからy座標を引くことでy座標をadjustしています。

次に、以下はComponentのBoundsを制御するケース
ペイントのケースでは面倒でやっていないのですが、こちらではbox2Dによって吐き出される角度も実装しています。

```C++

void Box::updatePosition(int height)
{
    auto b = this->body->GetPosition();

    setBounds(b.x * scale,
              (float)height - b.y * scale,
              this->dimensions.getX() * scale,
              this->dimensions.getY() * scale);
    
    float angle = this->body->GetAngle();

    Point<int> centrePos = Point<int>(getX() + getWidth()/2,
                                      getY() + getHeight()/2);
    
    setTransform(AffineTransform().translation(-centrePos.getX(), -centrePos.getY()).rotated(angle).translated(centrePos.getX(), centrePos.getY()));
    
}
```

角度の実装は少し工夫が必要で、オブジェクトの座標を回転の中心に持ってきた後、回転、元の位置に戻す、という工程が必要です。
その際、単純にgetX() getY()ではズレが生じ、ちゃんとオブジェクトのWidthとHeightも考慮する必要があります。(centrePosの箇所です。)

#Repeat
次に、オブジェクトの生成方法を簡単に紹介します。

```C++
    //MainComponent.h
    std::vector<Box*> boxes;

//MainComponent.cpp
void MainComponent::createBox(int x, int y)
{
    Box* newBox = new Box(this->box2DRatio);
    newBox->init(this->world.get(),
                 Point<float>((float)x / this->box2DRatio,
                              (float)(getHeight() - y) / this->box2DRatio),
                 Point<float>(1, 1));
    
    this->boxes.push_back(newBox);
    
    addAndMakeVisible(newBox);
}

```

マウスをクリックするとオブジェクトが生成されるようにします。


```C++
    //MainComponent.h
    void mouseDown(const MouseEvent& e) override;

//MainComponent.cpp
void MainComponent::mouseDown(const MouseEvent& e)
{
    auto pos = e.getPosition();    
    createBox(pos.getX(), pos.getY());
}

```

描写・座標変更はここ
座標を更新する際は、物理ワールドの更新が必要です。

今回は、フレームレート60 (float timeStep = 1.0 / 60.0)、int velocityIterations = 6, int positionIterations = 2で行っています。

```C++
    //MainComponent.h
    std::unique_ptr<b2World> world;

//MainComponent.cpp
void MainComponent::paint (juce::Graphics& g)
{
    g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));

    g.setFont (juce::Font (16.0f));
    g.setColour (juce::Colours::white);
    g.drawText ("Hello World!", getLocalBounds(), juce::Justification::centred, true);
    
    this->world->Step(this->timeStep,
                      this->velocityIterations,
                      this->positionIterations);
    
    for(auto box : this->boxes)
    {
        //box->paintBox(g, getHeight()); //どっちか片方ね
        box->updatePosition(getHeight());
    }
}
```



#リピート

最後にTimerを継承して、リピートしましょう。

```C++

void MainComponent::timerCallback()
{
    repaint();
}

```

私は    startTimer(60);　で行っています。


#終わり
以上、雑で申し訳ないですが、ざっと書きました。
書き漏れ、不明瞭な箇所ありましたら、ご連絡いください。
可能な限りお答えします。

最後まで見ていただき、ありがとうございました。