C++でOpenGLを使ってゲーム用ライブラリを作った話 #1

# はじめに
[農工大アドベントカレンダー9日目の記事](https://qiita.com/advent-calendar/2024/tuat)です。

前回記事 [C++でOpenGLを使ってゲーム用ライブラリを作った話 #0](https://qiita.com/upiscium/private/722ca42dcfea4b1b4326)

# この連載のリポジトリ
https://github.com/uPiscium/QiitaGameLib

今回は`Article1`フォルダの内容です。

# 宣伝
https://github.com/uPiscium/TerreateGraphics

今回の連載の元ネタにあたる、筆者が実際に作成したライブラリです。
気が向いたら覗いてみてください。(スターしてくれると筆者がとっても喜びます✨)


# TL;DR
とりあえず、`GLFWwindow`を自作クラスでラッピングしていい感じにしたよ。以上
(今回の[コード全文](#コード全文))

# 本記事の内容
- [今回の目標](#今回の目標)
- [クラスの設計](#クラスの設計)
- [クラスの設計-改-](#クラスの設計-改-)
- [おわりに](#おわりに)
- [コード全文](#コード全文)

# 今回の目標
`OpenGL`の連載2回目の記事ですね(^^)。
とりあえず今回は`#1`ということで`GLFWwindow`構造体を使いやすくラッピングしていくことを今回の目標とします。では早速やっていきましょう〜
(内容は相変わらずのスカスカなんでね...期待はしないでね...)

# クラスの設計
設計とか偉そうに言ってますが適当に書いているだけです。
とりあえず、どんな感じに`Window`を制御したいかを決定していきましょう。
最初は次のような設計にしておこうと思います。
```mermaid
classDiagram
class Window {
 - GLFWwindow *window

 + Window(int width, int height, std::string title)
 + GLFWwindow *GetWindowPtr()
 + bool IsPressed(int key)
 + bool IsClosed()
 + void Close()
 + void Update()
 + void Fill(float r, float g, float b, float a)
}
```

## コンストラクタ
画面の幅、高さ、タイトルを受け取って`GLFWwindow`構造体を生成し、描画対象に設定する。ここで生成した`GLFWwindow`構造体のインスタンスを通して画面の制御を行う。

## GetWindowPtr()関数
生成した`GLFWwindow`構造体の生ポインタを取得する。ウィンドウの生ポインタを必要とする`glfw`の関数を呼び出す場合に利用する。

## IsPressed()関数
指定したキーが押されているかを返す。キー入力を処理する場合に利用する。

## IsClosed()関数
ウィンドウが閉じたかどうかを返す。描画ループの終了判定に利用する。

## Close()関数
ウィンドウを閉じる。任意のタイミングでプログラム側からウィンドウを閉じられるようにする。

## Update()関数
画面のバッファを入れ替えて描画結果を見えるように画面上に反映する & OSからのシステムコールやイベント等を処理する関数。この関数を描画ループの最後に呼び出すこと描画結果を反映して、イベントを処理する。

## Fill()関数
画面を指定された`(r, g, b, a)`の色で塗りつぶす関数。描画ループの最初に呼び出すことで画面を初期化する。

---

ではここまでで一旦コードを書いてみましょう。
まずはヘッダファイル(`includes/Article1/window.hpp`)の中身
```cpp
#ifndef __WINDOW_HPP__
#define __WINDOW_HPP__

#include <glad/gl.h>

#include <GLFW/glfw3.h>
#include <string>

// ウィンドウクラス
class Window {
private:
  GLFWwindow *mWindow;

public:
  Window(int width, int height, std::string title);

  GLFWwindow *GetWindowPtr() const { return mWindow; }

  void Update();
  void Fill(float r, float g, float b, float a);
};

#endif // __WINDOW_HPP__

```
次に、ソースファイル(`impls/Article1/window.cpp`)の中身
```cpp
#include "../../includes/Article1/init.hpp"
#include "../../includes/Article1/window.hpp"

Window::Window(int width, int height, std::string title) {
  // ウィンドウの作成
  mWindow = glfwCreateWindow(width, height, title.c_str(), NULL, NULL);
  glfwMakeContextCurrent(mWindow);
  InitGLAD();
}

void Window::Update() {
  // バッファのスワップとイベントのポーリング
  glfwSwapBuffers(mWindow);
  glfwPollEvents();
}

void Window::Fill(float r, float g, float b, float a) {
  // 背景色の設定とクリア
  glClearColor(r, g, b, a);
  glClear(GL_COLOR_BUFFER_BIT);
}
```
`#0`の記事内で`main()`関数にベタ書きしていた内容をメソッドとしてまとめ上げたような感じになっていますね。それぞれの内容はコメントアウトに書いたとおりです。より詳しい解説は[前回記事](https://qiita.com/upiscium/private/722ca42dcfea4b1b4326)を参照してください。ソースファイルの中で`InitGLAD()`関数という紹介していない関数を呼び出していますが、これは`init.hpp`内の`glad`の初期化を行うヘルパー関数です。詳しい内容は`init.hpp`ファイル内を確認してください。
せっかく新しいクラスを作ったので組み込んで動かしたいですよね！`#0`で作成した`main()`関数を書き換えて実際に使ってみましょう〜

---

まずはヘッダファイルを追加して
```diff_cpp
...
+ #include "../includes/Article1/window.hpp"
...
```
続けてウィンドウを生成する部分の書き換えと、ついでにコールバック関数の設定もやっちゃいましょう〜
```diff_cpp
int main() {
  ...

  // ウィンドウの作成
- GLFWwindow *window =
-     glfwCreateWindow(800, 600, "OpenGL Triangle", nullptr, nullptr);
- if (!window) {
-   std::cerr << "Failed to create GLFW window" << std::endl;
-   glfwTerminate();
-   return -1;
- }
- glfwMakeContextCurrent(window);

- // GLADの初期化
- if (!gladLoadGL((GLADloadfunc)glfwGetProcAddress)) {
-   std::cerr << "Failed to initialize GLAD" << std::endl;
-   return -1;
- }

+ Window window(800, 600, "OpenGL Triangle");

  // ビューポートの設定
  glViewport(0, 0, 800, 600);
- glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
+ glfwSetFramebufferSizeCallback(window.GetWindowPtr(), framebuffer_size_callback);

  ...
```
次は描画ループ内の書き換えをやって
```diff_cpp
  // メインループ
- while (!glfwWindowShouldClose(window)) {
+ while (!window.IsClosed()) {
    // 入力処理
-   processInput(window);
+   if (window.IsPressed(GLFW_KEY_ESCAPE)) {
+     window.Close();
+   }


-   // 背景色の設定とクリア
-   glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
-   glClear(GL_COLOR_BUFFER_BIT);
+   window.Fill(0.2f, 0.2f, 0.2f, 1.0f);

    // シェーダープログラムの使用
    glUseProgram(shaderProgram);

    // 三角形の描画
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 3);

-   // バッファのスワップとイベントのポーリング
-   glfwSwapBuffers(window);
-   glfwPollEvents();
+   window.Update()
  }

  ...
```
最後に使わなくなった関数を削除して終了です。
```diff_cpp
...

// 関数宣言
void framebuffer_size_callback(GLFWwindow *window, int width, int height);
- void processInput(GLFWwindow *window);
unsigned int createShaderProgram(const char *vertexSource,
                                 const char *fragmentSource);
                                 
...

- // 入力処理
- void processInput(GLFWwindow *window) {
-   if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
-     glfwSetWindowShouldClose(window, true);
-   }
- }

...
```
今回の改装でウィンドウを生成して初期化する部分がだいぶスッキリとしていい感じになりましたね。他の部分は少し短くなったかな...？これを実行すると前回と同じ結果が得られます！(やったね)
しかし、前回と同じなのは少し面白みがありませんよね。それに今回作っていくのはゲーム用ライブラリなので、マウスとかの入力をいい感じに取得する機能が欲しいですよね？というわけで、`Window`クラスを更に改造して色々な入力を取れるようにする + おまけで各種コールバックを設定できるようにしていきましょう〜

# クラスの設計-改-
先程のクラスに
- カーソル位置
- スクロール量
- マウスクリック

を取得するメソッドと各種コールバックを設定するメソッドを付け加えて
- GetWindowPtr()関数

を削除してこんな感じにしてみました。
```mermaid
classDiagram
class Window {
 - GLFWwindow *window
 - std::pair~double, double~ cursorPos
 - std::pair~double, double~ scroll
 - FramebufferSizeCallback framebufferSizeCallback;
 - WindowPosCallback windowPosCallback;
 - WindowSizeCallback windowSizeCallback;
 - CursorEnterCallback cursorEnterCallback;
 - CursorPosCallback cursorPosCallback;
 - ScrollCallback scrollCallback;

 + Window(int width, int height, std::string title)
 + std::pair~double, double~ GetCursorPos()
 + std::pair~double, double~ GetScroll()
 + void SetFramebufferSizeCallback(FramebufferSizeCallback callback)
 + void SetWindowPosCallback(WindowPosCallback callback)
 + void SetWindowSizeCallback(WindowSizeCallback callback)
 + void SetCursorEnterCallback(CursorEnterCallback callback)
 + void SetCursorPosCallback(CursorPosCallback callback)
 + void SetScrollCallback(ScrollCallback callback)
 + bool IsPressed(int key)
 + bool IsClicked(int mousebutton)
 + bool IsClosed()
 + void Close()
 + void Update()
 + void Fill(float r, float g, float b, float a)
}
```
この他にもコールバック関数は色々あるのでもっと知りたい人は[glfw Window Reference](https://www.glfw.org/docs/3.3/group__window.html)か[glfw Window Guide](https://www.glfw.org/docs/3.3/window_guide.html)を見に行きましょう〜
(GetWindowPtr()関数を削除した理由は今回の記事の趣旨とは異なるので気になる人は下のところを開いて眺めて見てください〜)
<details><summary>GetWindowPtr()関数を削除した理由</summary>

GetWindowPtr()関数を削除した理由は**ウィンドウの制御はこのクラスのメソッドを通して行う**という制御フローを徹底するためです。
ここで`GLFWwindow`構造体の生ポインタを取得できてしまうと、このポインタを介してウィンドウを制御することが可能になってしまいます。これを許した場合、`Window`クラスの知らないところで**勝手にウィンドウの状態が変更されてしまう**ということが起こり、予期しないエラーやバグを発生させる原因となってしまいます。
こういった面倒事を回避するため今回の連載では極力こういう関数は利用しない方針でやっていきます。</details>

## GetCursorPos()関数
カーソルの画面上での座標を返す。
座標は右上が`(0, 0)`で左下が`(width, height)`になっている。

## GetScroll()関数
マウスのスクロール量を返す。
スクロール量は左右方向と上下方向のペアが返ってくる。

## SetFramebufferSizeCallback()関数
画面の描画範囲が変更されたときに呼び出されるコールバック関数を設定する。
関数の引数は`(Window *window, int width, int height)`

## SetWindowPosCallback()関数
ウィンドウの位置が変更されたときに呼び出されるコールバック関数を設定する。
関数の引数は`(Window *window, int x, int y)`

## SetWindowSizeCallback()関数
ウィンドウのサイズが変更されたときに呼び出されるコールバック関数を設定する。
関数の引数は`(Window *window, int width, int height)`

## SetCursorEnterCallback()関数
カーソルがウィンドウ上を出入りしたときに呼び出されるコールバック関数を設定する。
関数の引数は`(Window *window, int entered)`
(`entered`はカーソルのホバーで`1`になる)

## SetCursorPosCallback()関数
カーソルの位置が変更されたときに呼び出されるコールバック関数を設定する。
関数の引数は`(Window *window, double x, double y)`

## SetScrollCallback()関数
マウスのスクロールが行われたときに呼び出されるコールバック関数を設定する。
関数の引数は`(Window *window, double xoffset, double yoffset)`

---

ではこれらの変更を実際のクラスに反映していきましょう！
まずはコールバックの関数型を宣言して、ついでにラッパーの宣言もやっちゃいましょう〜
(今回はコールバック関数がグローバルスコープ上で見える必要が無いので`callback`という名前空間に閉じ込めています。各コールバックの引数の型は[glfw Window Reference](https://www.glfw.org/docs/3.3/group__window.html)に書かれているので詳しくはこっちを参照してください。)
```diff_cpp
...
+ #include <functional>

+ class Window;  // クラスの前方宣言
+ // コールバック関数の型
+ using FramebufferSizeCallback = std::function<void(Window *, int, int)>;
+ using WindowPosCallback = std::function<void(Window *, int, int)>;
+ using WindowSizeCallback = std::function<void(Window *, int, int)>;
+ using CursorEnterCallback = std::function<void(Window *, int)>;
+ using CursorPosCallback = std::function<void(Window *, double, double)>;
+ using ScrollCallback = std::function<void(Window *, double, double)>;

+ namespace callback {
+ // コールバック関数のラッパー
+ void FramebufferSizeCallbackWrapper(GLFWwindow *window, int width, int height);
+ void WindowPosCallbackWrapper(GLFWwindow *window, int x, int y);
+ void WindowSizeCallbackWrapper(GLFWwindow *window, int width, int height);
+ void CursorEnterCallbackWrapper(GLFWwindow *window, int entered);
+ void CursorPosCallbackWrapper(GLFWwindow *window, double x, double y);
+ void ScrollCallbackWrapper(GLFWwindow *window, double xoffset, double yoffset);
+ } // namespace callback

...
```
続けて`Window`クラスの定義も変えちゃいましょう
```diff_cpp
// ウィンドウクラス
class Window {
private:
  GLFWwindow *mWindow;
+ std::pair<double, double> mCursorPos;
+ std::pair<double, double> mScroll;
+ FramebufferSizeCallback mFramebufferSizeCallback;
+ WindowPosCallback mWindowPosCallback;
+ WindowSizeCallback mWindowSizeCallback;
+ CursorEnterCallback mCursorEnterCallback;
+ CursorPosCallback mCursorPosCallback;
+ ScrollCallback mScrollCallback;

+ // コールバック関数のフレンド宣言
+ friend void(callback::FramebufferSizeCallbackWrapper)(GLFWwindow *, int width,
+                                                       int height);
+ friend void(callback::WindowPosCallbackWrapper)(GLFWwindow *window, int x,
+                                                 int y);
+ friend void(callback::WindowSizeCallbackWrapper)(GLFWwindow *window,
+                                                  int width, int height);
+ friend void(callback::CursorEnterCallbackWrapper)(GLFWwindow *window,
+                                                   int entered);
+ friend void(callback::CursorPosCallbackWrapper)(GLFWwindow *window, double x,
+                                                double y);
+ friend void(callback::ScrollCallbackWrapper)(GLFWwindow *window,
+                                              double xoffset, double yoffset);

public:
  Window(int width, int height, std::string title);

- GLFWwindow *GetWindowPtr() const { return mWindow; }
+ std::pair<double, double> GetCursorPos() const { return mCursorPos; }
+ std::pair<double, double> GetScroll() const { return mScroll; }

+ void SetFramebufferSizeCallback(FramebufferSizeCallback callback) {
+   mFramebufferSizeCallback = callback;
+ }
+ void SetWindowPosCallback(WindowPosCallback callback) {
+   mWindowPosCallback = callback;
+ }
+ void SetWindowSizeCallback(WindowSizeCallback callback) {
+   mWindowSizeCallback = callback;
+ }
+ void SetCursorEnterCallback(CursorEnterCallback callback) {
+   mCursorEnterCallback = callback;
+ }
+ void SetCursorPosCallback(CursorPosCallback callback) {
+   mCursorPosCallback = callback;
+ }
+ void SetScrollCallback(ScrollCallback callback) {
+   mScrollCallback = callback;
+ }

...
```
ソースファイルも色々追記しましょう〜
まずはコールバックのラッパーから
```diff_cpp
#include "../../includes/Article1/init.hpp"
#include "../../includes/Article1/window.hpp"

+namespace callback {
+void FramebufferSizeCallbackWrapper(GLFWwindow *window, int width, int height) {
+  Window *ptr = (Window *)glfwGetWindowUserPointer(window);  // 紐付けたポインタ
+  if (ptr->mFramebufferSizeCallback) { // コールバックが登録されている
+    ptr->mFramebufferSizeCallback(ptr, width, height);
+  }
+}

+void WindowPosCallbackWrapper(GLFWwindow *window, int x, int y) {
+  Window *ptr = (Window *)glfwGetWindowUserPointer(window);
+  if (ptr->mWindowPosCallback) {
+    ptr->mWindowPosCallback(ptr, x, y);
+  }
+}

+void WindowSizeCallbackWrapper(GLFWwindow *window, int width, int height) {
+  Window *ptr = (Window *)glfwGetWindowUserPointer(window);
+  if (ptr->mWindowSizeCallback) {
+    ptr->mWindowSizeCallback(ptr, width, height);
+  }
+}

+void CursorEnterCallbackWrapper(GLFWwindow *window, int entered) {
+  Window *ptr = (Window *)glfwGetWindowUserPointer(window);
+  if (ptr->mCursorEnterCallback) {
+    ptr->mCursorEnterCallback(ptr, entered);
+  }
+}

+void CursorPosCallbackWrapper(GLFWwindow *window, double x, double y) {
+  Window *ptr = (Window *)glfwGetWindowUserPointer(window);
+  ptr->mCursorPos = {x, y};
+  if (ptr->mCursorPosCallback) {
+    ptr->mCursorPosCallback(ptr, x, y);
+  }
+}

+void ScrollCallbackWrapper(GLFWwindow *window, double xoffset, double yoffset) {
+  Window *ptr = (Window *)glfwGetWindowUserPointer(window);
+  ptr->mScroll = {xoffset, yoffset};
+  if (ptr->mScrollCallback) {
+    ptr->mScrollCallback(ptr, xoffset, yoffset);
+  }
+}
+} // namespace callback

...
```
このままではせっかく作ったラッパーが呼び出されないのでコンストラクタを書き換えて登録しましょう。加えて、コールバックの中で`Window`クラスのポインタが必要なのでここで紐付けちゃいましょう〜
```diff_cpp
...

Window::Window(int width, int height, std::string title) {
  ...

+ glfwSetWindowUserPointer(mWindow, this); // 自分のポインタをGLFWwindowと紐付け
+ // コールバックを登録
+ glfwSetFramebufferSizeCallback(mWindow,
+                                callback::FramebufferSizeCallbackWrapper);
+ glfwSetWindowPosCallback(mWindow, callback::WindowPosCallbackWrapper);
+ glfwSetWindowSizeCallback(mWindow, callback::WindowSizeCallbackWrapper);
+ glfwSetCursorEnterCallback(mWindow, callback::CursorEnterCallbackWrapper);
+ glfwSetCursorPosCallback(mWindow, callback::CursorPosCallbackWrapper);
+ glfwSetScrollCallback(mWindow, callback::ScrollCallbackWrapper);
}

...
```
最後に、`main.cpp`を少しだけ書き換えて今回の変更を反映しましょう
```diff_cpp
...
- void framebuffer_size_callback(GLFWwindow *window, int width, int height);
+ void framebuffer_size_callback(Window *window, int width, int height);
...

...
  // ビューポートの設定
  glViewport(0, 0, 800, 600);
- glfwSetFramebufferSizeCallback(window.GetWindowPtr(), framebuffer_size_callback);
+ window.SetFramebufferSizeCallback(framebuffer_size_callback);
...

...
// ウィンドウサイズが変更されたときのコールバック関数
- void framebuffer_size_callback(GLFWwindow *window, int width, int height) {
+ void framebuffer_size_callback(Window *window, int width, int height) {
  glViewport(0, 0, width, height);
}
...
```
ここまでで前回と同じ挙動を示しながら`Window`クラスを組み込んだプログラムを作成することができました！これで今回の記事でやることはおしまいです！お疲れ様でした！

# おわりに
~~今回は途中でやる気が在庫切れになった関係上、カスみたいな記事になってしまいましたが、次回はもう少しまともな記事を書きたいなぁと思います。(今回もシェーダー関係の話ができていない&バッファ系を一切触って無い)~~
途中でちょっとだけやる気が入荷したので[クラスの設計-改-](クラスの設計-改-)を追加しました。
(でもカス記事であることは変わらない...)
それではまた次の記事でお会いしましょう。(^^)ﾉｼ

# コード全文
<details><summary>init.hpp</summary>

```cpp
#ifndef __INIT_HPP__
#define __INIT_HPP__

#include <glad/gl.h>

#include <GLFW/glfw3.h>
#include <string>

extern bool sGLADLoaded;

void InitGLAD();

#endif // __INIT_HPP__
```
</details>
<details><summary>init.cpp</summary>

```cpp
#include "../../includes/Article1/init.hpp"

#include <stdexcept>

bool sGLADLoaded = false;

void InitGLAD() {
  if (!sGLADLoaded) {
    if (!gladLoadGL((GLADloadfunc)glfwGetProcAddress)) {
      throw std::runtime_error("Failed to initialize GLAD");
    }
    sGLADLoaded = true;
  }
}
```
</details>
<details><summary>window.hpp</summary>

```cpp
#ifndef __WINDOW_HPP__
#define __WINDOW_HPP__

#include <glad/gl.h>

#include <GLFW/glfw3.h>

#include <functional>
#include <string>

class Window; // クラスの前方宣言

// コールバック関数の型
typedef std::function<void(Window *, int, int)> FramebufferSizeCallback;
typedef std::function<void(Window *, int, int)> WindowPosCallback;
typedef std::function<void(Window *, int, int)> WindowSizeCallback;
typedef std::function<void(Window *, int)> CursorEnterCallback;
typedef std::function<void(Window *, double, double)> CursorPosCallback;
typedef std::function<void(Window *, double, double)> ScrollCallback;

namespace callback {
// コールバック関数のラッパー
void FramebufferSizeCallbackWrapper(GLFWwindow *window, int width, int height);
void WindowPosCallbackWrapper(GLFWwindow *window, int x, int y);
void WindowSizeCallbackWrapper(GLFWwindow *window, int width, int height);
void CursorEnterCallbackWrapper(GLFWwindow *window, int entered);
void CursorPosCallbackWrapper(GLFWwindow *window, double x, double y);
void ScrollCallbackWrapper(GLFWwindow *window, double xoffset, double yoffset);
} // namespace callback

// ウィンドウクラス
class Window {
private:
  GLFWwindow *mWindow;
  std::pair<double, double> mCursorPos;
  std::pair<double, double> mScroll;
  FramebufferSizeCallback mFramebufferSizeCallback;
  WindowPosCallback mWindowPosCallback;
  WindowSizeCallback mWindowSizeCallback;
  CursorEnterCallback mCursorEnterCallback;
  CursorPosCallback mCursorPosCallback;
  ScrollCallback mScrollCallback;

  // コールバック関数のフレンド宣言
  friend void(callback::FramebufferSizeCallbackWrapper)(GLFWwindow *, int width,
                                                        int height);
  friend void(callback::WindowPosCallbackWrapper)(GLFWwindow *window, int x,
                                                  int y);
  friend void(callback::WindowSizeCallbackWrapper)(GLFWwindow *window,
                                                   int width, int height);
  friend void(callback::CursorEnterCallbackWrapper)(GLFWwindow *window,
                                                    int entered);
  friend void(callback::CursorPosCallbackWrapper)(GLFWwindow *window, double x,
                                                  double y);
  friend void(callback::ScrollCallbackWrapper)(GLFWwindow *window,
                                               double xoffset, double yoffset);

public:
  Window(int width, int height, std::string title);

  std::pair<double, double> GetCursorPos() const { return mCursorPos; }
  std::pair<double, double> GetScroll() const { return mScroll; }

  void SetFramebufferSizeCallback(FramebufferSizeCallback callback) {
    mFramebufferSizeCallback = callback;
  }
  void SetWindowPosCallback(WindowPosCallback callback) {
    mWindowPosCallback = callback;
  }
  void SetWindowSizeCallback(WindowSizeCallback callback) {
    mWindowSizeCallback = callback;
  }
  void SetCursorEnterCallback(CursorEnterCallback callback) {
    mCursorEnterCallback = callback;
  }
  void SetCursorPosCallback(CursorPosCallback callback) {
    mCursorPosCallback = callback;
  }
  void SetScrollCallback(ScrollCallback callback) {
    mScrollCallback = callback;
  }

  bool IsPressed(int key) const {
    return glfwGetKey(mWindow, key) == GLFW_PRESS;
  }
  bool IsClicked(int mousebutton) const {
    return glfwGetMouseButton(mWindow, mousebutton) == GLFW_PRESS;
  }
  bool IsClosed() const { return glfwWindowShouldClose(mWindow); }

  void Close() { glfwSetWindowShouldClose(mWindow, true); }
  void Update();
  void Fill(float r, float g, float b, float a);
};

#endif // __WINDOW_HPP__
```
</details>
<details><summary>window.cpp</summary>

```cpp
#include "../../includes/Article1/init.hpp"
#include "../../includes/Article1/window.hpp"

namespace callback {
void FramebufferSizeCallbackWrapper(GLFWwindow *window, int width, int height) {
  Window *ptr = (Window *)glfwGetWindowUserPointer(window); // 紐付けたポインタ
  if (ptr->mFramebufferSizeCallback) { // コールバック関数が登録されている
    ptr->mFramebufferSizeCallback(ptr, width, height);
  }
}

void WindowPosCallbackWrapper(GLFWwindow *window, int x, int y) {
  Window *ptr = (Window *)glfwGetWindowUserPointer(window);
  if (ptr->mWindowPosCallback) {
    ptr->mWindowPosCallback(ptr, x, y);
  }
}

void WindowSizeCallbackWrapper(GLFWwindow *window, int width, int height) {
  Window *ptr = (Window *)glfwGetWindowUserPointer(window);
  if (ptr->mWindowSizeCallback) {
    ptr->mWindowSizeCallback(ptr, width, height);
  }
}

void CursorEnterCallbackWrapper(GLFWwindow *window, int entered) {
  Window *ptr = (Window *)glfwGetWindowUserPointer(window);
  if (ptr->mCursorEnterCallback) {
    ptr->mCursorEnterCallback(ptr, entered);
  }
}

void CursorPosCallbackWrapper(GLFWwindow *window, double x, double y) {
  Window *ptr = (Window *)glfwGetWindowUserPointer(window);
  ptr->mCursorPos = {x, y};
  if (ptr->mCursorPosCallback) {
    ptr->mCursorPosCallback(ptr, x, y);
  }
}

void ScrollCallbackWrapper(GLFWwindow *window, double xoffset, double yoffset) {
  Window *ptr = (Window *)glfwGetWindowUserPointer(window);
  ptr->mScroll = {xoffset, yoffset};
  if (ptr->mScrollCallback) {
    ptr->mScrollCallback(ptr, xoffset, yoffset);
  }
}
} // namespace callback

Window::Window(int width, int height, std::string title) {
  // ウィンドウの作成
  mWindow = glfwCreateWindow(width, height, title.c_str(), NULL, NULL);
  glfwMakeContextCurrent(mWindow);
  InitGLAD();

  glfwSetWindowUserPointer(mWindow, this); // 自分のポインタをGLFWwindowと紐付け
  // コールバックを登録
  glfwSetFramebufferSizeCallback(mWindow,
                                 callback::FramebufferSizeCallbackWrapper);
  glfwSetWindowPosCallback(mWindow, callback::WindowPosCallbackWrapper);
  glfwSetWindowSizeCallback(mWindow, callback::WindowSizeCallbackWrapper);
  glfwSetCursorEnterCallback(mWindow, callback::CursorEnterCallbackWrapper);
  glfwSetCursorPosCallback(mWindow, callback::CursorPosCallbackWrapper);
  glfwSetScrollCallback(mWindow, callback::ScrollCallbackWrapper);
}

void Window::Update() {
  // バッファのスワップとイベントのポーリング
  glfwSwapBuffers(mWindow);
  glfwPollEvents();
}

void Window::Fill(float r, float g, float b, float a) {
  // 背景色の設定とクリア
  glClearColor(r, g, b, a);
  glClear(GL_COLOR_BUFFER_BIT);
}
```
</details>
<details><summary>main.cpp</summary>

```cpp
#include <glad/gl.h>

#include <GLFW/glfw3.h>
#include <iostream>

#include "../../includes/Article1/window.hpp"

// 関数宣言
void framebuffer_size_callback(Window *window, int width, int height);
unsigned int createShaderProgram(const char *vertexSource,
                                 const char *fragmentSource);

// 頂点シェーダーのソースコード
const char *vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
void main() {
    gl_Position = vec4(aPos, 1.0);
}
)";

// フラグメントシェーダーのソースコード
const char *fragmentShaderSource = R"(
#version 330 core
out vec4 FragColor;
void main() {
    FragColor = vec4(1.0, 0.5, 0.2, 1.0);
}
)";

int main() {
  // GLFWの初期化
  if (!glfwInit()) {
    std::cerr << "Failed to initialize GLFW" << std::endl;
    return -1;
  }

  // OpenGLのバージョン設定 (3.3 Core Profile)
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

  // ウィンドウの作成
  Window window(800, 600, "OpenGL");

  // ビューポートの設定
  glViewport(0, 0, 800, 600);
  window.SetFramebufferSizeCallback(framebuffer_size_callback);

  // シェーダープログラムの作成
  unsigned int shaderProgram =
      createShaderProgram(vertexShaderSource, fragmentShaderSource);

  // 三角形の頂点データ
  float vertices[] = {
      0.0f,  0.5f,  0.0f, // 上
      -0.5f, -0.5f, 0.0f, // 左下
      0.5f,  -0.5f, 0.0f  // 右下
  };

  // VAOとVBOの作成
  unsigned int VAO, VBO;
  glGenVertexArrays(1, &VAO);
  glGenBuffers(1, &VBO);

  // VAOの設定
  glBindVertexArray(VAO);

  // VBOの設定
  glBindBuffer(GL_ARRAY_BUFFER, VBO);
  glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

  // 頂点属性の設定
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0);
  glEnableVertexAttribArray(0);

  // メインループ
  while (!window.IsClosed()) {
    // 入力処理
    if (window.IsPressed(GLFW_KEY_ESCAPE)) {
      window.Close();
    }

    // 背景色の設定とクリア
    window.Fill(0.2f, 0.2f, 0.2f, 1.0f);

    // シェーダープログラムの使用
    glUseProgram(shaderProgram);

    // 三角形の描画
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    // バッファのスワップとイベントのポーリング
    window.Update();
  }

  // リソースの解放
  glDeleteVertexArrays(1, &VAO);
  glDeleteBuffers(1, &VBO);
  glDeleteProgram(shaderProgram);

  // GLFWの終了処理
  glfwTerminate();
  return 0;
}

// ウィンドウサイズが変更されたときのコールバック関数
void framebuffer_size_callback(Window *window, int width, int height) {
  glViewport(0, 0, width, height);
}

// シェーダープログラムの作成関数
unsigned int createShaderProgram(const char *vertexSource,
                                 const char *fragmentSource) {
  // 頂点シェーダーのコンパイル
  unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
  glShaderSource(vertexShader, 1, &vertexSource, nullptr);
  glCompileShader(vertexShader);

  // コンパイルエラーチェック
  int success;
  char infoLog[512];
  glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
  if (!success) {
    glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog);
    std::cerr << "ERROR::VERTEX_SHADER::COMPILATION_FAILED\n"
              << infoLog << std::endl;
  }

  // フラグメントシェーダーのコンパイル
  unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
  glShaderSource(fragmentShader, 1, &fragmentSource, nullptr);
  glCompileShader(fragmentShader);

  // コンパイルエラーチェック
  glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
  if (!success) {
    glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog);
    std::cerr << "ERROR::FRAGMENT_SHADER::COMPILATION_FAILED\n"
              << infoLog << std::endl;
  }

  // シェーダープログラムのリンク
  unsigned int shaderProgram = glCreateProgram();
  glAttachShader(shaderProgram, vertexShader);
  glAttachShader(shaderProgram, fragmentShader);
  glLinkProgram(shaderProgram);

  // リンクエラーチェック
  glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
  if (!success) {
    glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
    std::cerr << "ERROR::SHADER_PROGRAM::LINKING_FAILED\n"
              << infoLog << std::endl;
  }

  // シェーダーオブジェクトの削除
  glDeleteShader(vertexShader);
  glDeleteShader(fragmentShader);

  return shaderProgram;
}
```
</details>