macOSでOpenGLプログラミング（1-9. マウス操作をサポートする）

[macOSでOpenGLプログラミングの目次に戻る](http://qiita.com/sazameki/items/d060f20f621c658046d9)

# はじめに

[前回](https://qiita.com/sazameki/items/b047df7ee21d96b70548)は、キーボードの操作をサポートする方法を解説しました。

今回は、マウスの操作をサポートしましょう。キーボード操作とマウス操作がサポートできれば、大概のゲームは作れるようになります。


# 1. Inputクラスを修正する

Inputクラスに、マウス操作の関数を追加しましょう。まずInput.hppファイルに、必要な変数と関数の宣言を追加します。次のコードの内、名前に「Mouse」と付いている変数8個と関数8個が、追加するコードです。また、インクルード文も1つ追加していますので、忘れずに書き加えてください。

```cpp:Input.hpp（一部）
...
#include <cstdlib>
#include <GLKit/GLKMath.h>
...
class Input
{
    static uint64_t keyState;
    static uint64_t keyStateOld;
    static uint64_t keyDownStateTriggered;
    static uint64_t keyUpStateTriggered;

    static bool     isMouseDown;
    static bool     isMouseDownOld;
    static bool     isMouseDownTriggered;
    static bool     isMouseUpTriggered;

    static bool     isMouseDownRight;
    static bool     isMouseDownOldRight;
    static bool     isMouseDownTriggeredRight;
    static bool     isMouseUpTriggeredRight;

public:
    static bool     GetKey(uint64_t keyCode);
    static bool     GetKeyDown(uint64_t keyCode);
    static bool     GetKeyUp(uint64_t keyCode);

    static bool         GetMouseButton(int button);
    static bool         GetMouseButtonDown(int button);
    static bool         GetMouseButtonUp(int button);
    static GLKVector2   GetMousePosition();

public:
    static void ProcessKeyDown(uint64_t keyCode);
    static void ProcessKeyUp(uint64_t keyCode);
    static void ProcessMouseDown();
    static void ProcessMouseUp();
    static void ProcessMouseDownRight();
    static void ProcessMouseUpRight();
    static void Update();

};
...
```

キーボードの各キーの情報は64ビット符号なし整数のビットマスクで管理していましたが、マウスは左ボタンと右ボタンそれぞれに、bool変数でON/OFFを表すための変数を用意して、それを更新したり状態を取得するための関数を用意しています。

マウスのカーソル位置を取得するための`GetMousePosition()`関数では、macOS標準のGLKitフレームワークで用意されているGLKVector2構造体を使用しますので、GLKit/GLKMath.hファイルをインクルードしています（GLKit/GLKit.hファイルにはObjective-Cのコードが含まれていますので、インクルードするのはGLKit/GLKMathTypes.hファイルであることに注意してください）。GLKitフレームワーク自体は、[「1-1. プロジェクトの作成〜OpenGLの基本設定」](https://qiita.com/sazameki/items/12adb193dc1472c6316a)でプロジェクトの作成と同時に追加しています。

次に、Inputクラスに追加した変数の初期化コードと、関数の実装コードを追加します。その前に、マウスのカーソル位置の取得にはObjective-Cのコードを書く必要がありますので、Input.cppファイルの拡張子を.mmに変更して、InputクラスがObjective-C++のファイルとしてコンパイルされるようにしておきましょう。

　<img width="240" src="https://qiita-image-store.s3.amazonaws.com/0/166309/d94804a0-835f-abc7-e941-f4c5e0cc581a.png">

Input.mmファイルの先頭に、必要なファイルの読み込みのコードと、マウス関係のstatic変数の実体と、それを初期化するためのコードを追加しましょう。

```cpp:Input.mm（先頭）
#import "MyGLView.h"
...
bool Input::isMouseDown = false;
bool Input::isMouseDownOld = false;
bool Input::isMouseDownTriggered = false;
bool Input::isMouseUpTriggered = false;

bool Input::isMouseDownRight = false;
bool Input::isMouseDownOldRight = false;
bool Input::isMouseDownTriggeredRight = false;
bool Input::isMouseUpTriggeredRight = false;
```

次に、Inputクラスの関数の実装コードを追加します。まずはマウスの状態を取得するための関数の実装です。

```cpp:Input.mm（一部）
bool Input::GetMouseButton(int button)
{
    if (button == 0) {
        return isMouseDown;
    } else {
        return isMouseDownRight;
    }
}

bool Input::GetMouseButtonDown(int button)
{
    if (button == 0) {
        return isMouseDownTriggered;
    } else {
        return isMouseDownTriggeredRight;
    }
}

bool Input::GetMouseButtonUp(int button)
{
    if (button == 0) {
        return isMouseUpTriggered;
    } else {
        return isMouseUpTriggeredRight;
    }
}

GLKVector2 Input::GetMousePosition()
{
    return [[MyGLView sharedInstance] mousePosition];
}
```

`GetMousePosition()`関数では、MyGLViewクラスのインスタンスを取得して、マウスのカーソル位置を取得するためのメソッドをさらに呼び出します。

次に、マウスの状態を設定するための`Process〜()`系の関数の実装です。

```cpp:Input.mm（一部）
void Input::ProcessMouseDown()
{
    isMouseDown = true;
}

void Input::ProcessMouseUp()
{
    isMouseDown = false;
}

void Input::ProcessMouseDownRight()
{
    isMouseDownRight = true;
}

void Input::ProcessMouseUpRight()
{
    isMouseDownRight = false;
}
```

そして`Input::Update()`関数の実装に、マウス関係の変数を更新するためのコードを追加します。キーボード関係の変数を更新するためのコードと見比べると、まったく同じことを、マウスの左ボタンと、マウスの右ボタンに対して計算していることが分かります。

```cpp:Input.mm（一部）
void Input::Update()
{
    keyDownStateTriggered = keyState & ~keyStateOld;
    keyUpStateTriggered = ~keyState & keyStateOld;
    keyStateOld = keyState;

    isMouseDownTriggered = isMouseDown & ~isMouseDownOld;
    isMouseUpTriggered = ~isMouseDown & isMouseDownOld;
    isMouseDownOld = isMouseDown;

    isMouseDownTriggeredRight = isMouseDownRight & ~isMouseDownOldRight;
    isMouseUpTriggeredRight = ~isMouseDownRight & isMouseDownOldRight;
    isMouseDownOldRight = isMouseDownRight;
}
```


# 2. MyGLViewクラスを修正する

MyGLView.hファイルに、次のように、GLKitフレームワークのインポート文と、マウスのカーソル位置を取得するためのメソッドの宣言を追加します。

```objc++:MyGLView.h
...
#import <GLKit/GLKit.h>

@interface MyGLView : NSOpenGLView

...
- (GLKVector2)mousePosition;

@end
```

MyGLView.mmファイルの先頭に、次のように、MyGLViewクラスのインスタンス変数を2つ追加します。

```objc++:MyGLView.mm（先頭）
@implementation MyGLView {
    ...
    NSWindow            *window;
    NSSize              size;
}
```

ビューの親であるウィンドウを取得するコードと、ビューのサイズを取得するためのコードは、どちらもメインスレッド上でしか実行できないため、ビューの作成時に呼ばれる`prepareOpenGL`メソッドで実行して、この2つの変数に保持しておきます。

```objc++:MyGLView.mm（一部）
- (void)prepareOpenGL
{
    [super prepareOpenGL];

    window = self.window;
    size = self.bounds.size;
    ...
}
```

そして`mousePosition`メソッドの実装を、次のように、保持しておいた2つの変数を使って実装します。

```objc++:MyGLView.mm（一部）
- (GLKVector2)mousePosition
{
    NSPoint location = [NSEvent mouseLocation];
    NSRect rect = NSMakeRect(location.x, location.y, 0.0f, 0.0f);
    rect = [window convertRectFromScreen:rect];
    location = rect.origin;
    location.x = location.x * 2 / size.width - 1.0f;
    location.y = location.y * 2 / size.height - 1.0f;
    return GLKVector2Make(location.x, location.y);
}
```

マウス位置はNSEventクラスの`mouseLocation`メソッドを使って取得できますが、この値はMacのスクリーン全体での座標における値なので、ウィンドウの座標系に変換します。そして変換した値を2倍した値をビューの大きさで割り、1を引くことで、X座標・Y座標ともに-1.0〜1.0の範囲の値が取得できます。

マウスカーソルの座標はピクセル単位で取得しても良いのですが、いずれカメラを移動したりモデルを回転・移動・スケーリングしたりするなどした場合には変換が必要になりますので、ここではあらかじめ正規化した値を取得するようにしました。


# 3. ViewControllerクラスを修正する

ViewController.mmファイルの最後の「`@end`」の行の直前に、マウスが押されたり離されたりした時に呼ばれる4つのメソッドを次のように追加して、先程用意したInputクラスの関数が呼ばれるようにしましょう。

```obj-c++:ViewController.mm（一部）
- (void)mouseDown:(NSEvent*)theEvent
{
    Input::ProcessMouseDown();
}

- (void)mouseUp:(NSEvent*)theEvent
{
    Input::ProcessMouseUp();
}

- (void)rightMouseDown:(NSEvent*)theEvent
{
    Input::ProcessMouseDownRight();
}

- (void)rightMouseUp:(NSEvent*)theEvent
{
    Input::ProcessMouseUpRight();
}
```


# 4. Gameクラスの実装を修正する

Game.hppを修正して、`value`変数を削除して、`value1`と`value2`の2つの変数を追加しましょう。

```cpp:Game.hpp（一部）
class Game
{
private:
    float   value1;
    float   value2;
...
};
```

Game.cppを修正して、Gameクラスのコンストラクタで、追加した2つの変数を初期化します。

```cpp:Game.cpp（一部）
Game::Game()
{
    glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    value1 = 0.0f;
    value2 = 0.0f;
}
```

`Game::Render()`関数の実装を、次のように書き換えましょう。マウスの左ボタンが押されている時に、マウスの現在のカーソル位置を取得して、X座標の-1.0〜1.0の値を0.0〜1.0の値に変換して`value1`変数に、Y座標の-1.0〜1.0の値を同様に変換して`value2`変数に格納します。そしてこれらの値を元にして、画面をクリアする色を変更します。

```cpp:Game.cpp（一部）
void Game::Render()
{
    if (Input::GetMouseButton(0)) {
        GLKVector2 pos = Input::GetMousePosition();
        value1 = (pos.x + 1.0f) / 2.0f;
        value2 = (pos.y + 1.0f) / 2.0f;
    }

    glClearColor(value1, value2, 1.0f - value1 * value2, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
}
```

実行して、マウスの左ボタンを押しながら画面上をドラッグすると、マウス位置に応じた色が表示されるようになっていることが確認できます。

　<img width="420" src="https://qiita-image-store.s3.amazonaws.com/0/166309/204d962c-063d-4d7d-dacd-8ec5d9348ad0.png">

ここまでのプロジェクト：[MyGLGame_step1-9.zip](http://sazameki.jp/temp/MyGLGame_step1-9.zip)


# 5. まとめ

今回はマウスの操作をサポートしました。

次回は、OpenGLで描画可能な頂点の数など、様々なOpenGLの環境を確認して、シェーダで描画を行うための準備をしましょう。

---
次の記事：[macOSでOpenGLプログラミング（1-10. OpenGLの環境を確認する）](https://qiita.com/sazameki/items/6b9870100a1081cedd25)