[C++]3D描画エンジン1

  1. 試しにポリゴンを表示してみる
  2. ポリゴンの光の色表示の基礎となっている「拡散反射光:ディフューズ値」の値を変更してみる
  3. シンプルな.ply形式の3Dデータフォーマットを読み取ってポリゴン表示してみる
  4. Blenderをインストールして3Dのサイコロとテクスチャ画像を書き出してXYZ軸を見極める
  5. 別の描画スクリーンを作って、Windowsみたいなサブのウィンドウを描く
  6. エディタを自作して視点カメラを動かせるようにする
  7. キーボードのWSキーでカメラ視点を手前や奥に移動できるようにする
  8. キーボードのDAキーとEQキーでカメラ視点を左右や上下に移動できるようにする
  9. Unityのアセットストアからとってきたテクスチャ1枚のシンプルな3DモデルをDXのモデルビューアで.mv1形式に変換して表示させてみる
  10. MV1形式の3Dモデルをアニメーション描画する
  11. 複数の3DモデルやアニメをResourceクラスで2重ロードしないよう読み込んで複数アニメを切替えする
  12. .ply形式の3DモデルデータをResourceクラスを継承したDataPly構造体で読み込む
  13. Vector3に色々な関数を用意して3D空間の当たり判定をする
  14. ドラッグできるオブジェクトのコントロール点を用意して当たり判定の球やカプセルなどの幅や高さを調整できるようにする
  15. 3Dモデルをクオータニオンで安定して回転できるようにする
  16. 自作したシェーダー言語をコンパイルしてGPU上で動くプログラムを実感する
  17. ディレクショナルライト、ポイントライト、スポットライトを生成してシェーダと連携させる
  18. 3Dモデルの表面のマテリアルを定義してシェーダでライトの計算と連携させる

試しにポリゴンを表示してみる

main.cppにプログラムを追加し、ポリゴン画像が正しく表示できるかテストします。
(DXの
プロジェクトとScreen.hはシューティングの別記事を参考に作っておいてください、
画像ファイルはシュー ティングの別記事を参考にImage/boss1.pngで保管されている前提で進めます)

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include <vector> 
#include "Screen.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    DxLib::SetGraphMode(Screen::Width, Screen::Height, 32 ) ; // 画面サイズWidth×Heightのカラービット数32ビットで起動
    DxLib::SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    DxLib::ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    DxLib::SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    DxLib::SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    // DXライブラリの初期化
    if (DxLib::DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    DxLib::SetDrawScreen(DX_SCREEN_BACK);

    // ゲームのwhileループを開始する前の初期化処理
    int texImage = -1;
    texImage = LoadGraph("Image/boss1.png");
    assert(texImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる
    float imgWidth, imgHeight;
    DxLib::GetGraphSizeF(texImage, &imgWidth, &imgHeight); // テクスチャ画像のサイズを得る

    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 0; keyControlXYZ.y = Screen::Height; keyControlXYZ.z = 0;
    float keyControlAngle = 0.0f; // 回転
    int animTime = 0; // アニメーションの時刻カウンタ

    DxLib::ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (DxLib::ProcessMessage() == 0)
    {    // ProcessMessage() == 0になるのは×ボタン押したときなど
        DxLib::ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに
        int PadInput = DxLib::GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        float x = keyControlXYZ.x; float y = keyControlXYZ.y; float z = 0.0f;
        float width = imgWidth; float height = imgHeight; // テクスチャ画僧のサイズ
        float scaleX = 0.5f; float scaleY = 0.5f; // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)でz軸(画面奥方向)まわりに画像を回転
        if (PadInput & PAD_INPUT_A)      keyControlAngle += speed;
        else if (PadInput & PAD_INPUT_B) keyControlAngle -= speed;

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数
        const float xRotate = 0 * Deg2Rad;  const float yRotate = 0 * Deg2Rad; const float zRotate = keyControlAngle * Deg2Rad;
       
        const float u0 = 0.0f; const float v0 = 0.0f; // テクスチャのUV切り出し起点(u:0~1.0, v:0~1.0)
        const float u1 = 1.0f; const float v1 = 1.0f; // テクスチャのUV切り出し終点 1.0は画像の右下を表す
       
        float centerX = x + width * scaleX / 2; // 四角形の中心(x+width/2,y-height/2,z)
        float centerY = y - height * scaleY / 2;

        // [Unityのxyz方向と回転] https://gametukurikata.com/basic/xyzaxis
        // [Unityの回転ZXYの順で適用したもの] https://light11.hatenadiary.com/entry/2019/01/24/223705
        MATRIX TransformMatrix = MGetTranslate(VGet(-centerX, -centerY, -z)); // 四角形の中心を 原点(0,0,0)に一旦移動させてから回転させる
       
        // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
        TransformMatrix = MMult(TransformMatrix, MGetRotZ(zRotate));
        TransformMatrix = MMult(TransformMatrix, MGetRotX(xRotate));
        TransformMatrix = MMult(TransformMatrix, MGetRotY(yRotate));
        TransformMatrix = MMult(TransformMatrix, MGetTranslate(VGet(centerX, centerY, z))); // 四角形の中心ぶんだけ平行移動させて戻す

        // 四角ポリゴンのインデックス順
        // 左手系(時計回りが表面)
        // ↓(x,y)
        //  0-1 4 ←(x + width * scale, y)
        //  |/ /|
        //  2 3-5 ←(x + width * scale, y - height * scale)
        // ↑(x, y - height * scale)     ↑[UIのy方向は下だが3D空間上は上が+方向なのでheightを-1方向に]
        std::vector<VERTEX3D> rectVertex;
        rectVertex.resize(4);
        std::vector<unsigned int> rectIndex;
        rectIndex.resize(6);  // 4頂点のうち 右上(1と4)と左下(2と3)は同位置に2つ頂点を使う(三角形2つぶんのため) 4頂点 + 2被り = 6
        // 4頂点分のデータをセット
        // 左上(0)
        rectVertex[0].pos = VTransform(VGet(x, y, z), TransformMatrix);
        rectVertex[0].norm = VGet(0.0f, 0.0f, -1.0f); // 法線ベクトル(面の反射方向:z方向-1.0のベクトルなら光がzマイナス方向に反射)
        rectVertex[0].dif = GetColorU8(255, 255, 255, 255); // 拡散反射光の色
        rectVertex[0].spc = GetColorU8(255, 255, 255, 255); // 鏡面反射光の色
        rectVertex[0].u = u0; // テクスチャ切り出し点 U(横方向)
        rectVertex[0].v = v0; // テクスチャ切り出し点 V(縦方向)
        rectVertex[0].su = 0.0f; // 補助テクスチャのUV(いまはない)
        rectVertex[0].sv = 0.0f;

        // 右上(1と4)
        rectVertex[1].pos = VTransform(VGet(x + width * scaleX, y, z), TransformMatrix);
        rectVertex[1].norm = VGet(0.0f, 0.0f, -1.0f);
        rectVertex[1].dif = GetColorU8(255, 255, 255, 255);
        rectVertex[1].spc = GetColorU8(255, 255, 255, 255);
        rectVertex[1].u = u1;
        rectVertex[1].v = v0;
        rectVertex[1].su = 0.0f;
        rectVertex[1].sv = 0.0f;

        // 左下(2と3)
        rectVertex[2].pos = VTransform(VGet(x, y - height * scaleY, z), TransformMatrix);
        rectVertex[2].norm = VGet(0.0f, 0.0f, -1.0f);
        rectVertex[2].dif = GetColorU8(255, 255, 255, 255);
        rectVertex[2].spc = GetColorU8(255, 255, 255, 255);
        rectVertex[2].u = u0;
        rectVertex[2].v = v1;
        rectVertex[2].su = 0.0f;
        rectVertex[2].sv = 0.0f;

        // 右下(5)
        rectVertex[3].pos = VTransform(VGet(x + width * scaleX, y - height * scaleY, z), TransformMatrix);
        rectVertex[3].norm = VGet(0.0f, 0.0f, -1.0f);
        rectVertex[3].dif = GetColorU8(255, 255, 255, 255);
        rectVertex[3].spc = GetColorU8(255, 255, 255, 255);
        rectVertex[3].u = u1;
        rectVertex[3].v = v1;
        rectVertex[3].su = 0.0f;
        rectVertex[3].sv = 0.0f;

        // 2ポリゴン分のインデックス順データをセット
        rectIndex[0] = 0;
        rectIndex[1] = 1; // 0-1 4
        rectIndex[2] = 2; // |/ /|   0,1,2で左上ポリゴンを 3,4,5で右下ポリゴンを描く
        rectIndex[3] = 2; // 2 3-5
        rectIndex[4] = 1;
        rectIndex[5] = 3;

        ++animTime;
        bool isDrawPolygon = animTime % 60 > 30; // 30フレームに1回三角ポリゴン(1個)と四角(2個)を描くのを切り替える↓
        DxLib::DrawPolygon32bitIndexed3D(rectVertex.data(), rectVertex.size(), rectIndex.data(), (isDrawPolygon) ? 1 : 2, texImage, FALSE); // 三角形ポリゴン2枚を描画

        DxLib::DrawSphere3D(VGet(x, y, z), 4.0f, 8, GetColor(255, 255, 0), GetColor(255, 255, 0),TRUE); // 左上に黄色の3D球を描く
        DxLib::DrawFormatString(0, 0, GetColor(255, 255, 0), "x:%f y:%f Angle:%f", x, y, keyControlAngle);

        DxLib::ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    DxLib::WaitKey();
    // DXライブラリの後始末
    DxLib::DxLib_End();
    // ソフトの終了
    return 0;
}

main.cppのプログラムを変更し、ポリゴンのインデックスの順番がポリゴンの表(時計回 り)と裏(反時計回り)に 影響を与えることを確認しましょう。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include <vector>
#include "Screen.h"
// #includeで別ファイルのヘッダファイル.hをこのコード内に読み込む

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    DxLib::SetGraphMode(Screen::Width, Screen::Height, 32 ) ; // 画面サイズWidth×Heightのカラービット数32ビットで起動
    DxLib::SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    DxLib::ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    DxLib::SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    DxLib::SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    // DXライブラリの初期化
    if (DxLib::DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    DxLib::SetDrawScreen(DX_SCREEN_BACK);

    // ゲームのwhileループを開始する前の初期化処理
    int texImage = -1;
    texImage = DxLib::LoadGraph("Image/boss1.png");
    assert(texImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる
    float imgWidth, imgHeight;
    DxLib::GetGraphSizeF(texImage, &imgWidth, &imgHeight); // テクスチャ画像のサイズを得る

    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 0; keyControlXYZ.y = Screen::Height; keyControlXYZ.z = 0;
    float keyControlAngle = 0.0f; // 回転
    int animTime = 0; // アニメーションの時刻カウンタ

    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);


    DxLib::ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (ProcessMessage() == 0)
    {    // ProcessMessage() == 0になるのは×ボタン押したときなど
        ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに
        int PadInput = DxLib::GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        float x = keyControlXYZ.x; float y = keyControlXYZ.y; float z = 0.0f;
        float width = imgWidth; float height = imgHeight; // テクスチャ画僧のサイズ
        float scaleX = 0.5f; float scaleY = 0.5f; // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)でz軸(画面奥方向)まわりに画像を回転
        if (PadInput & PAD_INPUT_A)      keyControlAngle += speed;
        else if (PadInput & PAD_INPUT_B) keyControlAngle -= speed;

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数
        const float xRotate = 0 * Deg2Rad;  const float yRotate = keyControlAngle * Deg2Rad; const float zRotate = 0 * Deg2Rad;
       
        const float u0 = 0.0f; const float v0 = 0.0f; // テクスチャのUV切り出し起点(u:0~1.0, v:0~1.0)
        const float u1 = 1.0f; const float v1 = 1.0f; // テクスチャのUV切り出し終点 1.0は画像の右下を表す
       
        float centerX = x + width * scaleX / 2; // 四角形の中心(x+width/2,y-height/2,z)
        float centerY = y - height * scaleY / 2;

        // [Unityのxyz方向と回転] https://gametukurikata.com/basic/xyzaxis
        // [Unityの回転ZXYの順で適用したもの] https://light11.hatenadiary.com/entry/2019/01/24/223705
        MATRIX TransformMatrix = MGetTranslate(VGet(-centerX, -centerY, -z)); // 四角形の中心を 原点(0,0,0)に一旦移動させてから回転させる
       
        // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
        TransformMatrix = MMult(TransformMatrix, MGetRotZ(zRotate));
        TransformMatrix = MMult(TransformMatrix, MGetRotX(xRotate));
        TransformMatrix = MMult(TransformMatrix, MGetRotY(yRotate));
        TransformMatrix = MMult(TransformMatrix, MGetTranslate(VGet(centerX, centerY, z))); // 四角形の中心ぶんだけ平行移動させて戻す

        // 四角ポリゴンのインデックス順
        // 左手系(時計回りが表面)
        // ↓(x,y)
        //  0-1 4 ←(x + width * scale, y)
        //  |/ /|
        //  2 3-5 ←(x + width * scale, y - height * scale)
        // ↑(x, y - height * scale)     ↑[UIのy方向は下だが3D空間上は上が+方向なのでheightを-1方向に]
        std::vector<VERTEX3D> rectVertex;
        rectVertex.resize(4);
        std::vector<unsigned int> rectIndex;
        rectIndex.resize(6);  // 4頂点のうち 右上(1と4)と左下(2と3)は同位置に2つ頂点を使う(三角形2つぶんのため) 4頂点 + 2被り = 6
        // 4頂点分のデータをセット
        // 左上(0)
        rectVertex[0].pos = VTransform(VGet(x, y, z), TransformMatrix);
        rectVertex[0].norm = VGet(0.0f, 0.0f, -1.0f); // 法線ベクトル(面の反射方向:z方向-1.0のベクトルなら光がzマイナス方向に反射)
        rectVertex[0].dif = GetColorU8(255, 255, 255, 255); // 拡散反射光の色
        rectVertex[0].spc = GetColorU8(255, 255, 255, 255); // 鏡面反射光の色
        rectVertex[0].u = u0; // テクスチャ切り出し点 U(横方向)
        rectVertex[0].v = v0; // テクスチャ切り出し点 V(縦方向)
        rectVertex[0].su = 0.0f; // 補助テクスチャのUV(いまはない)
        rectVertex[0].sv = 0.0f;

        // 右上(1と4)
        rectVertex[1].pos = VTransform(VGet(x + width * scaleX, y, z), TransformMatrix);
        rectVertex[1].norm = VGet(0.0f, 0.0f, -1.0f);
        rectVertex[1].dif = GetColorU8(255, 255, 255, 255);
        rectVertex[1].spc = GetColorU8(255, 255, 255, 255);
        rectVertex[1].u = u1;
        rectVertex[1].v = v0;
        rectVertex[1].su = 0.0f;
        rectVertex[1].sv = 0.0f;

        // 左下(2と3)
        rectVertex[2].pos = VTransform(VGet(x, y - height * scaleY, z), TransformMatrix);
        rectVertex[2].norm = VGet(0.0f, 0.0f, -1.0f);
        rectVertex[2].dif = GetColorU8(255, 255, 255, 255);
        rectVertex[2].spc = GetColorU8(255, 255, 255, 255);
        rectVertex[2].u = u0;
        rectVertex[2].v = v1;
        rectVertex[2].su = 0.0f;
        rectVertex[2].sv = 0.0f;

        // 右下(5)
        rectVertex[3].pos = VTransform(VGet(x + width * scaleX, y - height * scaleY, z), TransformMatrix);
        rectVertex[3].norm = VGet(0.0f, 0.0f, -1.0f);
        rectVertex[3].dif = GetColorU8(255, 255, 255, 255);
        rectVertex[3].spc = GetColorU8(255, 255, 255, 255);
        rectVertex[3].u = u1;
        rectVertex[3].v = v1;
        rectVertex[3].su = 0.0f;
        rectVertex[3].sv = 0.0f;

        // 2ポリゴン分のインデックス順データをセット
        rectIndex[0] = 2;
        rectIndex[1] = 1; // 0-1 4
        rectIndex[2] = 0; // |/ /|   0,1,2で左上ポリゴンを 3,4,5で右下ポリゴンを描く
        rectIndex[3] = 2; // 2 3-5
        rectIndex[4] = 1;
        rectIndex[5] = 3;

        ++animTime;
        bool isDrawPolygon = animTime % 60 > 30; // 30フレームに1回三角ポリゴン(1個)と四角(2個)を描くのを切り替える↓
        DxLib::DrawPolygon32bitIndexed3D(rectVertex.data(), rectVertex.size(), rectIndex.data(), (isDrawPolygon) ? 1 : 2, texImage, FALSE); // 三角形ポリゴン2枚を描画

        DxLib::DrawSphere3D(VGet(x, y, z), 4.0f, 8, GetColor(255, 255, 0), GetColor(255, 255, 0),TRUE); // 左上に黄色の3D球を描く
        DxLib::DrawFormatString(0, 0, GetColor(255, 255, 0), "x:%f y:%f Angle:%f", x, y, keyControlAngle);

        DxLib::ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    DxLib::WaitKey();
    // DXライブラリの後始末
    DxLib::DxLib_End();
    // ソフトの終了
    return 0;
}

いかがですか?キーボードのzキーとxキーでポリゴンをまわしてみ てポリゴンを裏から見ると表示されることが確認できましたか?




ポリゴンの光の色表示の基礎となっている「拡散反射光: ディ フューズ値」の値を変更してみる

main.cppのプログラムを変更し、ポリゴンのdifの値(拡散反射光:ディフューズ値)がポリゴンの反射 する色に影響を与えることを確認しましょう。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include <vector>
#include "Screen.h"
// #includeで別ファイルのヘッダファイル.hをこのコード内に読み込む

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    DxLib::SetGraphMode(Screen::Width, Screen::Height, 32 ) ; // 画面サイズWidth×Heightのカラービット数32ビットで起動
    DxLib::SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    DxLib::ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    DxLib::SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    DxLib::SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    // DXライブラリの初期化
    if (DxLib::DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    DxLib::SetDrawScreen(DX_SCREEN_BACK);

    // ゲームのwhileループを開始する前の初期化処理
    int texImage = -1;
    texImage = LoadGraph("Image/boss1.png");
    assert(texImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる
    float imgWidth, imgHeight;
    DxLib::GetGraphSizeF(texImage, &imgWidth, &imgHeight); // テクスチャ画像のサイズを得る

    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 0; keyControlXYZ.y = Screen::Height; keyControlXYZ.z = 0;
    float keyControlAngle = 0.0f; // 回転
    int animTime = 0; // アニメーションの時刻カウンタ

    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    DxLib::ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (DxLib::ProcessMessage() == 0)
    {    // ProcessMessage() == 0になるのは×ボタン押したときなど
        DxLib::ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに
        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        float x = keyControlXYZ.x; float y = keyControlXYZ.y; float z = 0.0f;
        float width = imgWidth; float height = imgHeight; // テクスチャ画僧のサイズ
        float scaleX = 0.5f; float scaleY = 0.5f; // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)でz軸(画面奥方向)まわりに画像を回転
        if (PadInput & PAD_INPUT_A)      keyControlAngle += speed;
        else if (PadInput & PAD_INPUT_B) keyControlAngle -= speed;

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数
        const float xRotate = 0 * Deg2Rad;  const float yRotate = 0 * Deg2Rad; const float zRotate = keyControlAngle * Deg2Rad;
       
        const float u0 = 0.0f; const float v0 = 0.0f; // テクスチャのUV切り出し起点(u:0~1.0, v:0~1.0)
        const float u1 = 1.0f; const float v1 = 1.0f; // テクスチャのUV切り出し終点 1.0は画像の右下を表す
       
        float centerX = x + width * scaleX / 2; // 四角形の中心(x+width/2,y-height/2,z)
        float centerY = y - height * scaleY / 2;

        // [Unityのxyz方向と回転] https://gametukurikata.com/basic/xyzaxis
        // [Unityの回転ZXYの順で適用したもの] https://light11.hatenadiary.com/entry/2019/01/24/223705
        MATRIX TransformMatrix = MGetTranslate(VGet(-centerX, -centerY, -z)); // 四角形の中心を 原点(0,0,0)に一旦移動させてから回転させる
       
        // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
        TransformMatrix = MMult(TransformMatrix, MGetRotZ(zRotate));
        TransformMatrix = MMult(TransformMatrix, MGetRotX(xRotate));
        TransformMatrix = MMult(TransformMatrix, MGetRotY(yRotate));
        TransformMatrix = MMult(TransformMatrix, MGetTranslate(VGet(centerX, centerY, z))); // 四角形の中心ぶんだけ平行移動させて戻す

        // 四角ポリゴンのインデックス順
        // 左手系(時計回りが表面)
        // ↓(x,y)
        //  0-1 4 ←(x + width * scale, y)
        //  |/ /|
        //  2 3-5 ←(x + width * scale, y - height * scale)
        // ↑(x, y - height * scale)     ↑[UIのy方向は下だが3D空間上は上が+方向なのでheightを-1方向に]
        std::vector<VERTEX3D> rectVertex;
        rectVertex.resize(4);
        std::vector<unsigned int> rectIndex;
        rectIndex.resize(6);  // 4頂点のうち 右上(1と4)と左下(2と3)は同位置に2つ頂点を使う(三角形2つぶんのため) 4頂点 + 2被り = 6
        // 4頂点分のデータをセット
        // 左上(0)
        rectVertex[0].pos = VTransform(VGet(x, y, z), TransformMatrix);
        rectVertex[0].norm = VGet(0.0f, 0.0f, -1.0f); // 法線ベクトル(面の反射方向:z方向-1.0のベクトルなら光がzマイナス方向に反射)
        rectVertex[0].dif = GetColorU8(255, 0, 0, 255); // 拡散反射光の色
        rectVertex[0].spc = GetColorU8(255, 255, 255, 255); // 鏡面反射光の色
        rectVertex[0].u = u0; // テクスチャ切り出し点 U(横方向)
        rectVertex[0].v = v0; // テクスチャ切り出し点 V(縦方向)
        rectVertex[0].su = 0.0f; // 補助テクスチャのUV(いまはない)
        rectVertex[0].sv = 0.0f;

        // 右上(1と4)
        rectVertex[1].pos = VTransform(VGet(x + width * scaleX, y, z), TransformMatrix);
        rectVertex[1].norm = VGet(0.0f, 0.0f, -1.0f);
        rectVertex[1].dif = GetColorU8(0, 0, 255, 255);
        rectVertex[1].spc = GetColorU8(255, 255, 255, 255);
        rectVertex[1].u = u1;
        rectVertex[1].v = v0;
        rectVertex[1].su = 0.0f;
        rectVertex[1].sv = 0.0f;

        // 左下(2と3)
        rectVertex[2].pos = VTransform(VGet(x, y - height * scaleY, z), TransformMatrix);
        rectVertex[2].norm = VGet(0.0f, 0.0f, -1.0f);
        rectVertex[2].dif = GetColorU8(255, 255, 255, 255);
        rectVertex[2].spc = GetColorU8(255, 255, 255, 255);
        rectVertex[2].u = u0;
        rectVertex[2].v = v1;
        rectVertex[2].su = 0.0f;
        rectVertex[2].sv = 0.0f;

        // 右下(5)
        rectVertex[3].pos = VTransform(VGet(x + width * scaleX, y - height * scaleY, z), TransformMatrix);
        rectVertex[3].norm = VGet(0.0f, 0.0f, -1.0f);
        rectVertex[3].dif = GetColorU8(255, 255, 255, 255);
        rectVertex[3].spc = GetColorU8(255, 255, 255, 255);
        rectVertex[3].u = u1;
        rectVertex[3].v = v1;
        rectVertex[3].su = 0.0f;
        rectVertex[3].sv = 0.0f;

        // 2ポリゴン分のインデックス順データをセット
        rectIndex[0] = 0;
        rectIndex[1] = 1; // 0-1 4
        rectIndex[2] = 2; // |/ /|   0,1,2で左上ポリゴンを 3,4,5で右下ポリゴンを描く
        rectIndex[3] = 2; // 2 3-5
        rectIndex[4] = 1;
        rectIndex[5] = 3;

        ++animTime;
        bool isDrawPolygon = animTime % 60 > 30; // 30フレームに1回三角ポリゴン(1個)と四角(2個)を描くのを切り替える↓
        DxLib::DrawPolygon32bitIndexed3D(rectVertex.data(), rectVertex.size(), rectIndex.data(), (isDrawPolygon) ? 1 : 2, texImage, FALSE); // 三角形ポリゴン2枚を描画

        DxLib::DrawSphere3D(VGet(x, y, z), 4.0f, 8, GetColor(255, 255, 0), GetColor(255, 255, 0),TRUE); // 左上に黄色の3D球を描く
        DxLib::DrawFormatString(0, 0, GetColor(255, 255, 0), "x:%f y:%f Angle:%f", x, y, keyControlAngle);

        DxLib::ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    DxLib::WaitKey();
    // DXライブラリの後始末
    DxLib_End();
    // ソフトの終了
    return 0;
}




シンプルな.ply形式の3Dデータフォーマットを読み取ってポリゴン表示してみる

メモ帳を開いて下記データをコピペして dicetest.plyという名前でImageフォルダに保存しましょう。

ply
format ascii 1.0
comment Created by Blender 3.5.1 - www.blender.org
element vertex 14
property float x
property float y
property float z
property float nx
property float ny
property float nz
property float s
property float t
element face 12
property list uchar uint vertex_indices
end_header
-1.000000 1.000000 1.000000 0.000000 0.000000 1.000000 0.875000 0.500000
1.000000 -1.000000 1.000000 0.577350 -0.577350 0.577350 0.625000 0.750000
1.000000 1.000000 1.000000 0.577350 0.577350 0.577350 0.625000 0.500000
-1.000000 -1.000000 -1.000000 0.000000 -1.000000 0.000000 0.375000 1.000000
1.000000 -1.000000 -1.000000 0.577350 -0.577350 -0.577350 0.375000 0.750000
-1.000000 -1.000000 1.000000 -1.000000 0.000000 0.000000 0.625000 0.000000
-1.000000 1.000000 -1.000000 -0.707107 0.707107 0.000000 0.375000 0.250000
-1.000000 -1.000000 -1.000000 -1.000000 0.000000 0.000000 0.375000 0.000000
1.000000 1.000000 -1.000000 0.577350 0.577350 -0.577350 0.375000 0.500000
-1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 0.125000 0.750000
-1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 0.125000 0.500000
-1.000000 1.000000 1.000000 -0.707107 0.707107 0.000000 0.625000 0.250000
-1.000000 -1.000000 1.000000 0.000000 0.000000 1.000000 0.875000 0.750000
-1.000000 -1.000000 1.000000 0.000000 -1.000000 0.000000 0.625000 1.000000
3 0 1 2
3 1 3 4
3 5 6 7
3 8 9 10
3 2 4 8
3 8 11 2
3 0 12 1
3 1 13 3
3 5 11 6
3 8 4 9
3 2 1 4
3 8 6 11


Ply.hを新規作成して、ファイルから.ply形式の3Dフォーマットを読み取るための処理を準 備しましょう。

#ifndef PLY_H_
#define PLY_H_

#include <assert.h> // 読込み失敗表示用
#include <string>
#include <vector>
#include <memory>
#include <utility> // ペア型std::pairを使う
#include <typeindex> // std::type_indexを使えばunordered_mapの辞書のキーにfloatなどのタイプの型を指定できるようになる
#include <unordered_map>
#include <fstream> // ファイル読み出しifstreamに必要
#include <sstream> // 文字列ストリームに必要
#include <functional> // std::functionでラムダ式を関数の引数にできる

namespace Ply //[.ply形式の3Dデータを読みだす]https://ja.wikipedia.org/wiki/PLY_(%E3%83%95%E3%82 %A1%E3%82%A4%E3%83%AB%E5%BD%A2%E5%BC%8F)
{
    enum class FileFormat { ASCII, BINARY };

    const std::type_index CHAR = std::type_index(typeid(int8_t));
    const std::type_index UCHAR = std::type_index(typeid(uint8_t));
    const std::type_index SHORT = std::type_index(typeid(int16_t));
    const std::type_index USHORT = std::type_index(typeid(uint16_t));
    const std::type_index INT = std::type_index(typeid(int32_t));
    const std::type_index UINT = std::type_index(typeid(uint32_t));
    const std::type_index FLOAT = std::type_index(typeid(float));
    const std::type_index DOUBLE = std::type_index(typeid(double));

    // ビックエンディアン方式でメモリに数値が格納される環境かチェック https://qiita.com/nia_tn1012/items/340a1f0ad71bf6085f7f
    static bool isBigEndian()
    {    // 0x01020304 という 4バイト数値 が メモリ上に 0x01 0x02 0x03 0x04 と上位バイトから順に格納する方式ならビッグエンディアンと判定
        const uint32_t i = 0x01020304; //                ↑
        return reinterpret_cast<const uint8_t*>(&i)[0] == 1;
    }

    const std::unordered_map<std::type_index, int> dataTypeByteSize // データタイプFLOATなど から そのステップサイズ(データ量)=4バイト への変換辞書
    {
        { CHAR, sizeof(char) }, // sizeof(char) = 1バイト
        { UCHAR, sizeof(unsigned char) }, // sizeof(unsigned char) = 1バイト
        { SHORT, sizeof(int16_t) }, // sizeof(int16_t) = 2バイト
        { USHORT, sizeof(uint16_t) }, // sizeof(uint16_t) = 2バイト
        { INT, sizeof(int32_t) }, // sizeof(int32_t) = 4バイト
        { UINT, sizeof(uint32_t) }, // sizeof(uint32_t) = 4バイト
        { FLOAT, sizeof(float) }, // sizeof(float) = 4バイト
        { DOUBLE, sizeof(double) } // sizeof(double) = 8バイト
    };

    static_assert(sizeof(char) == 1, "sizeof(char) = 1バイトじゃないので何かおかしい");
    static_assert(sizeof(unsigned char) == 1, "sizeof(unsigned char) = 1バイトじゃないので何かおかしい");
    static_assert(sizeof(int16_t) == 2, "sizeof(int16_t) = 2バイトじゃないので何かおかしい");
    static_assert(sizeof(uint16_t) == 2, "sizeof(uint16_t) = 2バイトじゃないので何かおかしい");
    static_assert(sizeof(int32_t) == 4, "sizeof(int32_t) = 4バイトじゃないので何かおかしい");
    static_assert(sizeof(uint32_t) == 4, "sizeof(uint32_t) = 4バイトじゃないので何かおかしい");
    static_assert(sizeof(float) == 4, "sizeof(float) = 4バイトじゃないので何かおかしい");
    static_assert(sizeof(double) == 8, "sizeof(double) = 8バイトじゃないので何かおかしい");

    const std::unordered_map<std::string, std::type_index> strToDataType // "int32"などの文字列 → INTなどそのタイプ(std::type_index)への変換辞書
    {
        { "char", CHAR }, { "uchar", UCHAR }, { "uint8", UCHAR },
        { "short", SHORT }, { "ushort", USHORT }, { "uint16", USHORT },
        { "int", INT }, { "int32", INT }, { "uint", UINT }, { "uint32", UINT },
        { "float", FLOAT }, { "float32", FLOAT },
        { "double", DOUBLE }, { "float64", DOUBLE }
    };

    const std::unordered_map<std::type_index, std::string> dataTypeToStr // INTなどそのタイプ から "int32"など文字列への変換辞書
    {
        { CHAR, "char" }, { UCHAR, "uchar" },
        { SHORT, "short" }, { USHORT, "ushort" },
        { INT, "int" }, { UINT, "uint" },
        { FLOAT, "float" }, { DOUBLE, "double" },
    };

    // std::type_index(typeid(float)) などのタイプから そのステップバイト数=4バイト に変換
    static size_t dataTypeToStepSize(const std::type_index& type)
    {
        auto it = dataTypeByteSize.find(type);
        assert(it != dataTypeByteSize.end() && "指定された type は plyのヘッダで未定義です");
        return it->second;
    }

    // "int" や "float32"などを std::type_index(typeid(float)) など std::type_index型に変換
    static std::type_index parseDataType(const std::string& type_name)
    {
        const auto& it = strToDataType.find(type_name);
        if (it != strToDataType.end()) return it->second;
        else throw std::exception(std::string("よくわからん定義してないタイプ名が来た:" + type_name).c_str());
    }

    template<typename Key, typename Value>
    struct IndexList
    {
    private:
        typedef typename std::pair<Key, std::shared_ptr<Value>> KeyValue;
        std::vector<KeyValue> data; // data配列にはキー"x" "y"などとバリュー(PropertyやElementなど)の"x"の型の情報(Property)が入る
    public:
        std::shared_ptr<Value> operator[] (const Key& key)
        {
            auto it = std::find_if(begin(), end(), [&key](const KeyValue& keyVal) { return keyVal.first == key; });
            assert(it != end() && "未登録のキーに[]でアクセスしようとしました");
            return it->second;
        }

        // ["x"] や ["vertex"]のようにプロパティやエレメントに[]オペレータでアクセスできる
        const std::shared_ptr<const Value> operator[] (const Key& key) const
        {    // keyに "x" が入ってきたらdata配列のbegin()からend()までkeyが "x"になるKeyValueを探す
            auto it = std::find_if(begin(), end(), [&key](const KeyValue& keyVal) { return keyVal.first == key; });
            assert(it != end() && "未登録のキーに[]でアクセスしようとしました");
            return it->second; // "x" のsecondがfloat型ならばfloat型のデータへの共有ポインタのアクセスを返す
        }

        bool has_key(const Key& key) // xやredなどキーが.plyのヘッダで例.property float x などのように宣言されているか
        {
            auto it = std::find_if(begin(), end(), [&key](const KeyValue& keyVal) { return keyVal.first == key; });
            if (it != end()) return true;
            else             return false;
        }

        void emplace_back(const Key& key, const std::shared_ptr<Value>& value)
        {
            data.emplace_back(key, value);
        }

        void clear() { data.clear(); }

        typedef typename std::vector<std::pair<Key, std::shared_ptr<Value>>>::iterator iterator;
        typedef typename std::vector<std::pair<Key, std::shared_ptr<Value>>>::const_iterator const_iterator;

        // 範囲for文のためのイテレータの先頭beginと終端endを定義

        iterator begin() { return data.begin(); };
        const_iterator begin() const { return data.begin(); };
        iterator end() { return data.end(); };
        const_iterator end() const { return data.end(); };
    };

    class Property // プロパティの例. property float x なら "x" は std::vector<unsigned char> dataにfloat型4バイト枠data[][][][]にまたがって保管
    {
    public:
        // ※unsigned char型の1バイト配列として定義してあるが実際に格納されるデータは2バイトなら箱2つ[ ][ ]で[ ]をまたいで格納される
        std::vector<unsigned char> data; // 読みだしたプロパティのデータの書き込み先配列
        const std::type_index type; // プロパティのタイプ 例. property float x なら floatのstd::type_index
        const unsigned int stepSize; // プロパティのデータのステップサイズ数 例. floatやint = 4バイト stepSize = 4
        const bool isList = false; // 例. property list uchar uint vertex_indices なら isList = true
        const size_t elementSize = 0; // エレメントのサイズ
        //std::vector<unsigned char> listCounts;

        Property(const std::type_index type, const size_t size, const bool isList = false, const size_t elementSize = 0)
            : type{ type }, isList{ isList }, stepSize{ (unsigned int)Ply::dataTypeToStepSize(type) }, elementSize{ elementSize }
        {
            this->data.resize(size * this->stepSize); // データの書き込み先配列のメモリを事前確保しておく
        }

        template<typename T> // TがPropertyのタイプと一致するか判定
        bool isType() const { return (type == std::type_index(typeid(T))); }

        template<typename T> // T型としてdata配列の先頭への T型のポインタを得る(const不変縛り)
        const T* ptr() const { assert(isType<T>()); return reinterpret_cast<const T*>(&data[0]); }

        template<typename T> // T型としてdata配列の先頭への T型のポインタを得る
        T* ptr() { assert(isType<T>()); return reinterpret_cast<T*>(data.data()); }

        // data配列のサイズをPropertyのステップサイズで割ることで配列の要素数を得られる
        const size_t size() const { assert(data.size() % stepSize == 0); return data.size() / stepSize; }

        template<typename T>
        const T& at(const size_t i) const
        {
            assert(isType<T>());
            assert((i + 1) * stepSize <= data.size()); //↓ [ ]をまたいで格納しているぶん i * stepSizeで
            return  *reinterpret_cast<const T*>(&data[i * stepSize]); // 2バイトなら箱2つ[ ][ ] i×2ぶんずらしたアドレスを返す
        }

        template<typename T>
        T& at(const size_t i)
        {
            assert(isType<T>());
            assert((i + 1) * stepSize <= data.size()); //↓ [ ]をまたいで格納しているぶん i * stepSizeで
            return  *reinterpret_cast<T*>(&data[i * stepSize]); // 2バイトなら箱2つ[ ][ ] i×2ぶんずらしたアドレスを返す
        }
    };

    class Elements // .plyの各elementを表す 例. element face 12 = ポリゴンが size = 12面ぶんありdata["face"]でアクセスできる
    {
        size_t size_;
    public:
        Elements(const size_t size) : size_(size) {}
        IndexList<std::string, Property> properties; // elementにぶらさがっている property float x などのリスト
        size_t size() const { return size_; }
    };


    typedef IndexList<std::string, Elements> Data; // ["x"] や["vertex"]のようにプロパティやエレメントに[]オペレータでアクセスして使う

    // Windowsで作成されたファイルは改行コード \n に \r がついたcarrige return形式になっているので除去してstd::getlineできる関数
    inline void getline_carriage_return(std::ifstream& inputfile, std::string& line)
    {
        std::getline(inputfile, line); // ファイルから1行をstd::getlineで取得
        // Windowsで作成されたファイルは改行コード \n に \r がついたcarrige return形式になっているので除去
        if (!line.empty() && line.back() == '\r')
            line.pop_back();
    }

    // " "スペース区切りでresult配列に出力する
    inline void split_space(const std::string& input, std::vector<std::string>& result)
    {
        result.clear();
        std::stringstream ss(input);
        while (true)
        {
            std::string elem;
            ss >> elem; // ssで>>するたびに" "スペースで区切られてelemに文字列が読みだされていく
            if (ss.fail()) break;
            result.push_back(elem);
        }

    }

    inline size_t str_to_size_t(const std::string& input) // 文字列をstd::stringstreamの >> でsize_t型の数値に変換
    {
        std::stringstream ss(input);
        size_t val; // size_t型はunsigned long long型64bit
        ss >> val; // 文字列を >> で 数値型に変換
        if (ss.fail()) throw std::exception("std::stringstream で 数値型(size_t型)に変換できない文字列がありました");
        return val;
    }

    // テキストASCII形式のデータを読みだしてpWriteAddressへ書き込んで保管する
    inline void readASCIIValue(std::ifstream& fin, unsigned char* const pWriteAddress, const std::type_index& type)
    {
        if (type == Ply::CHAR)
        {
            int temp;
            fin >> temp; // 一旦 int型に変換してから8ビットにキャストして書き込む必要がある
            *reinterpret_cast<int8_t*>(pWriteAddress) = static_cast<int8_t>(temp);
        }
        else if (type == Ply::UCHAR)
        {
            int temp;
            fin >> temp; // 一旦 int型に変換してから8ビットにキャストして書き込む必要がある
            *reinterpret_cast<uint8_t*>(pWriteAddress) = static_cast<uint8_t>(temp);
        }
        else if (type == Ply::SHORT)  fin >> *reinterpret_cast<int16_t*>(pWriteAddress);
        else if (type == Ply::USHORT) fin >> *reinterpret_cast<uint16_t*>(pWriteAddress);
        else if (type == Ply::INT)    fin >> *reinterpret_cast<int32_t*>(pWriteAddress);
        else if (type == Ply::UINT)   fin >> *reinterpret_cast<uint32_t*>(pWriteAddress);
        else if (type == Ply::FLOAT)  fin >> *reinterpret_cast<float*>(pWriteAddress);
        else if (type == Ply::DOUBLE) fin >> *reinterpret_cast<double*>(pWriteAddress);
        else throw std::exception("なんか知らない未知のtype型が来たのでおかしい");
    }

    // .plyデータのヘッダではなく本体データをヘッダのプロパティのステップサイズに沿って読みだす
    template <Ply::FileFormat format>
    void readDataContent(std::ifstream& fin, Ply::Data& loadingData)//, std::function<void()> onVetexFunc, std::function<void()> onIndexFunc)
    {
        //Property型の内部のstd::vector<unsigned char> data;の配列の先頭アドレスへの辞書を作ってデータ書き込み先へのリンクを保持しておく
        std::unordered_map<Property*, unsigned char*> writingPlace; // データ書込先の配列先頭アドレスを表す辞書
        for (auto& elementsKeyVal : loadingData) // elementsのキー("vertex"など)とバリュー(elements自体)のペアをループ
        {
            for (auto& propertyKeyVal : elementsKeyVal.second->properties) // propertyのキー("x"や"red"など)とバリュー(property自体)のペアをループ
            {
                auto& pProperty = propertyKeyVal.second;
                writingPlace[pProperty.get()] = pProperty->data.data(); // std::vector型のdata()を呼び出すと配列の先頭アドレスが得られる
            }
        }

        //uint8_t max_count = 3; //property list uchar uint vertex_indices のインデックスの数は3固定だが4とかに対応するなら工夫が必要

        for (auto& elementsKeyVal : loadingData) // 各エレメントをループ
        {
            auto& pElements = elementsKeyVal.second;
            const size_t elementsSize = pElements->size();
            for (size_t i = 0; i < elementsSize; ++i) // 各エレメントのサイズぶんだけループ
            {
                for (auto& propertyKeyVal : pElements->properties) // 各エレメントの全プロパティをループ
                {
                    auto& pProperty = propertyKeyVal.second;

                    if (!pProperty->isList) // リストじゃないとき 例. property float x など
                    {
                        auto& pWriteAddress = writingPlace[pProperty.get()]; // 読み込んだデータの書き込み先アドレスを得る
                        assert(pWriteAddress >= pProperty->data.data() && "書き込み先アドレスが事前確保したプロパティの内部配列の書き込み可能位置ではない");
                        //assert(ptData + prop->stepSize <= prop->data.data() + prop->data.size());

                        if (format == FileFormat::ASCII)
                            readASCIIValue(fin, pWriteAddress, pProperty->type); // テキストASCII形式をstd::ifstreamの >> 演算子で変換して読取
                        else
                            fin.read(reinterpret_cast<char*>(pWriteAddress), pProperty->stepSize); // バイナリ形式をstd::ifstreamのreadで読取

                        pWriteAddress += pProperty->stepSize; // 読取先アドレスをプロパティのステップサイズぶん先に進める
                    }
                    else // リストのとき 例. property list uchar uint vertex_indices など
                    {
                        // Read count
                        uint8_t indexCount;

                        if (format == FileFormat::ASCII)
                        {
                            int temp;
                            fin >> temp; // 一旦 int型に変換してから8ビットunsigned charにキャストして読み込む必要がある
                            indexCount = static_cast<unsigned char>(temp);
                        }
                        else fin.read(reinterpret_cast<char*>(&indexCount), sizeof(indexCount));

                        if (fin.fail()) throw std::exception("property listは 3(三角ポリゴンインデックス) か 6(テクスチャ3点×uv 2値 = 6)のみサポート");

                        //property list uchar uint vertex_indices のインデックスの数は3固定だが4とかに対応するなら工夫が必要
                        /*if (count > max_count && count != 3 && count != 6)
                        {
                            max_count = count;
                            size_t newSize = count * prop->elementSize * prop->stepSize;
                            if (prop->data.size() < newSize)
                                prop->data.resize(newSize);
                        }*/
                        //prop->listCounts.push_back(count); // save each counts

                        // Read data
                        auto& pWriteAddress = writingPlace[pProperty.get()]; // 読み込んだデータの書き込み先アドレスを得る
                        const size_t chunkSize = indexCount * pProperty->stepSize; // 読み出すチャンクの塊のサイズ
                        assert(pWriteAddress >= pProperty->data.data() && "書き込み先アドレスが事前確保したプロパティの内部配列の書き込み可能位置ではない");
                        //assert(ptData + chunkSize <= prop->data.data() + prop->data.size());

                        if (format == Ply::FileFormat::ASCII)
                        {
                            for (size_t n = 0; n < indexCount; ++n) // インデックスの数だけループ
                            {
                                readASCIIValue(fin, pWriteAddress, pProperty->type);
                                pWriteAddress += pProperty->stepSize; // 読取先アドレスをステップサイズぶん先に進める
                            }
                        }
                        else
                        {    // バイナリ形式ならチャンクの塊サイズ = chunkSize ぶんまとめて高速読み出し
                            fin.read(reinterpret_cast<char*>(pWriteAddress), chunkSize);
                            pWriteAddress += chunkSize; // 読取先アドレスをチャンクのサイズぶん先に進める
                        }
                    }
                }
            }
        }
    }

    // PLY形式のデータをロードする
    inline void load(const std::string& filepath, Ply::Data& loadingData)//, std::function<void()> onVetexFunc, std::function<void()> onIndexFunc)
    {
        std::string format; // .plyファイルのヘッダの format
        std::string version; // .plyファイルのヘッダの version

        std::ifstream fin(filepath, std::ios::binary); // バイナリ形式で読み込み

        if (!fin.is_open())
            throw std::exception((std::string("指定された ply ファイルが開けませんでした") + filepath).c_str());

        std::string line; // 読み出し1行ライン
        Ply::getline_carriage_return(fin, line);

        std::shared_ptr<Ply::Elements> currentElement = nullptr;

        if (line != "ply") throw std::exception(".plyファイルヘッダの先頭に ""ply"" がなかったのでフォーマットがおかしいです");

        while (line != "end_header") // .plyファイルのヘッダーの終わりを表す end_header になるまでwhileループで読取
        {
            Ply::getline_carriage_return(fin, line);
            if (fin.fail()) throw std::exception(".plyファイルヘッダーの読取に失敗");

            std::vector<std::string> lineContent;
            Ply::split_space(line, lineContent);

            if (lineContent.size() == 3 && lineContent[0] == "format")
            {
                format = lineContent[1]; // スペース区切りの2つめ
                version = lineContent[2]; // スペース区切りの3つめ
            }
            if (lineContent.size() == 3 && lineContent[0] == "element")
            {
                const std::string& name = lineContent[1]; // elementの名前 例.element vertex 14 なら"vertex"
                const size_t elementSize = Ply::str_to_size_t(lineContent[2]); // elementの数 ↑14 つまり3Dのvertex(点)が14個

                currentElement.reset(new Ply::Elements(elementSize)); // 今から読みだすエレメントのステップするサイズが確定

                loadingData.emplace_back(name, currentElement); // elementをデータ構造に登録
            }
            else if (lineContent.size() == 3 && lineContent[0] == "property") //例. property float x などの行を受け取る
            {
                if (!currentElement)
                    throw std::exception("elementが読まれてないのにpropertyが先に来ちゃったら何バイト飛ばしのステップで読みだして良いかわか らない");

                const std::type_index dataType = Ply::parseDataType(lineContent[1]); // "int" "char"や"float32"などのデータタイプがlineContent[1]に入ってる
                const std::string& name = lineContent[2]; // x y z や red green blue など↑のintやfloatが表す数値の名前(使われ方)が入っている

                std::shared_ptr<Ply::Property> newProperty(new Ply::Property(dataType, currentElement->size()));
                currentElement->properties.emplace_back(name, newProperty); // currentElementに property float x などのプロパティ情報(name="x" float)を登録
            }
            else if (lineContent.size() == 5 && lineContent[0] == "property" && lineContent[1] == "list") //例.property list uchar uint vertex_indices などの行
            {
                if (!currentElement)
                    throw std::exception("elementが読まれてないのにpropertyが先に来ちゃったら何バイト飛ばしのステップで読みだして良いかわか らない");

                const std::type_index indexCountType = Ply::parseDataType(lineContent[2]); // "uchar"などのインデックスの数を表す型がlineContent[2]に入ってる
                const std::type_index dataType = Ply::parseDataType(lineContent[3]); // "int" "char"や"float32"などのデータタイプがlineContent[3]に入ってる
                const std::string& name = lineContent[4]; // vertex_indices や texcoord などのインデックス数値の名前(使われ方)が入っている

                if (indexCountType != Ply::UCHAR)
                    throw std::exception("property listでは uchar 型しか インデックスの数を表す型にはつかえない 例. property list uchar uint vertex_indices");

                // ポリゴンindexの数は 3 固定で決め打ちしている↓のでblenderなどで「メッシュの三角面化」にチェックを入れてエクスポートする必要がある
                size_t lineDataNum = (name == "texcoord") ? 6 : 3; // テクスチャUV(st)座標 は 6つ 点1(u,v),点2(u,v),点3(u,v) = 6つの u v
                std::shared_ptr<Ply::Property> newProperty(new Ply::Property(dataType, lineDataNum * currentElement->size(), true, currentElement->size()));
                currentElement->properties.emplace_back(name, newProperty); // currentElementに 登 録             isList = true↑
            }
        }

        if (fin.fail()) throw std::exception(".plyファイルのヘッダを読み取ったあとの本データを読取る前にファイルエラーが出た");


        // ヘッダのプロパティ形式を元に本データを読み取る
        if (format == "ascii") // テキストのASCII形式の場合
        {
            // .plyデータの本体データをヘッダのプロパティのステップサイスに沿って読みだす
            Ply::readDataContent<Ply::FileFormat::ASCII>(fin, loadingData);//, onVetexFunc, onIndexFunc);
            if (fin.fail()) throw std::exception(".plyのアスキー形式のデータ読み出しに失敗しました");
        }
        else // 本体データがバイナリ形式の場合
        {
            const bool isBigEndian = Ply::isBigEndian();
            if (format != "binary_little_endian" && format != "binary_big_endian")
                throw std::exception(".plyのヘッダのformat部分が binary_little_endian でも binary_big_endian でもない");

            if ((isBigEndian && format != "binary_big_endian") || (!isBigEndian && format != "binary_little_endian"))
                throw std::exception(".plyのformat と お使いのPC環境のビックエンディアンorリトルエンディアン環境が合致していない");

            // .plyデータの本体データをヘッダのプロパティのステップサイスに沿って読みだす
            Ply::readDataContent<Ply::FileFormat::BINARY>(fin, loadingData);//, onVetexFunc, onIndexFunc);

            if (fin.fail()) throw std::exception(".plyのバイナリ形式のデータ読み出しに失敗しました");

            char endOfFileCheck; // ファイルの終わりにchar1バイト余分に読みだしてみてちゃんとファイルの終わりまでロードできたかチェック
            fin.read(&endOfFileCheck, 1);
            if (!fin.eof()) throw std::exception("ファイルの終わりに1バイト余分に読みだしたけどファイルに続きがあるのでちゃんと完全に読みだせてない");
        }
    }
}

#endif


main.cppを一旦まっさらにリセットしてから下記のプログラムで、.ply形式のファイルをロードしてポリゴンで サイコロ型の3Dモデルが表示できるかテストします。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include <vector>
#include "Screen.h"
#include "Ply.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    DxLib::SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズWidth×Heightのカラービット数32ビットで起動
    DxLib::SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    DxLib::ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    DxLib::SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    DxLib::SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    // DXライブラリの初期化
    if (DxLib::DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    DxLib::SetDrawScreen(DX_SCREEN_BACK);

    // ゲームのwhileループを開始する前の初期化処理
    int texImage = -1;
    texImage = LoadGraph("Image/boss1.png");
    assert(texImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる
    float imgWidth, imgHeight;
    DxLib::GetGraphSizeF(texImage, &imgWidth, &imgHeight); // テクスチャ画像のサイズを得る

    Ply::Data Data; // .plyのデータのクラス
    Ply::load("Image/dicetest.ply", Data); // .plyデータをロード

    size_t numIndex = 0; // インデックスの数
    std::vector<uint32_t> Index; // 3Dモデルのポリゴンのインデックス配列
    std::vector<VERTEX3D> Vertex; // 3Dモデルのポリゴンの頂点配列

    auto vertex = Data["vertex"]; // 頂点へのアクセス
    assert(vertex->size() != 0 && "Plyデータの頂点サイズが0です。ファイルのパスは大丈夫ですか?");
    Vertex.resize(vertex->size()); // 配列の数の事前確保

    // [.plyの頂点のプロパティ情報] https://jp.mathworks.com/help/vision/ug/the-ply-format.html
    const float* p_x = vertex->properties["x"]->ptr<float>();
    const float* p_y = vertex->properties["y"]->ptr<float>();
    const float* p_z = vertex->properties["z"]->ptr<float>();
    const float* p_nx = (vertex->properties.has_key("nx")) ? vertex->properties["nx"]->ptr<float>() : nullptr;
    const float* p_ny = (vertex->properties.has_key("ny")) ? vertex->properties["ny"]->ptr<float>() : nullptr;
    const float* p_nz = (vertex->properties.has_key("nz")) ? vertex->properties["nz"]->ptr<float>() : nullptr;
    bool has_norm = !(p_nx == nullptr || p_ny == nullptr || p_nz == nullptr);
    const float* p_s = (vertex->properties.has_key("s")) ? vertex->properties["s"]->ptr<float>() : nullptr; // Blender書き出しでは(u,v)ではなく(s,t)として書き出されている
    const float* p_t = (vertex->properties.has_key("t")) ? vertex->properties["t"]->ptr<float>() : nullptr;

    const unsigned char* p_R = (vertex->properties.has_key("red")) ? vertex->properties["red"]->ptr<unsigned char>()
        : (vertex->properties.has_key("r")) ? vertex->properties["r"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_G = (vertex->properties.has_key("green")) ? vertex->properties["green"]->ptr<unsigned char>()
        : (vertex->properties.has_key("g")) ? vertex->properties["g"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_B = (vertex->properties.has_key("blue")) ? vertex->properties["blue"]->ptr<unsigned char>()
        : (vertex->properties.has_key("b")) ? vertex->properties["b"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_A = (vertex->properties.has_key("alpha")) ? vertex->properties["alpha"]->ptr<unsigned char>()
        : (vertex->properties.has_key("a")) ? vertex->properties["a"]->ptr<unsigned char>() : nullptr;
    bool has_color = !(p_R == nullptr || p_G == nullptr || p_B == nullptr || p_A == nullptr);

    for (size_t i = 0, iEnd = Vertex.size(); i < iEnd; i += 1)
    {
        // 頂点のデータをセット 各ツールの系一覧 https://zenn.dev/it_ks/articles/cbe27860548ea1
        //Vertex[i].pos = VGet(p_x[i], p_y[i], p_z[i]); // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        //Vertex[i].pos = VGet(p_x[i], -p_y[i], p_z[i]); // 右手Y↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        //Vertex[i].norm = (has_norm) ? VNorm(VGet(p_nx[i], -p_ny[i], p_nz[i])) : VGet(0.0f, 0.0f, 0.0f);

        // 右手Z↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        Vertex[i].pos = VGet(-(p_y[i]), p_z[i], p_x[i]);
        Vertex[i].norm = (has_norm) ? VNorm(VGet(-p_ny[i], p_nz[i], p_nx[i])) : VGet(0.0f, 0.0f, 0.0f);
        Vertex[i].dif = (has_color) ? GetColorU8(p_R[i], p_G[i], p_B[i], p_A[i]) : GetColorU8(255, 255, 255, 255);
        Vertex[i].spc = GetColorU8(255, 255, 255, 255);
        Vertex[i].u = (p_s == nullptr) ? 0.0f : p_s[i];
        Vertex[i].v = (p_t == nullptr) ? 0.0f : 1 - p_t[i];
        Vertex[i].su = -1.0f;
        Vertex[i].sv = 0.0f;
    }

    const auto& face_index = Data["face"]->properties["vertex_indices"];
    Index.resize(face_index->size() * 3);

    for (size_t i = 0, iEnd = face_index->size(), nFace = 0; i < iEnd; i += 3)
    {
        //for (size_t j = 0; j < 3; ++j) // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        for (size_t j = 2; j + 1 > 0; --j) // 左手系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        {
            unsigned int index = face_index->at<unsigned int>(i + j);
            const size_t uvIndex = 2 * (i + j); // uとv 2つ * ( i + j )

            //Index[i + j] = index;   // 右手系(裏面は時計回り)
            Index[i + 2 - j] = index; // 左手系(裏面は反時計回り)

            // 三角形の2辺のベクトルの外積から法線を計算(法線がないとライトの光の反射方向をシェーディングできないから)
            if (j == 0 && !has_norm && Vertex[index].norm.x == 0.0f && Vertex[index].norm.y == 0.0f && Vertex[index].norm.z == 0.0f)
            {
                const unsigned int index0 = Index[i];
                const unsigned int index1 = Index[i + 1];
                const unsigned int index2 = Index[i + 2];

                // 外積 a×b cross_a_b を求める https://examist.jp/mathematics/space-vector/vector-gaiseki/
                const VECTOR a = VSub(Vertex[index1].pos, Vertex[index0].pos);
                const VECTOR b = VSub(Vertex[index2].pos, Vertex[index0].pos);
                // 左手系(表面は時計回り) 右手系と左手系では裏面の時計回りと反時計回りが変わる
                const VECTOR cross1_2 = VCross(a, b);
                // 右手系(表面は反時計回り)
                //const VECTOR cross2_1 = VCross(b, a);

                // 単位ベクトル(ノルム)に直す [単位ベクトル]は√(x*x + y*y + z*z) = 1となる
                const VECTOR norm1_2 = VNorm(cross1_2);
                //const VECTOR norm2_1 = VNorm(cross2_1);
                Vertex[index0].norm = norm1_2; Vertex[index1].norm = norm1_2; Vertex[index2].norm = norm1_2;
                //Vertex[index0].norm = norm2_1; Vertex[index1].norm = norm2_1; Vertex[index2].norm = norm2_1;
            }

            ++numIndex;
        }
    }


    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 200.0f; keyControlXYZ.y = 200.0f; keyControlXYZ.z = 0;
    VECTOR keyControlAngle = VGet(0.0f,0.0f,0.0f); // 回転

    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    DxLib::ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (DxLib::ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        DxLib::ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        VECTOR position = VGet(keyControlXYZ.x, keyControlXYZ.y, keyControlXYZ.z); // 3Dモデルの位置

        float width = imgWidth; float height = imgHeight; // テクスチャ画僧のサイズ
        VECTOR scale = VGet(100.0f, 100.0f, 100.0f); // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)とcキー(C)でx,y,z軸まわりに画像を回転
        if (PadInput & PAD_INPUT_A) keyControlAngle.x += speed;
        if (PadInput & PAD_INPUT_B) keyControlAngle.y += speed;
        if (PadInput & PAD_INPUT_C) keyControlAngle.z += speed;

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数
        const float xRotate = keyControlAngle.x * Deg2Rad;  const float yRotate = keyControlAngle.y * Deg2Rad; const float zRotate = keyControlAngle.z * Deg2Rad;


        MATRIX  matWorldDefault; // デフォルトのワールド行列
        DxLib::GetTransformToWorldMatrix(&matWorldDefault); // デフォルトのワールド行列を記憶しておく
        {
            //[ワールド行列] https://ny-program.hatenablog.com/entry/2019/09/15/114245
            MATRIX mat; // モデルのスケール×回転×移動を行列計算で設定する

            //[スケール]拡大縮小のスケール行列を得る
            mat = MGetScale(scale); // まずは拡大縮小のスケール行列からはじめる
            // [勉強]↓スケール行列は 斜めに x , y , zのスケールの倍率がならんでいる行列
            //mat.m[0][0] = scale.x;  mat.m[0][1] = 0.0f;    mat.m[0][2] = 0.0f;     mat.m[0][3] = 0.0f;
            //mat.m[1][0] = 0.0f;     mat.m[1][1] = scale.y; mat.m[1][2] = 0.0f;     mat.m[1][3] = 0.0f;
            //mat.m[2][0] = 0.0f;     mat.m[2][1] = 0.0f;    mat.m[2][2] = scale.z;  mat.m[2][3] = 0.0f;
            //mat.m[3][0] = 0.0f;     mat.m[3][1] = 0.0f;    mat.m[3][2] = 0.0f;     mat.m[3][3] = 1.0f;

            //[回転]
            MATRIX matRot;
            DxLib::CreateIdentityMatrix(&matRot);
            // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
            matRot = MMult(matRot, MGetRotZ(zRotate));
            matRot = MMult(matRot, MGetRotX(xRotate));
            matRot = MMult(matRot, MGetRotY(yRotate));
            mat = MMult(mat, matRot); //スケール × 回転

            //[移動]
            MATRIX matTrans = MGetTranslate(VGet(position.x, position.y, position.z)); // 位置positionをここで設定
            mat = MMult(mat, matTrans); //スケール×回転×移動

            DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4341

            DxLib::DrawPolygon32bitIndexed3D(Vertex.data(), Vertex.size(), Index.data(), numIndex / 3, texImage, FALSE); // 三角形ポリゴンを描画
        }
        DxLib::SetTransformToWorld(&matWorldDefault); // デフォルトのワールド行列に戻しておく

        DxLib::DrawFormatString(0, 0, GetColor(255, 255, 0), "x:%f y:%f Angle:%f %f %f", position.x, position.y, keyControlAngle.x, keyControlAngle.y, keyControlAngle.z);

        DxLib::ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    DxLib::WaitKey();
    // DXライブラリの後始末
    DxLib::DxLib_End();
    // ソフトの終了
    return 0;
}




Blenderをインストールして3Dのサイコロとテク スチャ画 像を書き出してXYZ軸を見極める

Blenderを下記サイトを参考にインストールしましょう。
https://www.blender.jp/install

Blenderを開いて、上部のメニューバーから[テクスチャペイント]をクリックし[+新規]をクリックしてdiceと名前をつけてテクスチャ画像を生 成します。

テクスチャ画像が生成されたら黒地に白で筆の半径50pxほどのサイコロの目を描いてみましょう。

上部のメニューバーから[シェーディング]をクリックして下画面のノード画面で右クリックして[+追加]→[テクスチャ]→[画像テ クスチャ]のノードを生成

画像テクスチャノードの画像として先ほどペイントしたdiceを選びます

画像テクスチャノードの[カラー]からプリンシプルBSDFノードの[ベースカラー]へと線を引っ張ってつなげるとサイコロのテクスチャ画像がでます

[テクスチャペイント]をクリックして、[画像]から[名前をつけて保存]を選び、テクスチャ画像をDXのプロジェクトのImageフォルダに dice.pngとして保存

メニューバーの[ファイル]→[エクスポート]→[Stanford PLY(.ply)]を選び、DXのプロジェクトのImageフォルダにdice.plyとしてエクスポート保存します。

エクスポートする際の[前方の軸][上向きの軸]はDXのmain.cppで読み取る際に対応関係があるので色んな軸の設定の仕方で実験してみましょう

main.cppのコードで読み込む.plyファイルをBlenderで作成したファイルに変えて、書き出す際のxyz 軸を変えながら試行錯誤してみましょう。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include "Screen.h"
#include "Ply.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズWidth×Heightのカラービット数32ビットで起動
    SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    SetDrawScreen(DX_SCREEN_BACK);

    // ゲームのwhileループを開始する前の初期化処理
    int texImage = -1;
    texImage = LoadGraph("Image/dice.png");
    assert(texImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる
    float imgWidth, imgHeight;
    GetGraphSizeF(texImage, &imgWidth, &imgHeight); // テクスチャ画像のサイズを得る

    Ply::Data Data; // .plyのデータのクラス
    Ply::load("Image/dice.ply", Data); // .plyデータをロード 軸を変えてdice1.plyや dice2.plyなどいろいろ試してみよう

    size_t numIndex = 0; // インデックスの数
    std::vector<uint32_t> Index; // 3Dモデルのポリゴンのインデックス配列
    std::vector<VERTEX3D> Vertex; // 3Dモデルのポリゴンの頂点配列

    auto vertex = Data["vertex"]; // 頂点へのアクセス
    assert(vertex->size() != 0 && "Plyデータの頂点サイズが0です。ファイルのパスは大丈夫ですか?");
    Vertex.resize(vertex->size()); // 配列の数の事前確保

    // [.plyの頂点のプロパティ情報] https://jp.mathworks.com/help/vision/ug/the-ply-format.html
    const float* p_x = vertex->properties["x"]->ptr<float>();
    const float* p_y = vertex->properties["y"]->ptr<float>();
    const float* p_z = vertex->properties["z"]->ptr<float>();
    const float* p_nx = (vertex->properties.has_key("nx")) ? vertex->properties["nx"]->ptr<float>() : nullptr;
    const float* p_ny = (vertex->properties.has_key("ny")) ? vertex->properties["ny"]->ptr<float>() : nullptr;
    const float* p_nz = (vertex->properties.has_key("nz")) ? vertex->properties["nz"]->ptr<float>() : nullptr;
    bool has_norm = !(p_nx == nullptr || p_ny == nullptr || p_nz == nullptr);
    const float* p_s = (vertex->properties.has_key("s")) ? vertex->properties["s"]->ptr<float>() : nullptr; // Blender書き出しでは(u,v)ではなく(s,t)として書き出されている
    const float* p_t = (vertex->properties.has_key("t")) ? vertex->properties["t"]->ptr<float>() : nullptr;

    const unsigned char* p_R = (vertex->properties.has_key("red")) ? vertex->properties["red"]->ptr<unsigned char>()
        : (vertex->properties.has_key("r")) ? vertex->properties["r"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_G = (vertex->properties.has_key("green")) ? vertex->properties["green"]->ptr<unsigned char>()
        : (vertex->properties.has_key("g")) ? vertex->properties["g"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_B = (vertex->properties.has_key("blue")) ? vertex->properties["blue"]->ptr<unsigned char>()
        : (vertex->properties.has_key("b")) ? vertex->properties["b"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_A = (vertex->properties.has_key("alpha")) ? vertex->properties["alpha"]->ptr<unsigned char>()
        : (vertex->properties.has_key("a")) ? vertex->properties["a"]->ptr<unsigned char>() : nullptr;
    bool has_color = !(p_R == nullptr || p_G == nullptr || p_B == nullptr || p_A == nullptr);

    for (size_t i = 0, iEnd = Vertex.size(); i < iEnd; i += 1)
    {
        // 頂点のデータをセット 各ツールの系一覧 https://zenn.dev/it_ks/articles/cbe27860548ea1
        //Vertex[i].pos = VGet(p_x[i], p_y[i], p_z[i]); // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        //Vertex[i].pos = VGet(p_x[i], -p_y[i], p_z[i]); // 右手Y↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        //Vertex[i].norm = (has_norm) ? VNorm(VGet(p_nx[i], -p_ny[i], p_nz[i])) : VGet(0.0f, 0.0f, 0.0f);

        // 右手Z↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        Vertex[i].pos = VGet(-(p_y[i]), p_z[i], p_x[i]);
        Vertex[i].norm = (has_norm) ? VNorm(VGet(-p_ny[i], p_nz[i], p_nx[i])) : VGet(0.0f, 0.0f, 0.0f);
        Vertex[i].dif = (has_color) ? GetColorU8(p_R[i], p_G[i], p_B[i], p_A[i]) : GetColorU8(255, 255, 255, 255);
        Vertex[i].spc = GetColorU8(255, 255, 255, 255);
        Vertex[i].u = (p_s == nullptr) ? 0.0f : p_s[i];
        Vertex[i].v = (p_t == nullptr) ? 0.0f : 1 - p_t[i];
        Vertex[i].su = -1.0f;
        Vertex[i].sv = 0.0f;
    }

    const auto& face_index = Data["face"]->properties["vertex_indices"];
    Index.resize(face_index->size() * 3);

    for (size_t i = 0, iEnd = face_index->size(), nFace = 0; i < iEnd; i += 3)
    {
        //for (size_t j = 0; j < 3; ++j) // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        for (size_t j = 2; j + 1 > 0; --j) // 左手系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        {
            unsigned int index = face_index->at<unsigned int>(i + j);
            const size_t uvIndex = 2 * (i + j); // uとv 2つ * ( i + j )

            //Index[i + j] = index;   // 右手系(裏面は時計回り)
            Index[i + 2 - j] = index; // 左手系(裏面は反時計回り)

            // 三角形の2辺のベクトルの外積から法線を計算(法線がないとライトの光の反射方向をシェーディングできないから)
            if (j == 0 && !has_norm && Vertex[index].norm.x == 0.0f && Vertex[index].norm.y == 0.0f && Vertex[index].norm.z == 0.0f)
            {
                const unsigned int index0 = Index[i];
                const unsigned int index1 = Index[i + 1];
                const unsigned int index2 = Index[i + 2];

                // 外積 a×b cross_a_b を求める https://examist.jp/mathematics/space-vector/vector-gaiseki/
                const VECTOR a = VSub(Vertex[index1].pos, Vertex[index0].pos);
                const VECTOR b = VSub(Vertex[index2].pos, Vertex[index0].pos);
                // 左手系(表面は時計回り) 右手系と左手系では裏面の時計回りと反時計回りが変わる
                const VECTOR cross1_2 = VCross(a, b);
                // 右手系(表面は反時計回り)
                //const VECTOR cross2_1 = VCross(b, a);

                // 単位ベクトル(ノルム)に直す [単位ベクトル]は√(x*x + y*y + z*z) = 1となる
                const VECTOR norm1_2 = VNorm(cross1_2);
                //const VECTOR norm2_1 = VNorm(cross2_1);
                Vertex[index0].norm = norm1_2; Vertex[index1].norm = norm1_2; Vertex[index2].norm = norm1_2;
                //Vertex[index0].norm = norm2_1; Vertex[index1].norm = norm2_1; Vertex[index2].norm = norm2_1;
            }

            ++numIndex;
        }
    }


    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 200.0f; keyControlXYZ.y = 200.0f; keyControlXYZ.z = 0;
    VECTOR keyControlAngle = VGet(0.0f,0.0f,0.0f); // 回転

    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        VECTOR position = VGet(keyControlXYZ.x, keyControlXYZ.y, keyControlXYZ.z); // 3Dモデルの位置

        float width = imgWidth; float height = imgHeight; // テクスチャ画僧のサイズ
        VECTOR scale = VGet(50.0f, 50.0f, 50.0f); // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)とcキー(C)でx,y,z軸まわりに画像を回転
        if (PadInput & PAD_INPUT_A) keyControlAngle.x += speed;
        if (PadInput & PAD_INPUT_B) keyControlAngle.y += speed;
        if (PadInput & PAD_INPUT_C) keyControlAngle.z += speed;

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数
        const float xRotate = keyControlAngle.x * Deg2Rad;  const float yRotate = keyControlAngle.y * Deg2Rad; const float zRotate = keyControlAngle.z * Deg2Rad;


        MATRIX  matWorldDefault; // デフォルトのワールド行列
        DxLib::GetTransformToWorldMatrix(&matWorldDefault); // デフォルトのワールド行列を記憶しておく
        {
            //[ワールド行列] https://ny-program.hatenablog.com/entry/2019/09/15/114245
            MATRIX mat; // モデルのスケール×回転×移動を行列計算で設定する

            //[スケール]拡大縮小のスケール行列を得る
            mat = MGetScale(scale); // まずは拡大縮小のスケール行列からはじめる
            // [勉強]↓スケール行列は 斜めに x , y , zのスケールの倍率がならんでいる行列
            //mat.m[0][0] = scale.x;  mat.m[0][1] = 0.0f;    mat.m[0][2] = 0.0f;     mat.m[0][3] = 0.0f;
            //mat.m[1][0] = 0.0f;     mat.m[1][1] = scale.y; mat.m[1][2] = 0.0f;     mat.m[1][3] = 0.0f;
            //mat.m[2][0] = 0.0f;     mat.m[2][1] = 0.0f;    mat.m[2][2] = scale.z;  mat.m[2][3] = 0.0f;
            //mat.m[3][0] = 0.0f;     mat.m[3][1] = 0.0f;    mat.m[3][2] = 0.0f;     mat.m[3][3] = 1.0f;

            //[回転]
            MATRIX matRot;
            DxLib::CreateIdentityMatrix(&matRot);
            // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
            matRot = MMult(matRot, MGetRotZ(zRotate));
            matRot = MMult(matRot, MGetRotX(xRotate));
            matRot = MMult(matRot, MGetRotY(yRotate));
            mat = MMult(mat, matRot); //スケール × 回転

            //[移動]
            MATRIX matTrans = MGetTranslate(VGet(position.x, position.y, position.z)); // 位置positionをここで設定
            mat = MMult(mat, matTrans); //スケール×回転×移動

            DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4341

            DxLib::DrawPolygon32bitIndexed3D(Vertex.data(), Vertex.size(), Index.data(), numIndex / 3, texImage, FALSE); // 三角形ポリゴンをたくさん描画
        }
        DxLib::SetTransformToWorld(&matWorldDefault); // デフォルトのワールド行列に戻しておく

        DxLib::DrawFormatString(0, 0, GetColor(255, 255, 0), "x:%f y:%f Angle:%f %f %f", position.x, position.y, keyControlAngle.x, keyControlAngle.y, keyControlAngle.z);

        ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    WaitKey();
    // DXライブラリの後始末
    DxLib_End();
    // ソフトの終了
    return 0;
}




別の描画スクリーンを作って、Windowsみたいなサブのウィ ンドウを描く


main.cppのコードにMakeScreen関数を使って別の描画先スクリーンを作って描画先スクリーンをスイッチする方法をテストしてみましょう。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include "Screen.h"
#include "Ply.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズWidth×Heightのカラービット数32ビットで起動
    SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    SetDrawScreen(DX_SCREEN_BACK);

    // ゲームのwhileループを開始する前の初期化処理
    int texImage = -1;
    texImage = LoadGraph("Image/dice.png");
    assert(texImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる
    float imgWidth, imgHeight;
    GetGraphSizeF(texImage, &imgWidth, &imgHeight); // テクスチャ画像のサイズを得る

    Ply::Data Data; // .plyのデータのクラス
    Ply::load("Image/dice.ply", Data); // .plyデータをロード 軸を変えてdice1.plyや dice2.plyなどいろいろ試してみよう

    size_t numIndex = 0; // インデックスの数
    std::vector<uint32_t> Index; // 3Dモデルのポリゴンのインデックス配列
    std::vector<VERTEX3D> Vertex; // 3Dモデルのポリゴンの頂点配列

    auto vertex = Data["vertex"]; // 頂点へのアクセス
    assert(vertex->size() != 0 && "Plyデータの頂点サイズが0です。ファイルのパスは大丈夫ですか?");
    Vertex.resize(vertex->size()); // 配列の数の事前確保

    // [.plyの頂点のプロパティ情報] https://jp.mathworks.com/help/vision/ug/the-ply-format.html
    const float* p_x = vertex->properties["x"]->ptr<float>();
    const float* p_y = vertex->properties["y"]->ptr<float>();
    const float* p_z = vertex->properties["z"]->ptr<float>();
    const float* p_nx = (vertex->properties.has_key("nx")) ? vertex->properties["nx"]->ptr<float>() : nullptr;
    const float* p_ny = (vertex->properties.has_key("ny")) ? vertex->properties["ny"]->ptr<float>() : nullptr;
    const float* p_nz = (vertex->properties.has_key("nz")) ? vertex->properties["nz"]->ptr<float>() : nullptr;
    bool has_norm = !(p_nx == nullptr || p_ny == nullptr || p_nz == nullptr);
    const float* p_s = (vertex->properties.has_key("s")) ? vertex->properties["s"]->ptr<float>() : nullptr; // Blender書き出しでは(u,v)ではなく(s,t)として書き出されている
    const float* p_t = (vertex->properties.has_key("t")) ? vertex->properties["t"]->ptr<float>() : nullptr;

    const unsigned char* p_R = (vertex->properties.has_key("red")) ? vertex->properties["red"]->ptr<unsigned char>()
        : (vertex->properties.has_key("r")) ? vertex->properties["r"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_G = (vertex->properties.has_key("green")) ? vertex->properties["green"]->ptr<unsigned char>()
        : (vertex->properties.has_key("g")) ? vertex->properties["g"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_B = (vertex->properties.has_key("blue")) ? vertex->properties["blue"]->ptr<unsigned char>()
        : (vertex->properties.has_key("b")) ? vertex->properties["b"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_A = (vertex->properties.has_key("alpha")) ? vertex->properties["alpha"]->ptr<unsigned char>()
        : (vertex->properties.has_key("a")) ? vertex->properties["a"]->ptr<unsigned char>() : nullptr;
    bool has_color = !(p_R == nullptr || p_G == nullptr || p_B == nullptr || p_A == nullptr);

    for (size_t i = 0, iEnd = Vertex.size(); i < iEnd; i += 1)
    {
        // 頂点のデータをセット 各ツールの系一覧 https://zenn.dev/it_ks/articles/cbe27860548ea1
        //Vertex[i].pos = VGet(p_x[i], p_y[i], p_z[i]); // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        //Vertex[i].pos = VGet(p_x[i], -p_y[i], p_z[i]); // 右手Y↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        //Vertex[i].norm = (has_norm) ? VNorm(VGet(p_nx[i], -p_ny[i], p_nz[i])) : VGet(0.0f, 0.0f, 0.0f);

        // 右手Z↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        Vertex[i].pos = VGet(-(p_y[i]), p_z[i], p_x[i]);
        Vertex[i].norm = (has_norm) ? VNorm(VGet(-p_ny[i], p_nz[i], p_nx[i])) : VGet(0.0f, 0.0f, 0.0f);
        Vertex[i].dif = (has_color) ? GetColorU8(p_R[i], p_G[i], p_B[i], p_A[i]) : GetColorU8(255, 255, 255, 255);
        Vertex[i].spc = GetColorU8(255, 255, 255, 255);
        Vertex[i].u = (p_s == nullptr) ? 0.0f : p_s[i];
        Vertex[i].v = (p_t == nullptr) ? 0.0f : 1 - p_t[i];
        Vertex[i].su = -1.0f;
        Vertex[i].sv = 0.0f;
    }

    const auto& face_index = Data["face"]->properties["vertex_indices"];
    Index.resize(face_index->size() * 3);

    for (size_t i = 0, iEnd = face_index->size(), nFace = 0; i < iEnd; i += 3)
    {
        //for (size_t j = 0; j < 3; ++j) // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        for (size_t j = 2; j + 1 > 0; --j) // 左手系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        {
            unsigned int index = face_index->at<unsigned int>(i + j);
            const size_t uvIndex = 2 * (i + j); // uとv 2つ * ( i + j )

            //Index[i + j] = index;   // 右手系(裏面は時計回り)
            Index[i + 2 - j] = index; // 左手系(裏面は反時計回り)

            // 三角形の2辺のベクトルの外積から法線を計算(法線がないとライトの光の反射方向をシェーディングできないから)
            if (j == 0 && !has_norm && Vertex[index].norm.x == 0.0f && Vertex[index].norm.y == 0.0f && Vertex[index].norm.z == 0.0f)
            {
                const unsigned int index0 = Index[i];
                const unsigned int index1 = Index[i + 1];
                const unsigned int index2 = Index[i + 2];

                // 外積 a×b cross_a_b を求める https://examist.jp/mathematics/space-vector/vector-gaiseki/
                const VECTOR a = VSub(Vertex[index1].pos, Vertex[index0].pos);
                const VECTOR b = VSub(Vertex[index2].pos, Vertex[index0].pos);
                // 左手系(表面は時計回り) 右手系と左手系では裏面の時計回りと反時計回りが変わる
                const VECTOR cross1_2 = VCross(a, b);
                // 右手系(表面は反時計回り)
                //const VECTOR cross2_1 = VCross(b, a);

                // 単位ベクトル(ノルム)に直す [単位ベクトル]は√(x*x + y*y + z*z) = 1となる
                const VECTOR norm1_2 = VNorm(cross1_2);
                //const VECTOR norm2_1 = VNorm(cross2_1);
                Vertex[index0].norm = norm1_2; Vertex[index1].norm = norm1_2; Vertex[index2].norm = norm1_2;
                //Vertex[index0].norm = norm2_1; Vertex[index1].norm = norm2_1; Vertex[index2].norm = norm2_1;
            }

            ++numIndex;
        }
    }


    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 200.0f; keyControlXYZ.y = 200.0f; keyControlXYZ.z = 0;
    VECTOR keyControlAngle = VGet(0.0f,0.0f,0.0f); // 回転

    float subscreenX = 0, subscreenY = 0; // 別描画スクリーンを描く位置
    int subscreenWidth = 600, subscreenHeight = 100;
    DxLib::SetDrawValidGraphCreateFlag(TRUE); // SetDrawScreen に引数として渡せる( 描画対象として使用できる )グラフィックハンドル作成を TRUE:許可
    // [別の描画スクリーンを用意] https://dxlib.xsrv.jp/function/dxfunc_graph1.html#R3N25
    int subscreen = DxLib::MakeScreen(subscreenWidth, subscreenHeight, FALSE);
    DxLib::SetDrawValidGraphCreateFlag(FALSE); // FLASE:通常のグラフィックハンドルを作成するモードに戻す


    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        int MouseX, MouseY;
        // マウスの位置を取得
        GetMousePoint(&MouseX, &MouseY);
        subscreenX = MouseX; subscreenY = MouseY; // マウスのポインタの位置を起点にサブのスクリーンを描く


        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        VECTOR position = VGet(keyControlXYZ.x, keyControlXYZ.y, keyControlXYZ.z); // 3Dモデルの位置

        float width = imgWidth; float height = imgHeight; // テクスチャ画僧のサイズ
        VECTOR scale = VGet(50.0f, 50.0f, 50.0f); // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)とcキー(C)でx,y,z軸まわりに画像を回転
        if (PadInput & PAD_INPUT_A) keyControlAngle.x += speed;
        if (PadInput & PAD_INPUT_B) keyControlAngle.y += speed;
        if (PadInput & PAD_INPUT_C) keyControlAngle.z += speed;

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数
        const float xRotate = keyControlAngle.x * Deg2Rad;  const float yRotate = keyControlAngle.y * Deg2Rad; const float zRotate = keyControlAngle.z * Deg2Rad;


        MATRIX  matWorldDefault; // デフォルトのワールド行列
        DxLib::GetTransformToWorldMatrix(&matWorldDefault); // デフォルトのワールド行列を記憶しておく
        {
            //[ワールド行列] https://ny-program.hatenablog.com/entry/2019/09/15/114245
            MATRIX mat; // モデルのスケール×回転×移動を行列計算で設定する

            //[スケール]拡大縮小のスケール行列を得る
            mat = MGetScale(scale); // まずは拡大縮小のスケール行列からはじめる
            // [勉強]↓スケール行列は 斜めに x , y , zのスケールの倍率がならんでいる行列
            //mat.m[0][0] = scale.x;  mat.m[0][1] = 0.0f;    mat.m[0][2] = 0.0f;     mat.m[0][3] = 0.0f;
            //mat.m[1][0] = 0.0f;     mat.m[1][1] = scale.y; mat.m[1][2] = 0.0f;     mat.m[1][3] = 0.0f;
            //mat.m[2][0] = 0.0f;     mat.m[2][1] = 0.0f;    mat.m[2][2] = scale.z;  mat.m[2][3] = 0.0f;
            //mat.m[3][0] = 0.0f;     mat.m[3][1] = 0.0f;    mat.m[3][2] = 0.0f;     mat.m[3][3] = 1.0f;

            //[回転]
            MATRIX matRot;
            DxLib::CreateIdentityMatrix(&matRot);
            // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
            matRot = MMult(matRot, MGetRotZ(zRotate));
            matRot = MMult(matRot, MGetRotX(xRotate));
            matRot = MMult(matRot, MGetRotY(yRotate));
            mat = MMult(mat, matRot); //スケール × 回転

            //[移動]
            MATRIX matTrans = MGetTranslate(VGet(position.x, position.y, position.z)); // 位置positionをここで設定
            mat = MMult(mat, matTrans); //スケール×回転×移動

            DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4341

            DxLib::DrawPolygon32bitIndexed3D(Vertex.data(), Vertex.size(), Index.data(), numIndex / 3, texImage, FALSE); // 三角形ポリゴンをたくさん描画
        }
        DxLib::SetTransformToWorld(&matWorldDefault); // デフォルトのワールド行列に戻しておく

        DxLib::DrawFormatString(0, 0, GetColor(255, 255, 0), "x:%f y:%f Angle:%f %f %f", position.x, position.y, keyControlAngle.x, keyControlAngle.y, keyControlAngle.z);

        int tmpResetSetting = DxLib::GetUseSetDrawScreenSettingReset(); // 設定を一旦tmpに退避
        DxLib::SetUseSetDrawScreenSettingReset(FALSE); // SetDrawScreen切り替え時に設定をリセットしない(FALSE)(これをやらないとカメラ視点などがリセットされるから)
        int tmpDrawScreen = DxLib::GetDrawScreen(); // 一旦変更前のスクリーンのハンドルを退避
        DxLib::SetDrawScreen(subscreen);// ゲームを描く先を用意しておいたサブスクリーンに設定
        {   // (0,0)位置を起点に
別 スクリーンへ文字列を描く
            DxLib::DrawString(0, 0, "サブのスクリーンを使ってWindowsみたいな自作ウインドウを描画してみる", GetColor(255, 255, 255));
        }
        DxLib::SetDrawScreen((tmpDrawScreen == -1) ? DX_SCREEN_BACK : tmpDrawScreen); // 描く先をもとのデフォルトのスクリーンに戻しておく
        DxLib::SetUseSetDrawScreenSettingReset((tmpResetSetting == -1) ? TRUE : tmpResetSetting); // 設定を元に戻す

        // 画面に別スクリーンの内容を描く
        DxLib::DrawRectExtendGraph((int)subscreenX, (int)subscreenY, (int)(subscreenX + subscreenWidth), (int)(subscreenY + subscreenHeight), 0, 0, subscreenWidth, subscreenHeight, subscreen, FALSE);
        // 別スクリーンの枠を描く
        DxLib::DrawBox( (int)((int)subscreenX - 1 + 0.5f),(int)((int)subscreenY - 1 + 0.5f),
                        (int)((int)(subscreenX + subscreenWidth + 1) + 0.5f), (int)((int)(subscreenY + subscreenHeight + 1) + 0.5f), GetColor(255,255,255), FALSE);



        ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    WaitKey();
    // DXライブラリの後始末
    DxLib_End();
    // ソフトの終了
    return 0;
}

このサブのスクリーンの機能をこてこてに魔改造するとウィンドウズ のようなドラッグできるウィンドウをつくることもできます。

Input.hを新規作成してドラッグや複数のスクリーンがあるときの一番上のレイヤーを保管する変数やマウスのドラッグを取れるようにしましょう。

#ifndef INPUT_H_
#define INPUT_H_

#include <climits> //int型の最大値2147483647につかう
#include <unordered_map> //【高速辞書配列】
#include <string> // キーボード押下の文字列出力につかう

#include "DxLib.h"

// パッド番号
enum class Pad
{
    Keyboard = -3, // キーボード入力を得る
    All = -2, // すべてのうちどれか1つでも押されたとき(マウス除く)
    None = -1, // パッド割当て無(やられたり待機中のプレイヤに)
    Key = 0,
    One,
    Two,
    Three,
    Four,
    Mouse, // マウスのボタン判定に
    NUM,  // コントローラの数=6(マウス込み)
};

enum class Mouse
{
    All = -2, // すべてのうちどれか1つでも押されたとき
    None = -1,
    DownL, //[左クリック押した瞬間=1]
    DragL, //[左ドラッグ中=1]
    UpL, //[左クリック離した瞬間=1]
    DownR, //[右クリック押した瞬間=1]
    DragR, //[右ドラッグ中=1]
    UpR, //[右クリック離した瞬間=1]
    X, // 現在のマウス位置
    ReleaseX, // 離した状態のマウス位置(ドラッグ中は開始前位置)
    DownLX, //[左] 押した位置
    DragLX, //[左] ドラッグ中の位置
    UpLX, //[左] 離した位置
    DownRX,//[右] 押した位置
    DragRX, //[右] ドラッグ中の位置
    UpRX, //[右] 離した位置
    Y, // 現在のマウス位置
    ReleaseY, // 離した状態のマウス位置(ドラッグ中は開始前位置)
    DownLY, //[左] 押した位置
    DragLY, //[左] ドラッグ中の位置
    UpLY, //[左] 離した位置
    DownRY,//[右] 押した位置
    DragRY, //[右] ドラッグ中の位置
    UpRY, //[右] 離した位置
    NUM
};

// 入力クラス
class Input
{
public:
    // ある動作として反応するボタン一覧(ビット論理和「|または」)
    enum Button
    {   //[反応ボタン一覧定義] https://dixq.net/g/04_05.html
        // 参加ボタン 1=Z 2=X 3=C 4=A 5=S 6=D 7=Q 8=W 9=ESC 10=SPACE
        Join = PAD_INPUT_1 | PAD_INPUT_2 | PAD_INPUT_3 | PAD_INPUT_4
        | PAD_INPUT_5 | PAD_INPUT_6 | PAD_INPUT_7 | PAD_INPUT_8
        | PAD_INPUT_9 | PAD_INPUT_10, //1Z~10SPACEキー全て反応
    };

    const int MaxPadNum = (int)Pad::Four + 1; // 最大パッド数(Keyぶん+1)

    static int prevStates[(int)Pad::NUM]; // 1フレーム前の状態
    static int currentStates[(int)Pad::NUM]; // 現在の状態
    static std::unordered_map<Pad, bool> isJoin; // 参加中のパッド番号辞書
    // 参加中のパッドかどうか?
    static bool IsJoin(Pad pad) { return isJoin.count(pad) && Input::isJoin[pad]; }
    static std::unordered_map<int, int> padDic; // パッド番号からDXの定義への辞書

    static int prevMouse; // 1フレーム前のマウス状態
    static int currentMouse; // 現在のマウス状態
    static int MouseX, MouseY; // 現在のマウス位置
    static int TopLayerMouseX, TopLayerMouseY; // Subsreenの一番上のレイヤのマウス位置XY(Subscreenクラスで更新)
    static int ClickX, ClickY, MButton, LogType;
    static float MouseWheel; // マウスのホイールの回転
    static std::unordered_map<Mouse, int> Click;//<Mouse⇒int> マウスのクリックXY位置辞書

    //[キーボード入力]https://dxlib.xsrv.jp/function/dxfunc_input.html#R5N28
    static char prevKey[256]; // 1フレーム前のキーボード状態
    static char currentKey[256]; // 現在のキーボード状態
    static std::string KeyString[256];//<キーコード⇒キー文字列>の辞書
    static std::string CapsString[256];//<Caps状態キーコード⇒キー文字列>の辞書
    static bool isCapsLocked;// CapsLock状態か?
    static bool isShifted;// Shiftが押された状態か?

    //★【DXコントローラバグ検知】初回プッシュが被ればDXバグで同一コントローラ
    static int firstPushTiming[(int)Pad::NUM - 1]; // マウスのぶんの配列はいらないから-1
    static int timing;// = 0;
    static bool isBugCheckMode;// = false; //バグチェックを開始するか

private:
    // 辞書配列の初期化
    static void InitPadDictionary()
    {   // 辞書配列で対応関係を結び付けておく
        padDic[(int)Pad::Key] = DX_INPUT_KEY_PAD1; // DX_INPUT_KEY
        padDic[(int)Pad::One] = DX_INPUT_PAD1;
        padDic[(int)Pad::Two] = DX_INPUT_PAD2;
        padDic[(int)Pad::Three] = DX_INPUT_PAD3;
        padDic[(int)Pad::Four] = DX_INPUT_PAD4;
    }

public:
    // 初期化。最初に1回だけ呼んでください。
    static void Init()
    {
        InitPadDictionary(); // 辞書配列の初期化
        InitKeyString(); // キーボード辞書配列の初期化

        // キー状態をゼロリセット
        for (int i = 0; i < (int)Pad::NUM; i++)
            prevStates[i] = currentStates[i] = 0;

        InitMouse(); // マウスの状態初期化
        InitKeyboard(); // キーボードの状態初期化

        timing = 0, isBugCheckMode = false; //バグチェックを開始するか
    }
    // マウスの状態初期化
    static void InitMouse()
    {
        MouseX = -1, MouseY = -1; // 現在のマウス位置を-1で初期状態では画面外として初期化
        ClickX = -1, ClickY = -1, MButton = 0, LogType = 0;

        Click[Mouse::ReleaseX] = -1; Click[Mouse::ReleaseY] = -1;
        Click[Mouse::UpLX] = -1;     Click[Mouse::UpLY] = -1;
        Click[Mouse::UpRX] = -1;     Click[Mouse::UpRY] = -1;
        Click[Mouse::DownLX] = -1;   Click[Mouse::DownLY] = -1;
        Click[Mouse::DownRX] = -1;   Click[Mouse::DownRY] = -1;
    }
    // キーボードの状態初期化
    static void InitKeyboard()
    {
        // キー状態をゼロリセット
        for (int i = 0; i < 256; i++)
            prevKey[i] = currentKey[i] = 0;
    }

    // 最新の入力状況に更新する処理。
    // 毎フレームの最初に(ゲームの処理より先に)呼んでください。
    static void Update()
    {
        for (int i = 0; i < (int)Pad::Mouse; i++)
        {    // 現在の状態を一つ前の状態として保存してGetJoypad..
            prevStates[i] = currentStates[i];
            currentStates[i] = GetJoypadInputState(padDic[i]);
        }

        UpdateMouse(); // マウス状態を更新
        UpdateKeyboard(); // キーボード状態を更新

        isBugCheckMode = true;
        timing++; //タイミングのカウントを+1
        if (timing == INT_MAX) timing = 0; // long型最大値になったら0に戻す
    }

    // キーボードの状態を更新する
    static void UpdateKeyboard()
    {
        memcpy(prevKey, currentKey, sizeof(prevKey));
        // キーボードの状態配列を更新する
        GetHitKeyStateAll(currentKey); // https://dxlib.xsrv.jp/function/dxfunc_input.html#R5N28

        if (GetButtonUp(Pad::Keyboard, KEY_INPUT_CAPSLOCK))
            isCapsLocked = !isCapsLocked; // CapsLock状態を反転
        // シフトが押された状態かを更新
        isShifted = (GetButton(Pad::Keyboard, KEY_INPUT_LSHIFT) || GetButton(Pad::Keyboard, KEY_INPUT_RSHIFT));
    }

    // マウスの状態を更新する
    static void UpdateMouse()
    {
        Click[Mouse::DownL] = 0; Click[Mouse::DownR] = 0; //Click[Mouse.UpL] = 0; Click[Mouse.UpR] = 0;
        Click[Mouse::All] = 0; // 状態辞書の0リセット

        prevMouse = currentMouse; // 前フレームの最新のマウス情報を1つ前の情報とする
        prevStates[(int)Pad::Mouse] = currentMouse;

        MouseWheel = GetMouseWheelRotVolF(); // マウスのホイール回転量を更新

        // マウスの位置を取得
        GetMousePoint(&MouseX, &MouseY);
        TopLayerMouseX = MouseX; TopLayerMouseY = MouseY;
        Click[Mouse::X] = MouseX; Click[Mouse::Y] = MouseY;
        currentMouse = GetMouseInput(); // マウス状態を得る
        currentStates[(int)Pad::Mouse] = currentMouse; // マウス状態をパッド状態にも併合

        // 押してない離した状態のマウス位置を更新
        if (currentMouse == 0)
        {
            Click[Mouse::ReleaseX] = MouseX; Click[Mouse::ReleaseY] = MouseY;
        }
        else Click[Mouse::All] = 1; // マウスのいずれかのボタンが押されている

        // 左マウスドラッグ中のXY位置を更新
        if (GetButton(Pad::Mouse, MOUSE_INPUT_LEFT) && !GetButtonDown(Pad::Mouse, MOUSE_INPUT_LEFT))
        {
            Click[Mouse::DragLX] = MouseX; Click[Mouse::DragLY] = MouseY; Click[Mouse::DragL] = 1;
        }
        else
            Click[Mouse::DragL] = 0;

        // 右マウスドラッグ中のXY位置を更新
        if (GetButton(Pad::Mouse, MOUSE_INPUT_RIGHT) && !GetButtonDown(Pad::Mouse, MOUSE_INPUT_RIGHT))
        {
            Click[Mouse::DragRX] = MouseX; Click[Mouse::DragRY] = MouseY; Click[Mouse::DragR] = 1;
        }
        else
            Click[Mouse::DragR] = 0;

        //[マウスクリック検知] https://dxlib.xsrv.jp/function/dxfunc_input.html#R5N40
        // マウスのボタンが押されたり離されたりしたかどうかの情報を取得する
        if (GetMouseInputLog2(&MButton, &ClickX, &ClickY, &LogType, TRUE) == 0)
        {   // [左ボタン]が押されたり離されたりしていた
            if ((MButton & MOUSE_INPUT_LEFT) != 0)
            {   // クリックされた瞬間と離された瞬間のマウス位置を記録
                if (LogType == MOUSE_INPUT_LOG_DOWN)
                {
                    Click[Mouse::DownLX] = ClickX; Click[Mouse::DownLY] = ClickY; Click[Mouse::DownL] = 1;
                }
                else if (LogType == MOUSE_INPUT_LOG_UP)
                {
                    Click[Mouse::UpLX] = ClickX; Click[Mouse::UpLY] = ClickY; Click[Mouse::UpL] = 1;
                }
            }// [右ボタン]が押されたり離されたりしていた
            else if ((MButton & MOUSE_INPUT_RIGHT) != 0)
            {   // クリックされた瞬間と離された瞬間のマウス位置を記録
                if (LogType == MOUSE_INPUT_LOG_DOWN)
                {
                    Click[Mouse::DownRX] = ClickX; Click[Mouse::DownRY] = ClickY; Click[Mouse::DownR] = 1;
                }
                else if (LogType == MOUSE_INPUT_LOG_UP)
                {
                    Click[Mouse::UpRX] = ClickX; Click[Mouse::UpRY] = ClickY; Click[Mouse::UpR] = 1;
                }
            }
        }
    }

    // ボタンが押されているか?
    static bool GetButton(int buttonId) { return GetButton(Pad::Key, buttonId); }
    static bool GetButton(Pad pad, int buttonId)
    {    //コントローラバグチェック
        if (ControllerBugCheck(pad, buttonId)) return false;

        if (pad == Pad::None) return false; // Noneなら判別不要
        else if (pad == Pad::All) // All指定の時は全てのパッド
        {    // GetButtonの中でGetButtonを呼ぶ【再起呼出し】テクニック
            for (int i = 0; i < (int)Pad::NUM; i++)
                if (GetButton((Pad)i, buttonId))
                    return true; //押されているPadを発見!
            return false; // 一つも押されていなかった
        }

        if (pad == Pad::Keyboard) // 今ボタンが押されているかどうかを返却
            return (currentKey[buttonId] == 1); //https://dxlib.xsrv.jp/function/dxfunc_input.html#R5N28

        // 今ボタンが押されているかどうかを返却
        return (currentStates[(int)pad] & buttonId) != 0;
    }

    // ボタンが押された瞬間か?
    static bool GetButtonDown(int buttonId) { return GetButtonDown(Pad::Key, buttonId); }
    static bool GetButtonDown(Pad pad, int buttonId)
    {    //コントローラバグチェック
        if (ControllerBugCheck(pad, buttonId)) return false;

        if (pad == Pad::None) return false; // Noneなら判別不要
        else if (pad == Pad::All) // All指定の時は全てのパッド
        {    // 再起呼出しテクニック
            for (int i = 0; i < (int)Pad::NUM; i++)
                if (GetButtonDown((Pad)i, buttonId))
                    return true; //押されているPadを発見!
            return false; // 一つも押されていなかった
        }

        if (pad == Pad::Keyboard) //https://dxlib.xsrv.jp/function/dxfunc_input.html#R5N28
            return (currentKey[buttonId] == 1) && (prevKey[buttonId] != 1);

        // 今は押されていて、かつ1フレーム前は押されていない場合はtrueを返却
        return ((currentStates[(int)pad] & buttonId) & ~(prevStates[(int)pad] & buttonId)) != 0;
    }

    // ボタンが離された瞬間か?
    static bool GetButtonUp(int buttonId) { return GetButtonDown(Pad::Key, buttonId); }
    static bool GetButtonUp(Pad pad, int buttonId)
    {    //コントローラバグチェック
        if (ControllerBugCheck(pad, buttonId)) return false;

        if (pad == Pad::None) return false; // Noneなら判別不要
        else if (pad == Pad::All) // All指定の時は全てのパッド
        {    // 再起呼出しテクニック
            for (int i = 0; i < (int)Pad::NUM; i++)
                if (GetButtonUp((Pad)i, buttonId))
                    return true; //押されているPadを発見!
            return false; // 一つも押されていなかった
        }

        if (pad == Pad::Keyboard) //https://dxlib.xsrv.jp/function/dxfunc_input.html#R5N28
            return (prevKey[buttonId] == 1) && (currentKey[buttonId] != 1);

        // 1フレーム前は押されていて、かつ今は押されている場合はtrueを返却
        return ((prevStates[(int)pad] & buttonId) & ~(currentStates[(int)pad] & buttonId)) != 0;
    }


    //コントローラのボタンの初回プッシュを検知し
    //他コントローラと初回タイミングが完全被りかチェック
    //★初回タイミングが完全被りなら【怪しい】重複コントローラとみなし判定処理スルー
    static bool ControllerBugCheck(Pad pad, int buttonId)
    {
        if (pad == Pad::Mouse) return false; // マウスはバグチェック不要
        else if (pad == Pad::Keyboard) return false; // キーボードはバグチェック不要
        // 今は押されていて、かつ1フレーム前は押されていない場合
        bool buttonDown = ((currentStates[(int)pad] & buttonId) & ~(prevStates[(int)pad] & buttonId)) != 0;
        //if (isBugCheckMode == false) return false;
        //コントローラの初回ボタンプッシュを検知
        if (buttonDown && firstPushTiming[(int)pad] == 0)
        {
            for (int i = 0; i < (int)Pad::NUM - 1; i++)
            {   //全コントローラ初回プッシュ被りがないかチェック完全同時は怪しいので-1
                bool isDown = ((currentStates[i] & buttonId) & ~(prevStates[i] & buttonId)) != 0;
                if ((Pad)i != pad && isDown ////初回プッシュ被り検出
                    && (timing == firstPushTiming[i] || firstPushTiming[i] == 0))
                {   //初回プッシュのタイミング被りは-1フラグを記録
                    firstPushTiming[i] = -1;
                }
            }
            if (firstPushTiming[(int)pad] != -1)
                firstPushTiming[(int)pad] = timing;//初回プッシュを記録
        }

        if (firstPushTiming[(int)pad] == -1) return true; //DXのコントローラ被りバグ!
        else return false; //コントローラ被りOK!
    }

    // キーボードのキーと文字列の対応関係配列の初期化 https://dxlib.xsrv.jp/function/dxfunc_input.html#R5N2
    static void InitKeyString()
    {
        // 辞書配列で対応関係を結び付けておく
        KeyString[KEY_INPUT_BACK] = "BackSpace";//"\b";
        KeyString[KEY_INPUT_TAB] = "\t";
        KeyString[KEY_INPUT_RETURN] = "\n";
        KeyString[KEY_INPUT_LSHIFT] = "ShiftL";
        KeyString[KEY_INPUT_RSHIFT] = "ShiftR";
        KeyString[KEY_INPUT_LCONTROL] = "CtrlL";
        KeyString[KEY_INPUT_RCONTROL] = "CtrlR";
        KeyString[KEY_INPUT_SPACE] = " ";
        KeyString[KEY_INPUT_PGUP] = "PgUp";
        KeyString[KEY_INPUT_PGDN] = "PgDn";
        KeyString[KEY_INPUT_END] = "End";
        KeyString[KEY_INPUT_HOME] = "Home";
        KeyString[KEY_INPUT_LEFT] = "Left";
        KeyString[KEY_INPUT_UP] = "Up";
        KeyString[KEY_INPUT_RIGHT] = "Right";
        KeyString[KEY_INPUT_DOWN] = "Down";
        KeyString[KEY_INPUT_INSERT] = "Insert";
        KeyString[KEY_INPUT_DELETE] = "Delete";
        KeyString[KEY_INPUT_MINUS] = "-"; CapsString[KEY_INPUT_MINUS] = "=";
        KeyString[KEY_INPUT_YEN] = "\\"; CapsString[KEY_INPUT_YEN] = "|";
        KeyString[KEY_INPUT_PREVTRACK] = "^"; CapsString[KEY_INPUT_PREVTRACK] = "~";
        KeyString[KEY_INPUT_PERIOD] = "."; CapsString[KEY_INPUT_PERIOD] = ">";
        KeyString[KEY_INPUT_SLASH] = "/"; CapsString[KEY_INPUT_SLASH] = "?";
        KeyString[KEY_INPUT_LALT] = "AltL";
        KeyString[KEY_INPUT_RALT] = "AltR";
        KeyString[KEY_INPUT_SCROLL] = "Scroll";
        KeyString[KEY_INPUT_SEMICOLON] = ";"; CapsString[KEY_INPUT_SEMICOLON] = "+";
        KeyString[KEY_INPUT_COLON] = ":"; CapsString[KEY_INPUT_COLON] = "*";
        KeyString[KEY_INPUT_LBRACKET] = "["; CapsString[KEY_INPUT_LBRACKET] = "{";
        KeyString[KEY_INPUT_RBRACKET] = "]"; CapsString[KEY_INPUT_RBRACKET] = "}";
        KeyString[KEY_INPUT_AT] = "@"; CapsString[KEY_INPUT_AT] = "`";
        KeyString[KEY_INPUT_BACKSLASH] = "\\"; CapsString[KEY_INPUT_BACKSLASH] = "_";
        KeyString[KEY_INPUT_COMMA] = ","; CapsString[KEY_INPUT_COMMA] = "<";
        KeyString[KEY_INPUT_CAPSLOCK] = "CapsLock";
        KeyString[KEY_INPUT_PAUSE] = "Pause";
        KeyString[KEY_INPUT_NUMPAD0] = "0";
        KeyString[KEY_INPUT_NUMPAD1] = "1";
        KeyString[KEY_INPUT_NUMPAD2] = "2";
        KeyString[KEY_INPUT_NUMPAD3] = "3";
        KeyString[KEY_INPUT_NUMPAD4] = "4";
        KeyString[KEY_INPUT_NUMPAD5] = "5";
        KeyString[KEY_INPUT_NUMPAD6] = "6";
        KeyString[KEY_INPUT_NUMPAD7] = "7";
        KeyString[KEY_INPUT_NUMPAD8] = "8";
        KeyString[KEY_INPUT_NUMPAD9] = "9";
        KeyString[KEY_INPUT_MULTIPLY] = "*";
        KeyString[KEY_INPUT_ADD] = "+";
        KeyString[KEY_INPUT_SUBTRACT] = "-";
        KeyString[KEY_INPUT_DECIMAL] = ".";
        KeyString[KEY_INPUT_DIVIDE] = "/";
        KeyString[KEY_INPUT_NUMPADENTER] = "\n";
        KeyString[KEY_INPUT_F1] = "F1";
        KeyString[KEY_INPUT_F2] = "F2";
        KeyString[KEY_INPUT_F3] = "F3";
        KeyString[KEY_INPUT_F4] = "F4";
        KeyString[KEY_INPUT_F5] = "F5";
        KeyString[KEY_INPUT_F6] = "F6";
        KeyString[KEY_INPUT_F7] = "F7";
        KeyString[KEY_INPUT_F8] = "F8";
        KeyString[KEY_INPUT_F9] = "F9";
        KeyString[KEY_INPUT_F10] = "F10";
        KeyString[KEY_INPUT_F11] = "F11";
        KeyString[KEY_INPUT_F12] = "F12";
        KeyString[KEY_INPUT_A] = "a"; CapsString[KEY_INPUT_A] = "A";
        KeyString[KEY_INPUT_B] = "b"; CapsString[KEY_INPUT_B] = "B";
        KeyString[KEY_INPUT_C] = "c"; CapsString[KEY_INPUT_C] = "C";
        KeyString[KEY_INPUT_D] = "d"; CapsString[KEY_INPUT_D] = "D";
        KeyString[KEY_INPUT_E] = "e"; CapsString[KEY_INPUT_E] = "E";
        KeyString[KEY_INPUT_F] = "f"; CapsString[KEY_INPUT_F] = "F";
        KeyString[KEY_INPUT_G] = "g"; CapsString[KEY_INPUT_G] = "G";
        KeyString[KEY_INPUT_H] = "h"; CapsString[KEY_INPUT_H] = "H";
        KeyString[KEY_INPUT_I] = "i"; CapsString[KEY_INPUT_I] = "I";
        KeyString[KEY_INPUT_J] = "j"; CapsString[KEY_INPUT_J] = "J";
        KeyString[KEY_INPUT_K] = "k"; CapsString[KEY_INPUT_K] = "K";
        KeyString[KEY_INPUT_L] = "l"; CapsString[KEY_INPUT_L] = "L";
        KeyString[KEY_INPUT_M] = "m"; CapsString[KEY_INPUT_M] = "M";
        KeyString[KEY_INPUT_N] = "n"; CapsString[KEY_INPUT_N] = "N";
        KeyString[KEY_INPUT_O] = "o"; CapsString[KEY_INPUT_O] = "O";
        KeyString[KEY_INPUT_P] = "p"; CapsString[KEY_INPUT_P] = "P";
        KeyString[KEY_INPUT_Q] = "q"; CapsString[KEY_INPUT_Q] = "Q";
        KeyString[KEY_INPUT_R] = "r"; CapsString[KEY_INPUT_R] = "R";
        KeyString[KEY_INPUT_S] = "s"; CapsString[KEY_INPUT_S] = "S";
        KeyString[KEY_INPUT_T] = "t"; CapsString[KEY_INPUT_T] = "T";
        KeyString[KEY_INPUT_U] = "u"; CapsString[KEY_INPUT_U] = "U";
        KeyString[KEY_INPUT_V] = "v"; CapsString[KEY_INPUT_V] = "V";
        KeyString[KEY_INPUT_W] = "w"; CapsString[KEY_INPUT_W] = "W";
        KeyString[KEY_INPUT_X] = "x"; CapsString[KEY_INPUT_X] = "X";
        KeyString[KEY_INPUT_Y] = "y"; CapsString[KEY_INPUT_Y] = "Y";
        KeyString[KEY_INPUT_Z] = "z"; CapsString[KEY_INPUT_Z] = "Z";
        KeyString[KEY_INPUT_0] = "0";
        KeyString[KEY_INPUT_1] = "1"; CapsString[KEY_INPUT_1] = "!";
        KeyString[KEY_INPUT_2] = "2"; CapsString[KEY_INPUT_2] = "\"";
        KeyString[KEY_INPUT_3] = "3"; CapsString[KEY_INPUT_3] = "#";
        KeyString[KEY_INPUT_4] = "4"; CapsString[KEY_INPUT_4] = "$";
        KeyString[KEY_INPUT_5] = "5"; CapsString[KEY_INPUT_5] = "%";
        KeyString[KEY_INPUT_6] = "6"; CapsString[KEY_INPUT_6] = "&";
        KeyString[KEY_INPUT_7] = "7"; CapsString[KEY_INPUT_7] = "'";
        KeyString[KEY_INPUT_8] = "8"; CapsString[KEY_INPUT_8] = "(";
        KeyString[KEY_INPUT_9] = "9"; CapsString[KEY_INPUT_9] = ")";
    }
};

#endif

<

Input.cppを新規作成してドラッグや複数のスクリーンがあるときの一番上のレイヤーを保管する変数 などの static変数を定義しましょう。

#include "Input.h"

int Input::prevStates[]; // 1フレーム前の状態
int Input::currentStates[]; // 現在の状態
// ↑static intのときはこの行書かないとシンボリックエラーになる件(このためだけにcpp書かなきゃならん)

std::unordered_map<Pad, bool> Input::isJoin; // 参加中のパッド番号辞書
// 参加中のパッドかどうか?
std::unordered_map<int, int> Input::padDic; // パッド番号からDXの定義への辞書

int Input::prevMouse; // 1フレーム前のマウス状態
int Input::currentMouse; // 現在のマウス状態
int Input::MouseX{ -1 }, Input::MouseY{ -1 }; // 現在のマウス位置
int Input::TopLayerMouseX{ -1 }, Input::TopLayerMouseY{ -1 }; // 現在のマウス位置
float Input::MouseWheel{ 0 };
int Input::ClickX{ -1 }, Input::ClickY{ -1 }, Input::MButton{ 0 }, Input::LogType{ 0 };
std::unordered_map<Mouse, int> Input::Click;//<Mouse⇒int> マウスのクリックXY位置辞書

//[キーボード入力]https://dxlib.xsrv.jp/function/dxfunc_input.html#R5N28
char Input::prevKey[256]; // 1フレーム前のキーボード状態
char Input::currentKey[256]; // 現在のキーボード状態
std::string Input::KeyString[256];//<キーコード⇒キー文字列>の辞書
std::string Input::CapsString[256];//<Caps状態キーコード⇒キー文字列>の辞書
bool Input::isCapsLocked{ false };// CapsLock状態か?
bool Input::isShifted{ false };// Shiftが押された状態か?


//★【DXコントローラバグ検知】初回プッシュが被ればDXバグで同一コントローラ
int Input::firstPushTiming[(int)Pad::NUM - 1]; // マウスのぶんの配列はいらないから-1
int Input::timing{ 0 };
bool Input::isBugCheckMode = false; //バグチェックを開始するか

Screen.hにWindowsのウィンドウみたいにドラッグできるSubScreenクラスを追加しましょう。

#ifndef _SCREEN_H
#define _SCREEN_H

#include <string>
#include <set> // サブスクリーンをDepth順に並べ替えるのに使う [multisetを使用するのに必要]
#include <unordered_map> // サブスクリーンをidをキーに保管するのに使う
#include <memory> // std::shared_ptrに使う

#include "DxLib.h"

#include <assert.h> // アサート警告表示用


#include "Input.h"

// 画面解像度
class Screen
{
public: //publicはC#と違いpublic:以下にまとめて書かれるスタイル
    static const int Width = 1280; // 幅
    static const int Height = 720; // 高さ C#と違いstaticクラスではなく個々の【すべての変数にstaticをつけて】staticなクラスとする

    // X,Yがウィンドウの中にあるか
    static bool IsInsideWindow(int screenX = Input::MouseX, int screenY = Input::MouseY) { return (0 <= screenX && screenX < Width) && (0 <= screenY && screenY < Height); }

};// C#と違ってクラスの定義も;セミコロンで終わる


class SubScreen : public std::enable_shared_from_this<SubScreen>
{    // [std::shared_ptrでthisポインタをつかうためには]↑ https://www.kabuku.co.jp/developers/cpp_enable_shared_from_this
protected:
    int id; //サブスクリーンの管理ID
    int handle = -1; //サブスクリーンへのハンドル
    int tmpDrawScreen = -1; // DrawStartとDrawEndのあいだDrawStart前のhandleを一時保管する
    int tmpResetSetting = -1; // DrawStartとDrawEndのあいだDrawStart前の設定を一時保管する

    static int serialDivID; //= 0 サブスクリーンを新たに生成するときの通し番号(シリアルID)

public:
    int frameWidth = 2;//サブスクリーンの枠の幅
    unsigned int frameColor = DxLib::GetColor(255, 255, 255); //サブスクリーンの枠の色
    float x = 0, y = 0; //サブスクリーンの描画位置
    float zoomRate = 1.0f; // スクリーンに描くズーム率
    float LocalX(float mainScreenX) { return (mainScreenX - x) / zoomRate; } // メインのスクリーンのXからサブスクリーンのローカルなXへ変換
    float LocalY(float mainScreenY) { return (mainScreenY - y) / zoomRate; } // メインのスクリーンのYからサブスクリーンのローカルなYへ変換
    bool IsInside(float mainScreenX, float mainScreenY) { return (x < mainScreenX && mainScreenX < x + Width * zoomRate) && (y < mainScreenY && mainScreenY < y + Height * zoomRate); } // メインのスクリーン上のXとYがサブスクリーンの内側にあるか
    bool IsOnBarFrame(float mainScreenX, float mainScreenY) { return (x - frameWidth - 1 < mainScreenX && mainScreenX < x + Width * zoomRate + frameWidth + 1) && (y - frameWidth - 1 - barFrameHeight < mainScreenY && mainScreenY < y + frameWidth + 1 + Height * zoomRate); } // バーを含むウィンドウの内側にあるか
    bool IsOnBar(float mainScreenX, float mainScreenY) { return (x - frameWidth - 1 < mainScreenX && mainScreenX < x + Width * zoomRate + frameWidth + 1) && (y - frameWidth - 1 - barFrameHeight < mainScreenY && mainScreenY < y + frameWidth + 1); } // バーを含むウィンドウの内側にあるか

    float Width = 0, Height = 0; // サブスクリーンの幅と高さ
    int Alpha = 255; // デフォルトは透明じゃない(アルファ不透明度=255)
    float renderX = 0, renderY = 0; //描画オフセット位置(一部だけを抜き出してスクリーンにレンダリングするときに設定)
    float renderWidth = 0; float renderHeight = 0; // 抜き出してスクリーンに描く部位の幅と高さ

    int Depth = 0; //[この値が大きいほど手前に割り込んで描かれる] [Unityの描画順を参考に] https://tama-lab.net/2017/07/unity%E3%81%A7%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E6%8F%8F%E7%94%BB%E9%A0%86%E3%82%92%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/
    static bool isDepthLayerChanged; //= false 新しいレイヤが追加されり変更があった時だけ並べ替えるためのフラグ

    bool isInitAgain = false; // WidthやHeightやAlphaが変わったらDxLib::MakeScreenのやり直し
    bool isFrameShow = false; // ウィンドウのフレームを描くか
    bool isBarShow = false; // ウィンドウズみたいなウィンドウのバーを描くか
    int barFrameHeight = 15; // ウィンドウのバーの高さ
    int barFrameFontSize = 14; // ウィンドウのバーのnameのフォントサイズ
    unsigned int barColor = DxLib::GetColor(0, 0, 0); //バーの色

    unsigned int stringColor = DxLib::GetColor(255, 255, 255); //サブスクリーンの枠の色
    std::string drawString = ""; // デバッグなどのためにスクリーンの左上に表示する文字列(サブスクリーン番号など)
    std::string tag = "";
    std::string name = ""; //サブスクリーン名

    int GetID() { return id; } // サブスクリーンの管理ID
    inline static int NewID() { return ++serialDivID; } //スクリーンの通し番号を+1(先に+1するので0はなく1からID割り振り)

    static std::shared_ptr<SubScreen> Get(int id) { // ★constは添え字[]読み取りの処理を定義
        if (SubScreen::layers.count(id) == 0)
            assert("指定したIDの分割スクリーンはまだ未生成!Make()関数で新規生成してください" == "");
        return layers[id]; // 読み取り
    }

protected:
    //          ペア<Depth, id>
    typedef std::pair<int, int> depth_id_pair;
    static std::multiset<depth_id_pair> depth_id_ordered; // std::multiset[キーの無い版のstd::map(マルチでキーかぶりOK版)] http://alctail.sakura.ne.jp/tip/cplus_kannrenn/cplusplus/stl.shtml#stl2-7
public:
    // オーダー(並べ替えられた)IDを返す
    static std::vector<int> GetOrderedIDs(bool isDesc = false) { // Desc(降順) 4 3 2 2 1 みたいな降りるid順
        std::vector<int> orderedIDs;
        std::multiset<depth_id_pair>::iterator ite = depth_id_ordered.begin();
        while (ite != depth_id_ordered.end())
        {
            int id = ite->second;
            orderedIDs.emplace_back(id); // depth順に追加
            ite++;
        }
        if (!isDesc) return orderedIDs;

        //[逆順の範囲for文] https://koturn.hatenablog.com/entry/2018/07/21/190000
        std::vector<int> descIDs;
        for (auto itr = std::rbegin(orderedIDs); itr != std::rend(orderedIDs); ++itr)
            descIDs.emplace_back(*itr); // 逆順に
        return descIDs;
    }

    class Greater_Depth
    {
    public: // Depthでソートするために比較用オペレータを定義
        bool operator() (SubScreen* left, SubScreen* right) const
        {
            return (left->Depth > right->Depth); // 不等号 > で比較
        }
    };
    // Depthでソートするためにオペレータをオーバーロード
    bool operator < (const SubScreen& another) const {
        return this->Depth > another.Depth;
    }

    //[protectedなコンストラクタでもstd::make_sharedで呼ぶテク] https://gintenlabo.hatenablog.com/entry/20131211/1386771626
    template<typename ProtectedT>
    struct ProtectedCall : public ProtectedT // ProtectedTをpublicで継承することで ProtectedCallから見てProtectedTのコンストラクタはpublicになるテク
    {
        template<class... ArgsT> // std::make_sharedはprotectedな継承クラスは呼べないのでそれを回避するテクニック
        explicit ProtectedCall(ArgsT&&... args)
            : ProtectedT(std::forward<ArgsT>(args)...) { } //std::forwardで複数引数を完全転送で引き渡し https://proc-cpuinfo.fixstars.com/2016/03/c-html/
    };

    struct Layers : public std::unordered_map<int, std::shared_ptr<SubScreen>> // std::unordered_mapを継承してmake_sharedの機能も追加する
    {
        template <class TypeT>
        std::shared_ptr<TypeT> make_shared() // この関数経由で新規生成すればNewIDで新しいIDで辞書にemplaceで登録もいっしょにしてくれる
        {
            int newID = SubScreen::NewID(); //スクリーンの通し番号を+1(先に+1するので0はなく1からID割り振り)
            auto pNewObj = std::make_shared<ProtectedCall<TypeT>>(); // std::make_sharedで共有状態で新規生成 (ProtectedCall経由でprotectedなコンストラクタも呼べる)
            this->emplace(newID, pNewObj); //★idはかぶらないはずだからそれをキーに辞書に登録
            return std::dynamic_pointer_cast<TypeT>(this->at(newID));// std::shared_ptrのキャスト https://chiku2gonzalez.hatenablog.com/entry/2014/10/23/230011
        }
    };
    static Layers layers; // 生成したサブスクリーンの高速辞書配列<id,サブスクリーンの共有ポインタ>

protected:
    // コンストラクタはprotected:(外部読み取り不可)にしておくのでCreate()関数で生成してCreate().Set(~)で初期設定して ください
    // [Factoryパターン:生成経路限定工場] https://qiita.com/shoheiyokoyama/items/d752834a6a2e208b90ca
    // Factoryパターンの本質はクラスのユーザーに勝手にnewやmake_sharedで自由にコンストラクタを呼ばせないこと
    // newのかわりにCreate関数を経由して生成を強制することでCreateと同時にIDを振ったり辞書に登録して勝手にnewされて一貫性が崩れるの を防ぐ
    SubScreen() //float x = 0, float y = 0, float Width = 0, float Height = 0, int Alpha = 255, int Depth = 0, std::string name = "")
    {
        // std::enable_shared_from_thisを継承するとコンストラクタ内ではまだthisは未確定なので shared_from_this()を使う関数(例.Set関数)は呼べなくなる
        // Set(x, y, Width, Height, Alpha, Depth, name);

        this->id = SubScreen::serialDivID; // 通し番号割り振り(ゲーム中は被らない【通し】番号)
    }
public:
    // 新しくサブスクリーンを生成してそれへの共有ポインタを返す
    static std::shared_ptr<SubScreen> Create()
    {
        isDepthLayerChanged = true; // 新しいレイヤーが増えたので再並べ替えフラグをオンに
        return SubScreen::layers.make_shared<SubScreen>();
    }
    // サブスクリーンを削除
    static void Erase(int id) { SubScreen::layers.erase(id); isDepthLayerChanged = true; }

    virtual ~SubScreen() { if (handle >= 0) DxLib::DeleteGraph(handle); } // 仮想デストラクタ

    virtual void InitScreen()
    {   // 分割画面を描くためのスクリーンの描画ハンドルを得る
        // ★複数プレイヤ画面や複数カメラ視点に使える[敵目線カメラサブ画面など]
        if (renderWidth >= 1 && renderHeight >= 1)
        {
            // SetDrawScreen に引数として渡せる( 描画対象として使用できる )グラフィックハンドルを作成するかどうかを設定する( TRUE:描画可能グラフィックハンドルを作成する  FLASE:通常のグラフィックハンドルを作成する( デフォルト ) )
            DxLib::SetDrawValidGraphCreateFlag(TRUE);

            // [描画スクリーンの紙を用意] https://dxlib.xsrv.jp/function/dxfunc_graph1.html#R3N25
            handle = DxLib::MakeScreen((int)renderWidth, (int)renderHeight, (Alpha == 255 ? FALSE : TRUE));
            // ↑[半透明スクリーンも可] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=3650

            // スクリーンを作成したらデフォルト設定に戻しておかないと
            DxLib::SetDrawValidGraphCreateFlag(FALSE);

            isInitAgain = false; // 再びInitが必要かのフラグをオフに
        }
    }

    std::shared_ptr<SubScreen> SetX(float x) { this->x = x; return shared_from_this(); }
    std::shared_ptr<SubScreen> SetY(float y) { this->y = y; return shared_from_this(); }
    std::shared_ptr<SubScreen> SetZoomRate(float zoomRate) { this->zoomRate = (zoomRate < 0.1f) ? 0.1f : zoomRate; return shared_from_this(); }
    std::shared_ptr<SubScreen> SetWidth(float width) { this->Width = width;  return shared_from_this(); }
    std::shared_ptr<SubScreen> SetHeight(float height) { this->Height = height; return shared_from_this(); }
    std::shared_ptr<SubScreen> SetRenderX(float renderX) { this->renderX = renderX; return shared_from_this(); }
    std::shared_ptr<SubScreen> SetRenderY(float renderY) { this->renderY = renderY; return shared_from_this(); }
    std::shared_ptr<SubScreen> SetRenderWidth(float renderWidth) { if (((int)this->renderWidth) != (int)renderWidth)  isInitAgain = true; this->renderWidth = renderWidth;   return shared_from_this(); }
    std::shared_ptr<SubScreen> SetRenderHeight(float renderHeight) { if (((int)this->renderHeight) != (int)renderHeight) isInitAgain = true; this->renderHeight = renderHeight; return shared_from_this(); }
    std::shared_ptr<SubScreen> SetAlpha(int alpha) { if (((int)this->Alpha) != (int)alpha)  isInitAgain = true;  this->Alpha = alpha;  return shared_from_this(); }
    std::shared_ptr<SubScreen> SetDepth(int depth) { if (this->Depth != depth) isDepthLayerChanged = true; this->Depth = depth; return shared_from_this(); }
    // フレームに表示出力する文字の設定
    std::shared_ptr<SubScreen> SetDrawString(std::string drawString = "") { this->drawString = drawString; return shared_from_this(); }
    // フレーム枠の幅の設定
    std::shared_ptr<SubScreen> SetFrameWidth(int frameWidth) { this->frameWidth = frameWidth; return shared_from_this(); }
    // フレーム枠の色の設定
    std::shared_ptr<SubScreen> SetFrameColor(unsigned int frameColor) { this->frameColor = frameColor; return shared_from_this(); }
    // フレームとバーを見せるかの設定
    std::shared_ptr<SubScreen> SetIsBarFrameShow(bool isBarShow, bool isFrameShow = true) { this->isBarShow = isBarShow; this->isFrameShow = isFrameShow; return shared_from_this(); }
    // フレームのバーの高さの設定
    std::shared_ptr<SubScreen> SetBarFrameHeight(int barFrameHeight) { this->barFrameHeight = barFrameHeight; return shared_from_this(); }
    // フレームのバーのnameのフォントのサイズ設定
    std::shared_ptr<SubScreen> SetBarFrameFontSize(int barFrameFontSize) { this->barFrameFontSize = barFrameFontSize; return shared_from_this(); }
    // フレームのバーの背景色設定
    std::shared_ptr<SubScreen> SetBarColor(unsigned int barColor) { this->barColor = barColor; return shared_from_this(); }
    // フレームの設定
    std::shared_ptr<SubScreen> SetFrame(int frameWidth, unsigned int frameColor, bool isBarShow = false, int barFrameHeight = 15) { this->frameWidth = frameWidth; this->isFrameShow = (frameWidth>0)?true:false; this->frameColor = frameColor; this->isBarShow = isBarShow; this->barFrameHeight = barFrameHeight; return shared_from_this(); }

    // 色々セッティングして初回と設定変更があった時だけサブ画面を描くためのスクリーンの描画ハンドルを得る
    std::shared_ptr<SubScreen> Set(float x = 0, float y = 0, float width = 0, float height = 0, int alpha = 255, int depth = 0, std::string name = "")
    {
        this->x = x; this->y = y;

        SetWidth(width); SetHeight(height); SetRenderWidth(width); SetRenderHeight(height); SetAlpha(alpha);
        if (isInitAgain) InitScreen(); // 変更があるか確認してある時だけInitし直すようにすると早くなる

        if (this->Depth != depth) isDepthLayerChanged = true; // Depthに変更があれば再並べ替えフラグをオンに
        this->Depth = depth; this->name = name;

        return shared_from_this(); // 設定した後の自分自身を返す
    }

    virtual void Update()
    {
        // なにかUpdateしたいことがあれば処理を追加
    }

    virtual void Draw()
    {
        DrawScreen(); // スクリーンをDrawRectExtendGraphを使って描く(ズームやアルファ透明度も反映させる)
        DrawFrame(); // スクリーンのまわりにフレームを描く
        DrawBarFrame(); // スクリーンの上にWindowsみたいなバーを描く

        // デバッグ用の文字表示
        int tmpFontSize = GetFontSize();// 元のフォントサイズを保管
        DxLib::SetFontSize(barFrameFontSize); // 描画する文字列のサイズを設定
        {
            DxLib::DrawString((int)x, (int)y, drawString.c_str(), stringColor); // デバッグ用の文字列をスクリーンの左上(0,0)に描画
        }
        DxLib::SetFontSize(tmpFontSize); // 元のフォントサイズ設定にすぐ戻す
    }

    virtual void DrawScreen() // スクリーンをDrawRectExtendGraphを使って描く(ズームやアルファ透明度も反映させる)
    {
        int tmpBlendMode, tmpBlendParam;
        DxLib::GetDrawBlendMode(&tmpBlendMode, &tmpBlendParam); // 現在の描画ブレンドモードを取得しておく
        if (Alpha != 255) DxLib::SetDrawBlendMode(DX_BLENDMODE_ALPHA, Alpha); // 半透明のスクリーンも描ける https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4978
        {   // 画面ウィンドウに描画されたスクリーン内容を描く
            DxLib::DrawRectExtendGraph((int)x, (int)y, (int)(x + Width * zoomRate), (int)(y + Height * zoomRate), renderX, renderY, renderWidth, renderHeight, handle, (Alpha == 255 ? FALSE : TRUE));
        }
        if (Alpha != 255) DxLib::SetDrawBlendMode(tmpBlendMode, tmpBlendParam); // もとのブレンドモードに戻しておく
    }

    virtual void DrawFrame() // スクリーンのまわりにフレームを描く
    {
        if (!isFrameShow) return; // フレームを描かない設定の場合
        //サブスクリーンの枠の表示
        for (int f = 1; f <= frameWidth; f++)
        {
            DxLib::DrawBox(
                (int)((int)x - f + 0.5f),
                (int)((int)y - 1 + 0.5f),
                (int)((int)(x + Width * zoomRate + f) + 0.5f),
                (int)((int)(y + Height * zoomRate + f) + 0.5f),
                frameColor, FALSE);
        }
    }

    virtual void DrawBarFrame() // スクリーンの上にWindowsみたいなバーを描く
    {
        if (!isBarShow) return; // バーのフレームを描かない設定の場合
       
        // バーの背景の地を塗りつぶす
        DxLib::DrawBox(
            (int)((int)x + 0.5f),
            (int)((int)y - barFrameHeight + 0.5f),
            (int)((int)(x + Width * zoomRate) + 0.5f),
            (int)((int)(y - 1) + 0.5f),
            barColor, TRUE);

        // マウスが上に乗った時の色はGetColor(255, 0, 0)で赤枠にしている
        unsigned int mouseOverColor = (this->id == topMouseOverID) ? GetColor(255, 0, 0) : frameColor;
        int barFrameWidth = (1 < frameWidth) ? 2 : 1;
        // for文でフレームの枠=frameWidthの大きさぶんBoxを複数描いて枠を太くする
        for (int f = frameWidth - barFrameWidth; f <= frameWidth; f++)
        {
            DxLib::DrawBox(
                (int)((int)x - f + 0.5f),
                (int)((int)y - barFrameHeight - f + 0.5f),
                (int)((int)(x + Width * zoomRate + f) + 0.5f),
                (int)((int)(y + Height * zoomRate + f) + 0.5f),
                (f == frameWidth) ? mouseOverColor : frameColor, FALSE);
        }

        // バーにウィンドウの名前表示(バーの高さがフォントのサイズ以下なら文字サイズをバーの大きさに合わせる)
        int tmpFontSize = GetFontSize();// 元のフォントサイズを保管
        DxLib::SetFontSize((barFrameHeight > barFrameFontSize) ? barFrameFontSize : barFrameHeight); // 描画する文字列のサイズを設定
        {
            DxLib::DrawString((int)x, (int)y - barFrameHeight, name.c_str(), frameColor); // バーにnameの文字列を描く
        }
        DxLib::SetFontSize(tmpFontSize); // 元のフォントサイズ設定にすぐ戻す
       
    }


    static void UpdateScreens() // スクリーンの状態の更新(ドラッグ移動など)
    {
        for (auto&& subScreen : layers)
            subScreen.second->Update();
       
        //OrderByLayerDepth(); // レイヤーのDepth順に並べ替え→FocusOnClickScreen関数でやる

        FocusOnClickScreen(); // クリックしたスクリーンを一番上にするように並べ替え

        DragScreen();// サブスクリーンをドラッグで移動できるように

        ZoomScreens(); // サブスクリーンをマウスホイールでズームインズームアウトできるように
    }

    static void DrawScreens() // サブスクリーンを全部まとめて描く
    {
        OrderByLayerDepth(); // レイヤーのDepth順に並べ替え

        //↓並べ変わった順を使ってDepth順にサブスクリーンをDraw
        std::multiset<depth_id_pair>::iterator ite = depth_id_ordered.begin();
        while (ite != depth_id_ordered.end())
        {
            int id = ite->second;
            if (layers.count(id) > 0)
                layers[id]->Draw(); // depth順にDrawでサブスクリーンを複数レイヤーで描く
            ite++;
        }
    }

    // サブスクリーンの描画内容をきれいにクリア
    void ClearDrawScreen()
    {
        if (handle < 0) return;
        DrawStart(); // 設定先をサブスクリーンとする
        DxLib::ClearDrawScreen(); // 一旦 サブスクリーンをきれいにクリア
        DrawEnd();
    }

    // ★このStartとEndのあいだの描画処理はサブスクリーンに描かれるようになる
    void DrawStart()
    {   // サブスクリーンが描画可能状態なら
        if (handle < 0) return;
        if (isInitAgain) InitScreen();// 変更があるか確認してある時だけInitでDxLib::MakeScreenをし直す
        tmpResetSetting = GetUseSetDrawScreenSettingReset(); // 設定を一旦tmpに退避
        SetUseSetDrawScreenSettingReset(FALSE); // SetDrawScreen切り替え時に設定をリセットしない(FALSE)(これをやらないとカメラ視点などがリセットされるから)
        tmpDrawScreen = DxLib::GetDrawScreen(); // Endまでのあいだ一旦変更前のhandleを退避
        // ゲームを描く先を用意しておいたサブスクリーンに設定(キャンバスを指定する)
        DxLib::SetDrawScreen(handle);
    }

    // ★このStartとEndのあいだの描画処理はサブスクリーンに描かれるようになる
    void DrawEnd()
    {   // サブスクリーンが描画可能状態なら
        if (handle < 0) return;
        // ゲームを描く先をもとのデフォルトに戻しておく
        DxLib::SetDrawScreen((tmpDrawScreen == -1) ? DX_SCREEN_BACK : tmpDrawScreen);
        SetUseSetDrawScreenSettingReset((tmpResetSetting == -1) ? TRUE : tmpResetSetting); // 設定をすぐに元に戻す
    }



    // スタティック機能群(サブスクリーンの辞書やマウスでのドラッグ移動など)
public:
    static bool isMouseOvered; //= false マウスオーバーしてるレイヤがあるか
    static int topMouseOverID; //= -1 一番上のマウスオーバーしてるレイヤのID
    static int topDepthID; //= -1 一番上のレイヤのID
    static int topDepth; //= -1 一番上のレイヤのDepth
protected:
   
    static void OrderByLayerDepth() // レイヤーのDepth順に並べ替え
    {
        if (!isDepthLayerChanged) return; // レイヤーのDepthに変化がなければ並べ替えは不要

        depth_id_ordered.clear(); // 並べ替え前に既存の順序データを一度クリア
        for (const auto& subScreen : layers)
        {
            int id = subScreen.first;
            int depth = subScreen.second->Depth;//ペア(左,右)だと左の数字の順に自動で並ぶ
            depth_id_ordered.insert(std::make_pair(depth, id)); // https://qiita.com/izmktr/items/17e3009041b841b26a34
        } //全部マルチセットに↑入れると自動でdepth順に並べ変わってる

        CheckLayerOrder(); // マウスオーバーしてるレイヤや一番上のレイヤも再び調べる
    }

    static void CheckLayerOrder() // 一番上のマウスオーバーしてるレイヤを調べる
    {
        Input::TopLayerMouseX = Input::MouseX; Input::TopLayerMouseY = Input::MouseY; // 一番上のレイヤのTopLayerMouseマウス座標をリセット
        isMouseOvered = false; // ループを回す前にfalseで初期化
        topMouseOverID = -1; // -1ならマウスオーバーしているレイヤはない
        //↓並べ変わった順を使ってDepth順に調べ、一番上のマウスオーバーしてるレイヤを調べる
        std::multiset<depth_id_pair>::iterator ite = depth_id_ordered.begin();
        topDepth = -1;
        while (ite != depth_id_ordered.end())
        {
            int id = ite->second;
            auto subscreen = layers[id];
            bool _isMouseOvered = subscreen->IsOnBarFrame((float)Input::MouseX, (float)Input::MouseY);
            if (_isMouseOvered && id >= 0) // depth順で一番上のマウスオーバーしてるレイヤを調べる
                topMouseOverID = id; // idが0以上のレイヤしか検知しない
            if (_isMouseOvered)
                isMouseOvered |= _isMouseOvered; // | で[または]のビット演算(1つでもtrueがあれば最終的にtrueに)

            if (ite->first >= topDepth)
                topDepthID = id; // マウスが乗ってるかと関係なしに最大Depthも記録
            topDepth = ite->first;
            ite++;
            if (ite == depth_id_ordered.end() && topMouseOverID != -1)
            {
                auto topLayerScreen = layers[topMouseOverID];
                // 一番上のレイヤーのマウスの「ローカルな」座標を登録しておけば「そのウィンドウ内のマウス位置」を得られる
                Input::TopLayerMouseX = topLayerScreen->LocalX(Input::MouseX);
                Input::TopLayerMouseY = topLayerScreen->LocalY(Input::MouseY); // LocalXYならそのウィンドウの左上を(0,0)とした「ウィンドウ内のマウス位置」が求まる
            }
        }
    }

public:
    static int clickDownID; //= -1 クリックした瞬間のマウスが乗ってるサブウィンドウの番号(マウスを話した瞬間のIDと比べて同じならそのウィンドウにフォーカスする)
    static void FocusOnClickScreen() // クリックしたスクリーンを一番上にするように並べ替え
    {
        OrderByLayerDepth(); // レイヤーのDepth順に並べ替え
        if (topMouseOverID == -1) return;

        if (Input::GetButtonDown(Pad::Mouse, MOUSE_INPUT_LEFT)) // マウスの右ボタンが押された瞬間か?(ドラッグ開始)
        {   // clickDownID = クリックした瞬間(ドラッグ開始時)のマウスが乗ってる一番上のサブウィンドウのID番号
            clickDownID = topMouseOverID;
        }
        else if (Input::GetButtonUp(Pad::Mouse, MOUSE_INPUT_LEFT)) // マウスの右ボタンが離された瞬間か?
        {
            int clickUpID = topMouseOverID; // マウスのクリックを離した瞬間のスクリーンIDは基本はマウスが乗っている一番上のスクリーン
            if (topMouseOverID != clickDownID // ↑例外的に、clickDownIDがマウスを離した瞬間のtopMouseOverIDと違うとき
                && layers.count(clickDownID) > 0
                && layers[clickDownID]->Depth < layers[topMouseOverID]->Depth) // clickDownIDのDepthがマウスを離した瞬間に一番上にあるウィンドウのDepthより小さい
            {
                clickUpID = clickDownID; // clickUpIDをドラッグ中の後ろに隠れているウィンドウに変える
            }

            // ドラッグを離した瞬間のスクリーンを一番上のDepthへと書き換えたい
            if (layers.count(topDepthID) > 0)
            {
                auto clickUpScreen = layers[clickUpID]; // マウスを離した瞬間のドラッグ中の一番上のレイヤにしたいスクリーン
                auto topDepthScreen = layers[topDepthID]; // Depthが一番大きいスクリーン
                if (clickUpScreen->Depth <= topDepthScreen->Depth) // Depthが一番大きいスクリーンよりクリックを離したスクリーンのDepthが小さいなら
                {
                    int newTopDepth = topDepthScreen->Depth;
                    if (clickUpScreen->Depth == newTopDepth && clickUpID != topDepthID)
                        newTopDepth++; //  マウスを離した瞬間のマウスが乗ってる一番上のレイヤのスクリーンのDepthをtopDepthScreenより+1大きくする
                    //if (clickUpScreen->Depth != topDepthScreen->Depth)
                    //    topDepthScreen->SetDepth(clickUpScreen->Depth); // 現在の一番上のレイヤのスクリーンとクリックを離したスクリーンのDepthを入れ替え
                    clickUpScreen->SetDepth(newTopDepth); // +1大きくしたDepthをSetする

                    OrderByLayerDepth(); // [再び並べ替え]レイヤーのDepth順に
                }
            }
        }
    }

    /*static void FocusOnScreen(int id)
    {
        // 特定のidへフォーカスさせたければ関数を自作してもよいかも
    }*/

    static int prevDragX; //= -1 直前のドラッグ中のマウスの位置
    static int prevDragY; //= -1 直前のドラッグ中のマウスの位置
    static bool isDraging() { return (dragingID != -1); }
    static int dragingID; //= -1 ドラッグ中のサブスクリーンのID

    static bool DragScreen() // サブスクリーンをドラッグで移動できるように
    {
        int dragID = -1; // ドラッグ移動する一番上のサブスクリーンのID
        if (dragingID == -1 && isMouseOvered)
            dragID = topMouseOverID;
        else if (dragingID != -1)
            dragID = dragingID;


        if (dragID != -1 && Input::Click[Mouse::DragL] && Screen::IsInsideWindow())
        {
            auto dragScreen = layers[dragID];
            dragingID = dragID;
            if (prevDragX != -1 && prevDragY != -1)
            {   // ★前の位置prevと現在の位置に移動があれば、その差ぶんだけ+=することでドラッグ移動を実現
                if (prevDragX != Input::Click[Mouse::DragLX])
                    dragScreen->x += (float)(Input::Click[Mouse::DragLX] - prevDragX);

                if (prevDragY != Input::Click[Mouse::DragLY])
                    dragScreen->y += (float)(Input::Click[Mouse::DragLY] - prevDragY);
            }
            prevDragX = Input::Click[Mouse::DragLX];
            prevDragY = Input::Click[Mouse::DragLY];
            return true;
        }
        else
        {
            prevDragX = -1; prevDragY = -1;
            dragingID = -1;
            return false;
        }
    }

    // サブスクリーンをバーの上てマウスホイールグリグリするとズームインズームアウトできるように
    static void ZoomScreens(bool isZoomByBar = true)
    {
        if (!isMouseOvered) return;

        auto zoomScreen = layers[topMouseOverID];
        if (isZoomByBar && !zoomScreen->IsOnBar((float)Input::MouseX, (float)Input::MouseY))
            return; // バーの上にカーソルがない

        zoomScreen->SetZoomRate(zoomScreen->zoomRate + 0.1f * (Input::MouseWheel));
    }
};


#endif

Screen.cppを新規作成して static変数を定義しましょう。

#include "Screen.h"

int SubScreen::serialDivID{ 0 }; // サブスクリーンを新たに生成するときの通し番号(シリアルID)
bool SubScreen::isDepthLayerChanged{ false }; // 新しいレイヤが追加されり変更があった時だけ並べ替えるためのフラグ

std::multiset<SubScreen::depth_id_pair> SubScreen::depth_id_ordered; // std::multiset[キーの無い版のstd::map(マルチでキーかぶりOK版)]

bool SubScreen::isMouseOvered{ false }; // マウスオーバーしてるレイヤがあるか
int SubScreen::topMouseOverID{ -1 }; // 一番上のマウスオーバーしてるレイヤのID
int SubScreen::topDepthID{ -1 }; // 一番上のレイヤのID
int SubScreen::topDepth{ -1 }; // 一番上のレイヤのDepth


SubScreen::Layers SubScreen::layers; // 生成したサブスクリーンの高速辞書配列<id,サブスクリーンの共有ポインタ>

int SubScreen::clickDownID{ -1 }; // クリックした瞬間のマウスが乗ってるサブウィンドウの番号(マウスを話した瞬間のIDと比べて同じならそのウィンドウにフォーカスする)

int SubScreen::prevDragX{ -1 }; // 直前のドラッグ中のマウスの位置
int SubScreen::prevDragY{ -1 }; // 直前のドラッグ中のマウスの位置
int SubScreen::dragingID{ -1 }; // ドラッグ中のサブスクリーンのID

ウィンドウのドラッグってどうやってるのって、シンプルにいうなら
前の瞬間(prev)と 現在の位置に移動があれば、その差ぶんだけ+=する、というだけなのでベースとなっているアイデア自体は実はシンプルなものです
(ただレイヤーの並べ替えとか一番上じゃないレイヤーをクリックしたときにフォーカスして一番上にするとか考え出すと結構たいへんですが)

main.cppのコードにSubScreenクラスを使ってドラッグできるウィンドウズみたいなサブのスクリーンを表 示させてみましょう。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include "Input.h"
#include "Screen.h"
#include "Ply.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズWidth×Heightのカラービット数32ビットで起動
    SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    SetDrawScreen(DX_SCREEN_BACK);

    // ゲームのwhileループを開始する前の初期化処理
    int texImage = -1;
    texImage = LoadGraph("Image/dice.png");
    assert(texImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる
    float imgWidth, imgHeight;
    GetGraphSizeF(texImage, &imgWidth, &imgHeight); // テクスチャ画像のサイズを得る

    Ply::Data Data; // .plyのデータのクラス
    Ply::load("Image/dice.ply", Data); // .plyデータをロード 軸を変えてdice1.plyや dice2.plyなどいろいろ試してみよう

    size_t numIndex = 0; // インデックスの数
    std::vector<uint32_t> Index; // 3Dモデルのポリゴンのインデックス配列
    std::vector<VERTEX3D> Vertex; // 3Dモデルのポリゴンの頂点配列

    auto vertex = Data["vertex"]; // 頂点へのアクセス
    assert(vertex->size() != 0 && "Plyデータの頂点サイズが0です。ファイルのパスは大丈夫ですか?");
    Vertex.resize(vertex->size()); // 配列の数の事前確保

    // [.plyの頂点のプロパティ情報] https://jp.mathworks.com/help/vision/ug/the-ply-format.html
    const float* p_x = vertex->properties["x"]->ptr<float>();
    const float* p_y = vertex->properties["y"]->ptr<float>();
    const float* p_z = vertex->properties["z"]->ptr<float>();
    const float* p_nx = (vertex->properties.has_key("nx")) ? vertex->properties["nx"]->ptr<float>() : nullptr;
    const float* p_ny = (vertex->properties.has_key("ny")) ? vertex->properties["ny"]->ptr<float>() : nullptr;
    const float* p_nz = (vertex->properties.has_key("nz")) ? vertex->properties["nz"]->ptr<float>() : nullptr;
    bool has_norm = !(p_nx == nullptr || p_ny == nullptr || p_nz == nullptr);
    const float* p_s = (vertex->properties.has_key("s")) ? vertex->properties["s"]->ptr<float>() : nullptr; // Blender書き出しでは(u,v)ではなく(s,t)として書き出されている
    const float* p_t = (vertex->properties.has_key("t")) ? vertex->properties["t"]->ptr<float>() : nullptr;

    const unsigned char* p_R = (vertex->properties.has_key("red")) ? vertex->properties["red"]->ptr<unsigned char>()
        : (vertex->properties.has_key("r")) ? vertex->properties["r"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_G = (vertex->properties.has_key("green")) ? vertex->properties["green"]->ptr<unsigned char>()
        : (vertex->properties.has_key("g")) ? vertex->properties["g"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_B = (vertex->properties.has_key("blue")) ? vertex->properties["blue"]->ptr<unsigned char>()
        : (vertex->properties.has_key("b")) ? vertex->properties["b"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_A = (vertex->properties.has_key("alpha")) ? vertex->properties["alpha"]->ptr<unsigned char>()
        : (vertex->properties.has_key("a")) ? vertex->properties["a"]->ptr<unsigned char>() : nullptr;
    bool has_color = !(p_R == nullptr || p_G == nullptr || p_B == nullptr || p_A == nullptr);

    for (size_t i = 0, iEnd = Vertex.size(); i < iEnd; i += 1)
    {
        // 頂点のデータをセット 各ツールの系一覧 https://zenn.dev/it_ks/articles/cbe27860548ea1
        //Vertex[i].pos = VGet(p_x[i], p_y[i], p_z[i]); // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        //Vertex[i].pos = VGet(p_x[i], -p_y[i], p_z[i]); // 右手Y↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        //Vertex[i].norm = (has_norm) ? VNorm(VGet(p_nx[i], -p_ny[i], p_nz[i])) : VGet(0.0f, 0.0f, 0.0f);

        // 右手Z↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        Vertex[i].pos = VGet(-(p_y[i]), p_z[i], p_x[i]);
        Vertex[i].norm = (has_norm) ? VNorm(VGet(-p_ny[i], p_nz[i], p_nx[i])) : VGet(0.0f, 0.0f, 0.0f);
        Vertex[i].dif = (has_color) ? GetColorU8(p_R[i], p_G[i], p_B[i], p_A[i]) : GetColorU8(255, 255, 255, 255);
        Vertex[i].spc = GetColorU8(255, 255, 255, 255);
        Vertex[i].u = (p_s == nullptr) ? 0.0f : p_s[i];
        Vertex[i].v = (p_t == nullptr) ? 0.0f : 1 - p_t[i];
        Vertex[i].su = -1.0f;
        Vertex[i].sv = 0.0f;
    }

    const auto& face_index = Data["face"]->properties["vertex_indices"];
    Index.resize(face_index->size() * 3);

    for (size_t i = 0, iEnd = face_index->size(), nFace = 0; i < iEnd; i += 3)
    {
        //for (size_t j = 0; j < 3; ++j) // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        for (size_t j = 2; j + 1 > 0; --j) // 左手系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        {
            unsigned int index = face_index->at<unsigned int>(i + j);
            const size_t uvIndex = 2 * (i + j); // uとv 2つ * ( i + j )

            //Index[i + j] = index;   // 右手系(裏面は時計回り)
            Index[i + 2 - j] = index; // 左手系(裏面は反時計回り)

            // 三角形の2辺のベクトルの外積から法線を計算(法線がないとライトの光の反射方向をシェーディングできないから)
            if (j == 0 && !has_norm && Vertex[index].norm.x == 0.0f && Vertex[index].norm.y == 0.0f && Vertex[index].norm.z == 0.0f)
            {
                const unsigned int index0 = Index[i];
                const unsigned int index1 = Index[i + 1];
                const unsigned int index2 = Index[i + 2];

                // 外積 a×b cross_a_b を求める https://examist.jp/mathematics/space-vector/vector-gaiseki/
                const VECTOR a = VSub(Vertex[index1].pos, Vertex[index0].pos);
                const VECTOR b = VSub(Vertex[index2].pos, Vertex[index0].pos);
                // 左手系(表面は時計回り) 右手系と左手系では裏面の時計回りと反時計回りが変わる
                const VECTOR cross1_2 = VCross(a, b);
                // 右手系(表面は反時計回り)
                //const VECTOR cross2_1 = VCross(b, a);

                // 単位ベクトル(ノルム)に直す [単位ベクトル]は√(x*x + y*y + z*z) = 1となる
                const VECTOR norm1_2 = VNorm(cross1_2);
                //const VECTOR norm2_1 = VNorm(cross2_1);
                Vertex[index0].norm = norm1_2; Vertex[index1].norm = norm1_2; Vertex[index2].norm = norm1_2;
                //Vertex[index0].norm = norm2_1; Vertex[index1].norm = norm2_1; Vertex[index2].norm = norm2_1;
            }

            ++numIndex;
        }
    }


    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 200.0f; keyControlXYZ.y = 200.0f; keyControlXYZ.z = 0;
    VECTOR keyControlAngle = VGet(0.0f,0.0f,0.0f); // 回転

        // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen1 = SubScreen::Create()->Set(600, 450, 600, 100, 255, 0, "サブスクリーン1")->SetIsBarFrameShow(true,true);
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen2 = SubScreen::Create()->Set(200, 100, 150, 100, 255, 1, "サブスクリーン2\nバーを30で太めに")->SetFrame(1,GetColor(255,255,255),true,30);
    // サブのスクリーンを生成する 透明度を128にして半透明にしてみる
    std::shared_ptr<SubScreen> subscreen3 = SubScreen::Create()->Set(150, 450, 300, 100, 128, 1, "サブスクリーン3")->SetIsBarFrameShow(true)->SetBarColor(GetColor(0, 0, 255));
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen4 = SubScreen::Create()->Set(300, 100, 600, 100, 255, 1, "サブスクリーン4");


    float subscreenX = 0, subscreenY = 0; // 別描画スクリーンを描く位置
    int subscreenWidth = 600, subscreenHeight = 100;
    DxLib::SetDrawValidGraphCreateFlag(TRUE); // SetDrawScreen に引数として渡せる( 描画対象として使用できる )グラフィックハンドル作成を TRUE:許可
    // [別の描画スクリーンを用意] https://dxlib.xsrv.jp/function/dxfunc_graph1.html#R3N25
    int subscreen = DxLib::MakeScreen(subscreenWidth, subscreenHeight, FALSE);
    DxLib::SetDrawValidGraphCreateFlag(FALSE); // FLASE:通常のグラフィックハンドルを作成するモードに戻す


    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Input::Update(); // マウスのドラッグなどを更新
        SubScreen::UpdateScreens(); // スクリーンの位置やドラッグによる移動を更新する

       
        int MouseX, MouseY;
        // マウスの位置を取得
        GetMousePoint(&MouseX, &MouseY);
        subscreenX = MouseX; subscreenY = MouseY; // マウスのポインタの位置を起点にサブのスクリーンを描く


        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        VECTOR position = VGet(keyControlXYZ.x, keyControlXYZ.y, keyControlXYZ.z); // 3Dモデルの位置

        float width = imgWidth; float height = imgHeight; // テクスチャ画僧のサイズ
        VECTOR scale = VGet(50.0f, 50.0f, 50.0f); // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)とcキー(C)でx,y,z軸まわりに画像を回転
        if (PadInput & PAD_INPUT_A) keyControlAngle.x += speed;
        if (PadInput & PAD_INPUT_B) keyControlAngle.y += speed;
        if (PadInput & PAD_INPUT_C) keyControlAngle.z += speed;

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数
        const float xRotate = keyControlAngle.x * Deg2Rad;  const float yRotate = keyControlAngle.y * Deg2Rad; const float zRotate = keyControlAngle.z * Deg2Rad;


        MATRIX  matWorldDefault; // デフォルトのワールド行列
        DxLib::GetTransformToWorldMatrix(&matWorldDefault); // デフォルトのワールド行列を記憶しておく
        {
            //[ワールド行列] https://ny-program.hatenablog.com/entry/2019/09/15/114245
            MATRIX mat; // モデルのスケール×回転×移動を行列計算で設定する

            //[スケール]拡大縮小のスケール行列を得る
            mat = MGetScale(scale); // まずは拡大縮小のスケール行列からはじめる
            // [勉強]↓スケール行列は 斜めに x , y , zのスケールの倍率がならんでいる行列
            //mat.m[0][0] = scale.x;  mat.m[0][1] = 0.0f;    mat.m[0][2] = 0.0f;     mat.m[0][3] = 0.0f;
            //mat.m[1][0] = 0.0f;     mat.m[1][1] = scale.y; mat.m[1][2] = 0.0f;     mat.m[1][3] = 0.0f;
            //mat.m[2][0] = 0.0f;     mat.m[2][1] = 0.0f;    mat.m[2][2] = scale.z;  mat.m[2][3] = 0.0f;
            //mat.m[3][0] = 0.0f;     mat.m[3][1] = 0.0f;    mat.m[3][2] = 0.0f;     mat.m[3][3] = 1.0f;

            //[回転]
            MATRIX matRot;
            DxLib::CreateIdentityMatrix(&matRot);
            // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
            matRot = MMult(matRot, MGetRotZ(zRotate));
            matRot = MMult(matRot, MGetRotX(xRotate));
            matRot = MMult(matRot, MGetRotY(yRotate));
            mat = MMult(mat, matRot); //スケール × 回転

            //[移動]
            MATRIX matTrans = MGetTranslate(VGet(position.x, position.y, position.z)); // 位置positionをここで設定
            mat = MMult(mat, matTrans); //スケール×回転×移動

            DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4341

            DxLib::DrawPolygon32bitIndexed3D(Vertex.data(), Vertex.size(), Index.data(), numIndex / 3, texImage, FALSE); // 三角形ポリゴンをたくさん描画
        }
        DxLib::SetTransformToWorld(&matWorldDefault); // デフォルトのワールド行列に戻しておく

        DxLib::DrawFormatString(0, 0, GetColor(255, 255, 0), "x:%f y:%f Angle:%f %f %f", position.x, position.y, keyControlAngle.x, keyControlAngle.y, keyControlAngle.z);

        int tmpResetSetting = DxLib::GetUseSetDrawScreenSettingReset(); // 設定を一旦tmpに退避
        DxLib::SetUseSetDrawScreenSettingReset(FALSE); // SetDrawScreen切り替え時に設定をリセットしない(FALSE)(これをやらないとカメラ視点などがリセットされるから)
        int tmpDrawScreen = DxLib::GetDrawScreen(); // 一旦変更前のスクリーンのハンドルを退避
        DxLib::SetDrawScreen(subscreen);// ゲームを描く先を用意しておいたサブスクリーンに設定
        {   // (0,0)位置を起点に別 スクリーンへ文字列を描く
            DxLib::DrawString(0, 0, "サブのスクリーンを使ってWindowsみたいな自作ウインドウを描画してみる", GetColor(255, 255, 255));
        }
        DxLib::SetDrawScreen((tmpDrawScreen == -1) ? DX_SCREEN_BACK : tmpDrawScreen); // 描く先をもとのデフォルトのスクリーンに戻しておく
        DxLib::SetUseSetDrawScreenSettingReset((tmpResetSetting == -1) ? TRUE : tmpResetSetting); // 設定を元に戻す

        // 画面に別スクリーンの内容を描く
        DxLib::DrawRectExtendGraph((int)subscreenX, (int)subscreenY, (int)(subscreenX + subscreenWidth), (int)(subscreenY + subscreenHeight), 0, 0, subscreenWidth, subscreenHeight, subscreen, FALSE);
        // 別スクリーンの枠を描く
        DxLib::DrawBox( (int)((int)subscreenX - 1 + 0.5f),(int)((int)subscreenY - 1 + 0.5f),
                        (int)((int)(subscreenX + subscreenWidth + 1) + 0.5f), (int)((int)(subscreenY + subscreenHeight + 1) + 0.5f), GetColor(255,255,255), FALSE);


        subscreen1->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen1->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // サブの別のスクリーンの(0,0)位置を起点に文字列を描く
            DxLib::DrawString(0, 0, "サブのスクリーンを使ってWindowsみたいな自作ウインドウを描画してみる", GetColor(255, 255, 255));
        }
        subscreen1->DrawEnd();

        subscreen3->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen3->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // 半透明のスクリーンも描ける
            DxLib::DrawBox(0, 0, subscreen3->Width, subscreen3->Height, GetColor(0, 255, 0), TRUE);
            DxLib::DrawString(0, 0, "アルファを設定すれば\n半透明スクリーンも可能", GetColor(255, 255, 255));
        }
        subscreen3->DrawEnd();

        subscreen4->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen4->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // サブの別のスクリーンの(0,0)位置を起点に文字列を描く
            DxLib::DrawString(0, 0, "枠の無いスクリーンを使えば2Pの分割画面などにも応用できる", GetColor(255, 255, 255));
        }
        subscreen4->DrawEnd();



        SubScreen::DrawScreens(); // サブのスクリーンを全部描く


        ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    WaitKey();
    // DXライブラリの後始末
    DxLib_End();
    // ソフトの終了
    return 0;
}

いかがですか?ウィンドウズっぽいものが自作できたでしょうか?
ドラッグだけじゃなくバーの上でマウスのホイールをグリグリするとウィンドウがズームできる機能もおまけでついてます




エディタを自作して視点カメラを動かせるようにする


Singleton.hを新規作成して Editorクラスが唯一のシングルトンを継承できるように準備する。

#ifndef SINGLETON_H_
#define SINGLETON_H_

// テンプレートT型シングルトンSingleton<~> ~になんでも指定して唯一物にできる
// ★Singleton<T>型は【必ず唯一で複製不可】(使い方:どこからでもアクセスし変数を共有するクラス向き)
// ★ゲーム内の【どこからアクセスしても統一性が保たれる】(唯一だから)「クラス名と実体が一致」
template<class T>
class Singleton
{
public:
    // GetInstanceを通して【しか】T型クラスをつくれないうえに、
    // ★作ったものは【かならず唯一で複製不可】
    // それが【シングルトン型】!
    // ゲーム中で唯一で複製不可にしたい【ゲームを一律管理するクラス】などに最適!
    // https://teratail.com/questions/17416
    // http://rudora7.blog81.fc2.com/blog-entry-393.html
    // ②↓同じ関数の定義はプログラム全体で1つだけしか許されません。(static関数名は被らない)
    static inline T& GetInstance()
    {   //①↓関数の中で定義されたstatic変数は、関数の定義毎に1つ領域が確保されます。
        static T instance; //メモリ上にstatic変数確保(静かに常に待機するinstance)
        return instance; //①=(常駐)②=(唯一) ①と②両方満たすinstanceは【メモリ上に唯一】
    }

protected:
    Singleton() {} // 外部でのインスタンス作成は禁止(protected:内部公開、外部禁止)
    virtual ~Singleton() {} // 仮想デストラクタ(シングルトンを【継承するためには忘れちゃダメ】)

private:
    void operator=(const Singleton& obj) {} // 代入演算子禁止
    Singleton(const Singleton& obj) {} // コピーコンストラクタ禁止(コピーできない【唯一】)
    static T* instance; // private:だから外部アクセスできない【GetInstance()経由しか無理】
};

// ふだんcppで書くstatic変数初期化も★【Tテンプレートクラスだからすべて.hヘッダにまとめて書いた】
template< class T >
T* Singleton< T >::instance = 0; // static変数はclass内で初期化できないが、class外では = 0できる

#endif


Editor.hを新規作成して マウスの右ドラッグでカメラを回転、左ドラッグでカメラの視点を移動、
キーボードのT,Yキーでカメラz軸回転、キーボードのH,Jキーでカメラy軸回転、キーボードのV,Bキーでカメラx軸回転できるようにする。

#ifndef EDITOR_H_
#define EDITOR_H_

#include "DxLib.h"
#include "Input.h"
#include "Screen.h"

#include "Singleton.h"

class Editor : public Singleton<Editor>
{
public:

    struct Camera
    {
        float x = 0.f, y = 0.0f, z = -10.0f; // カメラはz軸マイナス位置からz軸プラス向きに原点を見る形で初期化される
        float VRotate = 0.0f, HRotate = 0.0f, TRotate = 0.0f; // X軸 V,Y軸 H,Z軸 T まわりのカメラのオイラー回転の角度
        inline Camera& operator +=(VECTOR move) { x += move.x; y += move.y; z += move.z; return *this; } // VECTOR型を足したときはカメラを移動させる
        float wheelSpeed = 1.0f; // マウスのホイールと連動した際のカメラのスピード

        // スクリーンの位置からカメラ画面の視界(ワールド座標)の奥への方向の単位ベクトル(ノルム)を求める
        VECTOR GetCameraWorldDirByScreenXY(float screenX = (float)Input::MouseX, float screenY = (float)Input::MouseY)
        {    // スクリーン(X,Y)からマウスの指すワールド座標(X,Y,Z)を求める
            VECTOR currentPos = DxLib::ConvScreenPosToWorldPos(VGet(screenX, screenY, 0.0f));
            // カメラからマウスの指す位置への方向の単位ベクトル(ノルム)を求める
            return DxLib::VNorm(DxLib::VGet(currentPos.x - x, currentPos.y - y, currentPos.z - z));
        }
    };
    Camera camera;

    int prevDragX = -1, prevDragY = -1; // 前フレームでのスクリーン上でのドラッグXY位置
   
    bool isInitFinished = false; // 一回でもInitされたかのフラグ
   
    void Init()
    {
        if (isInitFinished) return; // 最初の一回だけInit処理をするためのフラグ
        isInitFinished = true;
    }

    void UpdateKeyInput()
    {
        Init();

        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_V))
            camera.VRotate -= 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_B))
            camera.VRotate += 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_H))
            camera.HRotate -= 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_J))
            camera.HRotate += 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_T))
            camera.TRotate -= 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_Y))
            camera.TRotate += 1.0f;
    }

    void UpdateMouseInput()
    {
        Init();
        if (!SubScreen::isMouseOvered) // マウスオーバーしているSubscreenがないとき
        {
            if (Input::MouseWheel != 0.0f) // マウスのホイールをグリグリしたとき
            {
                // スクリーンの位置からカメラ画面の視界の奥への方向の単位ベクトル(ノルム)を求める
                VECTOR camNorm = camera.GetCameraWorldDirByScreenXY((float)Input::MouseX, (float)Input::MouseY);
                float camSpeed = camera.wheelSpeed;
                camera.wheelSpeed *= (camera.wheelSpeed < 100.0f) ? 1.5f : 1.0f; // カメラのズーム速度を加速する
                // 求めた単位ベクトル×カメラのスピードぶんカメラの位置を移動
                camera.x += camNorm.x * Input::MouseWheel * camSpeed;
                camera.y += camNorm.y * Input::MouseWheel * camSpeed;
                camera.z += camNorm.z * Input::MouseWheel * camSpeed;
            }
            else if (camera.wheelSpeed > 0.1f) camera.wheelSpeed *= 0.9f; // カメラのズーム速度を減衰する
            else camera.wheelSpeed = 0.1f; // カメラのズーム速度を減衰して最小0.1にすることで繊細なズーム調整もできるようにする


            if ((Input::Click[Mouse::DragL] || Input::Click[Mouse::DragR]) && Screen::IsInsideWindow())
            {
                if (prevDragX != -1 && prevDragY != -1)
                {   // 前の位置prevと現在の位置に移動があれば、その差ぶんだけ+=することでドラッグ移動を実現
                    if (Input::Click[Mouse::DragL]) // マウス左クリック
                    {
                        if (prevDragX != Input::Click[Mouse::DragLX] || prevDragY != Input::Click[Mouse::DragLY])
                        {    // マウスポインタがある画面上の座標に該当する3D空間上の座標を取得 https://dxlib.xsrv.jp/function/dxfunc_3d_camera.html#R12N11
                            float positionRatio = 0.9f;// 奥行の位置 Near 面 0.0f ~ 1.0f Far 面
                            VECTOR start = DxLib::ConvScreenPosToWorldPos(VGet((float)prevDragX, (float)prevDragY, positionRatio));
                            VECTOR end = DxLib::ConvScreenPosToWorldPos(VGet((float)Input::Click[Mouse::DragLX], (float)Input::Click[Mouse::DragLY], positionRatio));
                            float camSpeed = 400; // ドラッグに対するカメラの移動速度
                            camera.x -= (end.x - start.x) * camSpeed;
                            camera.y -= (end.y - start.y) * camSpeed;
                            camera.z -= (end.z - start.z) * camSpeed;
                        }
                    }
                    if (Input::Click[Mouse::DragR]) // マウス右クリック
                    {    // カメラの角度を変更
                        if (prevDragX != Input::Click[Mouse::DragRX])
                            camera.HRotate += (float)(Input::Click[Mouse::DragRX] - prevDragX);

                        if (prevDragY != Input::Click[Mouse::DragRY])
                            camera.VRotate += (float)(Input::Click[Mouse::DragRY] - prevDragY);

                        // ±360度を超えたら割り算の余りを設定して 例.365度 → 5度に戻してやる
                        if (camera.HRotate > 360.0f || camera.HRotate < -360.0f) camera.HRotate = std::fmodf(camera.HRotate, 360.0f);
                        if (camera.VRotate > 360.0f || camera.VRotate < -360.0f) camera.VRotate = std::fmodf(camera.VRotate, 360.0f);
                    }
                }

                prevDragX = (Input::Click[Mouse::DragL]) ? Input::Click[Mouse::DragLX] : Input::Click[Mouse::DragRX];
                prevDragY = (Input::Click[Mouse::DragL]) ? Input::Click[Mouse::DragLY] : Input::Click[Mouse::DragRY];
                return;
            }
            else
            {
                prevDragX = -1; prevDragY = -1; // マウスのドラッグがないときはリセット
            }
        }
    }

    void UpdateCamera()
    {
        Init();

        // カメラの位置と回転値をセット、カメラの位置は原点
        DxLib::SetCameraPositionAndAngle(
            DxLib::VGet(camera.x, camera.y, camera.z),
            camera.VRotate * DX_PI_F / 180.0f, camera.HRotate * DX_PI_F / 180.0f, camera.TRotate * DX_PI_F / 180.0f);
    }

    // まとめてUpdateしたいときはUpdateを使い、個別にUIやKeyやMouseを別々にタイミングを取りながらUpdateしたいときは Updateは呼ばない
    void Update()
    {
        Init();
        UpdateKeyInput();
        UpdateMouseInput();
        UpdateCamera();
    }

    void Draw()
    {
        // 原点からx軸(R:赤) y軸(G:緑) z軸(B:青)をライン表示する
        DxLib::DrawLine3D(VGet(0, 0, 0), VGet(1.0f * 5.0f, 0.0f * 5.0f, 0.0f * 5.0f), GetColor(255, 0, 0));
        DxLib::DrawLine3D(VGet(0, 0, 0), VGet(0.0f * 5.0f, 1.0f * 5.0f, 0.0f * 5.0f), GetColor(0, 255, 0));
        DxLib::DrawLine3D(VGet(0, 0, 0), VGet(0.0f * 5.0f, 0.0f * 5.0f, 1.0f * 5.0f), GetColor(0, 0, 255));
        // 画面に回転量を描画
        DxLib::DrawFormatString(0, 0, DxLib::GetColor(255, 255, 255), "Cam(x:%f y:%f z:%f)", camera.x, camera.y, camera.z);
        DxLib::DrawFormatString(0, 20, DxLib::GetColor(255, 255, 255), "Rot(V:%f H:%f T:%f)", camera.VRotate, camera.HRotate, camera.TRotate);
    }

};

#endif


main.cppのコードにEditorクラスを使ってカメラの視点を移動、回転させてみま しょう。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include "Input.h"
#include "Screen.h"
#include "Ply.h"

#include "Editor.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズWidth×Heightのカラービット数32ビットで起動
    SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    SetDrawScreen(DX_SCREEN_BACK);

    // ゲームのwhileループを開始する前の初期化処理
    int texImage = -1;
    texImage = LoadGraph("Image/dice.png");
    assert(texImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる
    float imgWidth, imgHeight;
    GetGraphSizeF(texImage, &imgWidth, &imgHeight); // テクスチャ画像のサイズを得る

    Ply::Data Data; // .plyのデータのクラス
    Ply::load("Image/dice.ply", Data); // .plyデータをロード 軸を変えてdice1.plyや dice2.plyなどいろいろ試してみよう

    size_t numIndex = 0; // インデックスの数
    std::vector<uint32_t> Index; // 3Dモデルのポリゴンのインデックス配列
    std::vector<VERTEX3D> Vertex; // 3Dモデルのポリゴンの頂点配列

    auto vertex = Data["vertex"]; // 頂点へのアクセス
    assert(vertex->size() != 0 && "Plyデータの頂点サイズが0です。ファイルのパスは大丈夫ですか?");
    Vertex.resize(vertex->size()); // 配列の数の事前確保

    // [.plyの頂点のプロパティ情報] https://jp.mathworks.com/help/vision/ug/the-ply-format.html
    const float* p_x = vertex->properties["x"]->ptr<float>();
    const float* p_y = vertex->properties["y"]->ptr<float>();
    const float* p_z = vertex->properties["z"]->ptr<float>();
    const float* p_nx = (vertex->properties.has_key("nx")) ? vertex->properties["nx"]->ptr<float>() : nullptr;
    const float* p_ny = (vertex->properties.has_key("ny")) ? vertex->properties["ny"]->ptr<float>() : nullptr;
    const float* p_nz = (vertex->properties.has_key("nz")) ? vertex->properties["nz"]->ptr<float>() : nullptr;
    bool has_norm = !(p_nx == nullptr || p_ny == nullptr || p_nz == nullptr);
    const float* p_s = (vertex->properties.has_key("s")) ? vertex->properties["s"]->ptr<float>() : nullptr; // Blender書き出しでは(u,v)ではなく(s,t)として書き出されている
    const float* p_t = (vertex->properties.has_key("t")) ? vertex->properties["t"]->ptr<float>() : nullptr;

    const unsigned char* p_R = (vertex->properties.has_key("red")) ? vertex->properties["red"]->ptr<unsigned char>()
        : (vertex->properties.has_key("r")) ? vertex->properties["r"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_G = (vertex->properties.has_key("green")) ? vertex->properties["green"]->ptr<unsigned char>()
        : (vertex->properties.has_key("g")) ? vertex->properties["g"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_B = (vertex->properties.has_key("blue")) ? vertex->properties["blue"]->ptr<unsigned char>()
        : (vertex->properties.has_key("b")) ? vertex->properties["b"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_A = (vertex->properties.has_key("alpha")) ? vertex->properties["alpha"]->ptr<unsigned char>()
        : (vertex->properties.has_key("a")) ? vertex->properties["a"]->ptr<unsigned char>() : nullptr;
    bool has_color = !(p_R == nullptr || p_G == nullptr || p_B == nullptr || p_A == nullptr);

    for (size_t i = 0, iEnd = Vertex.size(); i < iEnd; i += 1)
    {
        // 頂点のデータをセット 各ツールの系一覧 https://zenn.dev/it_ks/articles/cbe27860548ea1
        //Vertex[i].pos = VGet(p_x[i], p_y[i], p_z[i]); // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        //Vertex[i].pos = VGet(p_x[i], -p_y[i], p_z[i]); // 右手Y↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        //Vertex[i].norm = (has_norm) ? VNorm(VGet(p_nx[i], -p_ny[i], p_nz[i])) : VGet(0.0f, 0.0f, 0.0f);

        // 右手Z↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        Vertex[i].pos = VGet(-(p_y[i]), p_z[i], p_x[i]);
        Vertex[i].norm = (has_norm) ? VNorm(VGet(-p_ny[i], p_nz[i], p_nx[i])) : VGet(0.0f, 0.0f, 0.0f);
        Vertex[i].dif = (has_color) ? GetColorU8(p_R[i], p_G[i], p_B[i], p_A[i]) : GetColorU8(255, 255, 255, 255);
        Vertex[i].spc = GetColorU8(255, 255, 255, 255);
        Vertex[i].u = (p_s == nullptr) ? 0.0f : p_s[i];
        Vertex[i].v = (p_t == nullptr) ? 0.0f : 1 - p_t[i];
        Vertex[i].su = -1.0f;
        Vertex[i].sv = 0.0f;
    }

    const auto& face_index = Data["face"]->properties["vertex_indices"];
    Index.resize(face_index->size() * 3);

    for (size_t i = 0, iEnd = face_index->size(), nFace = 0; i < iEnd; i += 3)
    {
        //for (size_t j = 0; j < 3; ++j) // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        for (size_t j = 2; j + 1 > 0; --j) // 左手系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        {
            unsigned int index = face_index->at<unsigned int>(i + j);
            const size_t uvIndex = 2 * (i + j); // uとv 2つ * ( i + j )

            //Index[i + j] = index;   // 右手系(裏面は時計回り)
            Index[i + 2 - j] = index; // 左手系(裏面は反時計回り)

            // 三角形の2辺のベクトルの外積から法線を計算(法線がないとライトの光の反射方向をシェーディングできないから)
            if (j == 0 && !has_norm && Vertex[index].norm.x == 0.0f && Vertex[index].norm.y == 0.0f && Vertex[index].norm.z == 0.0f)
            {
                const unsigned int index0 = Index[i];
                const unsigned int index1 = Index[i + 1];
                const unsigned int index2 = Index[i + 2];

                // 外積 a×b cross_a_b を求める https://examist.jp/mathematics/space-vector/vector-gaiseki/
                const VECTOR a = VSub(Vertex[index1].pos, Vertex[index0].pos);
                const VECTOR b = VSub(Vertex[index2].pos, Vertex[index0].pos);
                // 左手系(表面は時計回り) 右手系と左手系では裏面の時計回りと反時計回りが変わる
                const VECTOR cross1_2 = VCross(a, b);
                // 右手系(表面は反時計回り)
                //const VECTOR cross2_1 = VCross(b, a);

                // 単位ベクトル(ノルム)に直す [単位ベクトル]は√(x*x + y*y + z*z) = 1となる
                const VECTOR norm1_2 = VNorm(cross1_2);
                //const VECTOR norm2_1 = VNorm(cross2_1);
                Vertex[index0].norm = norm1_2; Vertex[index1].norm = norm1_2; Vertex[index2].norm = norm1_2;
                //Vertex[index0].norm = norm2_1; Vertex[index1].norm = norm2_1; Vertex[index2].norm = norm2_1;
            }

            ++numIndex;
        }
    }


    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 0.0f; keyControlXYZ.y = 0.0f; keyControlXYZ.z = 0;
    VECTOR keyControlAngle = VGet(0.0f,0.0f,0.0f); // 回転

        // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen1 = SubScreen::Create()->Set(600, 450, 600, 100, 255, 0, "サブスクリーン1")->SetIsBarFrameShow(true,true);
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen2 = SubScreen::Create()->Set(200, 100, 150, 100, 255, 1, "サブスクリーン2\nバーを30で太めに")->SetFrame(1,GetColor(255,255,255),true,30);
    // サブのスクリーンを生成する 透明度を128にして半透明にしてみる
    std::shared_ptr<SubScreen> subscreen3 = SubScreen::Create()->Set(150, 450, 300, 100, 128, 1, "サブスクリーン3")->SetIsBarFrameShow(true)->SetBarColor(GetColor(0, 0, 255));
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen4 = SubScreen::Create()->Set(300, 100, 600, 100, 255, 1, "サブスクリーン4");

    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    Editor& editor = Editor::GetInstance(); // エディタのシングルトン参照を得る

    //奥行0.1~5000までをカメラの描画範囲とする
    DxLib::SetCameraNearFar(0.1f, 5000.0f);


    ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Input::Update(); // マウスのドラッグなどを更新
        SubScreen::UpdateScreens(); // スクリーンの位置やドラッグによる移動を更新する

        editor.UpdateKeyInput(); // エディタのキーのインプットを更新

        editor.UpdateMouseInput(); // エディタのマウスのインプットを更新
        editor.UpdateCamera(); // エディタのカメラを更新

       
        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        VECTOR position = VGet(keyControlXYZ.x, keyControlXYZ.y, keyControlXYZ.z); // 3Dモデルの位置

        float width = imgWidth; float height = imgHeight; // テクスチャ画像のサイズ
        VECTOR scale = VGet(1.0f, 1.0f, 1.0f); // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)とcキー(C)でx,y,z軸まわりに画像を回転
        if (PadInput & PAD_INPUT_A) keyControlAngle.x += speed;
        if (PadInput & PAD_INPUT_B) keyControlAngle.y += speed;
        if (PadInput & PAD_INPUT_C) keyControlAngle.z += speed;

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数
        const float xRotate = keyControlAngle.x * Deg2Rad;  const float yRotate = keyControlAngle.y * Deg2Rad; const float zRotate = keyControlAngle.z * Deg2Rad;


        MATRIX  matWorldDefault; // デフォルトのワールド行列
        DxLib::GetTransformToWorldMatrix(&matWorldDefault); // デフォルトのワールド行列を記憶しておく
        {
            //[ワールド行列] https://ny-program.hatenablog.com/entry/2019/09/15/114245
            MATRIX mat; // モデルのスケール×回転×移動を行列計算で設定する

            //[スケール]拡大縮小のスケール行列を得る
            mat = MGetScale(scale); // まずは拡大縮小のスケール行列からはじめる
            // [勉強]↓スケール行列は 斜めに x , y , zのスケールの倍率がならんでいる行列
            //mat.m[0][0] = scale.x;  mat.m[0][1] = 0.0f;    mat.m[0][2] = 0.0f;     mat.m[0][3] = 0.0f;
            //mat.m[1][0] = 0.0f;     mat.m[1][1] = scale.y; mat.m[1][2] = 0.0f;     mat.m[1][3] = 0.0f;
            //mat.m[2][0] = 0.0f;     mat.m[2][1] = 0.0f;    mat.m[2][2] = scale.z;  mat.m[2][3] = 0.0f;
            //mat.m[3][0] = 0.0f;     mat.m[3][1] = 0.0f;    mat.m[3][2] = 0.0f;     mat.m[3][3] = 1.0f;

            //[回転]
            MATRIX matRot;
            DxLib::CreateIdentityMatrix(&matRot);
            // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
            matRot = MMult(matRot, MGetRotZ(zRotate));
            matRot = MMult(matRot, MGetRotX(xRotate));
            matRot = MMult(matRot, MGetRotY(yRotate));
            mat = MMult(mat, matRot); //スケール × 回転

            //[移動]
            MATRIX matTrans = MGetTranslate(VGet(position.x, position.y, position.z)); // 位置positionをここで設定
            mat = MMult(mat, matTrans); //スケール×回転×移動

            DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4341

            DxLib::DrawPolygon32bitIndexed3D(Vertex.data(), Vertex.size(), Index.data(), numIndex / 3, texImage, FALSE); // 三角形ポリゴンをたくさん描画
        }
        DxLib::SetTransformToWorld(&matWorldDefault); // デフォルトのワールド行列に戻しておく

        DxLib::DrawFormatString(0, 100, GetColor(255, 255, 0), "x:%f y:%f Angle:%f %f %f", position.x, position.y, keyControlAngle.x, keyControlAngle.y, keyControlAngle.z);

        subscreen1->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen1->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // サブの別のスクリーンの(0,0)位置を起点に文字列を描く
            DxLib::DrawString(0, 0, "サブのスクリーンを使ってWindowsみたいな自作ウインドウを描画してみる", GetColor(255, 255, 255));
        }
        subscreen1->DrawEnd();

        subscreen3->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen3->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // 半透明のスクリーンも描ける
            DxLib::DrawBox(0, 0, subscreen3->Width, subscreen3->Height, GetColor(0, 255, 0), TRUE);
            DxLib::DrawString(0, 0, "アルファを設定すれば\n半透明スクリーンも可能", GetColor(255, 255, 255));
        }
        subscreen3->DrawEnd();

        subscreen4->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen4->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // サブの別のスクリーンの(0,0)位置を起点に文字列を描く
            DxLib::DrawString(0, 0, "枠の無いスクリーンを使えば2Pの分割画面などにも応用できる", GetColor(255, 255, 255));
        }
        subscreen4->DrawEnd();


        SubScreen::DrawScreens(); // サブのスクリーンを全部描く

        editor.Draw(); // エディタのDraw処理

        ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    WaitKey();
    // DXライブラリの後始末
    DxLib_End();
    // ソフトの終了
    return 0;
}



いかがでしょうか?カメラの視点の位置をマウスの左ドラッグで移動、右ドラッグで回転、
キーボードのT,Yキーでカメラz軸回転、キーボードのH,Jキーでカメラy軸回転、キーボードのV,Bキーでカメラx軸回転できるようになりましたか?




キーボードのWSキーでカメラ視点を手前や奥に移動でき るようにする

Editor.hを変更してキーボードのWSキーでカメラ視点を手前や奥に移動できるようにします。

#ifndef EDITOR_H_
#define EDITOR_H_

#include "DxLib.h"
#include "Input.h"
#include "Screen.h"

#include "Singleton.h"

class Editor : public Singleton<Editor>
{
public:

    struct Camera
    {
        float x = 0.f, y = 0.0f, z = -10.0f; // カメラはz軸マイナス位置からz軸プラス向きに原点を見る形で初期化される
        inline Camera& operator +=(VECTOR move) { x += move.x; y += move.y; z += move.z; return *this; } // VECTOR型を足したときはカメラを移動させる
        float VRotate = 0.0f, HRotate = 0.0f, TRotate = 0.0f; // X軸 V,Y軸 H,Z軸 T まわりのカメラのオイラー回転の角度
        float wheelSpeed = 1.0f; // マウスのホイールと連動した際のカメラのスピード

        // スクリーンの位置からカメラ画面の視界(ワールド座標)の奥への方向の単位ベクトル(ノルム) を求める
        VECTOR GetCameraWorldDirByScreenXY(float screenX = (float)Input::MouseX, float screenY = (float)Input::MouseY)
        {    // スクリーン(X,Y)からマウスの指すワールド座標(X,Y,Z)を求める
            VECTOR currentPos = DxLib::ConvScreenPosToWorldPos(VGet(screenX, screenY, 0.0f));
            // カメラからマウスの指す位置への方向の単位ベクトル(ノルム)を求める
            return DxLib::VNorm(DxLib::VGet(currentPos.x - x, currentPos.y - y, currentPos.z - z));
        }
    };
    Camera camera;

    int prevDragX = -1, prevDragY = -1; // 前フレームでのスクリーン上でのドラッグXY位置
   
    bool isInitFinished = false; // 一回でもInitされたかのフラグ
   
    void Init()
    {
        if (isInitFinished) return; // 最初の一回だけInit処理をするためのフラグ
        isInitFinished = true;
    }

    void UpdateKeyInput()
    {
        Init();

        float camXYZSpeed = 1.0f;
        // スクリーンの位置からカメラ画面の視界(ワールド座標)の奥への方向のの単位ベクトル(ノルム)を求める
        VECTOR camNorm = camera.GetCameraWorldDirByScreenXY((float)Input::MouseX, (float)Input::MouseY);
        // キーボードのWSキーでカメラ視点を手前や奥に移動できるようにする
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_W))
            camera += VGet(camNorm.x * camXYZSpeed, camNorm.y * camXYZSpeed, camNorm.z * camXYZSpeed);
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_S))
            camera += VGet(-camNorm.x * camXYZSpeed, -camNorm.y * camXYZSpeed, -camNorm.z * camXYZSpeed);


        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_V))
            camera.VRotate -= 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_B))
            camera.VRotate += 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_H))
            camera.HRotate -= 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_J))
            camera.HRotate += 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_T))
            camera.TRotate -= 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_Y))
            camera.TRotate += 1.0f;
    }

    void UpdateMouseInput()
    {
        Init();
        if (!SubScreen::isMouseOvered) // マウスオーバーしているSubscreenがないとき
        {
            if (Input::MouseWheel != 0.0f) // マウスのホイールをグリグリしたとき
            {
                // スクリーンの位置からカメラ画面の視界の奥への方向の単位ベクトル(ノルム)を求める
                VECTOR camNorm = camera.GetCameraWorldDirByScreenXY((float)Input::MouseX, (float)Input::MouseY);
                float camSpeed = camera.wheelSpeed;
                camera.wheelSpeed *= (camera.wheelSpeed < 100.0f) ? 1.5f : 1.0f; // カメラのズーム速度を加速する
                // 求めた単位ベクトル×カメラのスピードぶんカメラの位置を移動
                camera.x += camNorm.x * Input::MouseWheel * camSpeed;
                camera.y += camNorm.y * Input::MouseWheel * camSpeed;
                camera.z += camNorm.z * Input::MouseWheel * camSpeed;
            }
            else if (camera.wheelSpeed > 0.1f) camera.wheelSpeed *= 0.9f; // カメラのズーム速度を減衰する
            else camera.wheelSpeed = 0.1f; // カメラのズーム速度を減衰して最小0.1にすることで繊細なズーム調整もできるようにする


            if ((Input::Click[Mouse::DragL] || Input::Click[Mouse::DragR]) && Screen::IsInsideWindow())
            {
                if (prevDragX != -1 && prevDragY != -1)
                {   // 前の位置prevと現在の位置に移動があれば、その差ぶんだけ+=することでドラッグ移動を実現
                    if (Input::Click[Mouse::DragL]) // マウス左クリック
                    {
                        if (prevDragX != Input::Click[Mouse::DragLX] || prevDragY != Input::Click[Mouse::DragLY])
                        {    // マウスポインタがある画面上の座標に該当する3D空間上の座標を取得 https://dxlib.xsrv.jp/function/dxfunc_3d_camera.html#R12N11
                            float positionRatio = 0.9f;// 奥行の位置 Near 面 0.0f ~ 1.0f Far 面
                            VECTOR start = DxLib::ConvScreenPosToWorldPos(VGet((float)prevDragX, (float)prevDragY, positionRatio));
                            VECTOR end = DxLib::ConvScreenPosToWorldPos(VGet((float)Input::Click[Mouse::DragLX], (float)Input::Click[Mouse::DragLY], positionRatio));
                            float camSpeed = 400; // ドラッグに対するカメラの移動速度
                            camera.x -= (end.x - start.x) * camSpeed;
                            camera.y -= (end.y - start.y) * camSpeed;
                            camera.z -= (end.z - start.z) * camSpeed;
                        }
                    }
                    if (Input::Click[Mouse::DragR]) // マウス右クリック
                    {    // カメラの角度を変更
                        if (prevDragX != Input::Click[Mouse::DragRX])
                            camera.HRotate += (float)(Input::Click[Mouse::DragRX] - prevDragX);

                        if (prevDragY != Input::Click[Mouse::DragRY])
                            camera.VRotate += (float)(Input::Click[Mouse::DragRY] - prevDragY);

                        // ±360度を超えたら割り算の余りを設定して 例.365度 → 5度に戻してやる
                        if (camera.HRotate > 360.0f || camera.HRotate < -360.0f) camera.HRotate = std::fmodf(camera.HRotate, 360.0f);
                        if (camera.VRotate > 360.0f || camera.VRotate < -360.0f) camera.VRotate = std::fmodf(camera.VRotate, 360.0f);
                    }
                }

                prevDragX = (Input::Click[Mouse::DragL]) ? Input::Click[Mouse::DragLX] : Input::Click[Mouse::DragRX];
                prevDragY = (Input::Click[Mouse::DragL]) ? Input::Click[Mouse::DragLY] : Input::Click[Mouse::DragRY];
                return;
            }
            else
            {
                prevDragX = -1; prevDragY = -1; // マウスのドラッグがないときはリセット
            }
        }
    }

    void UpdateCamera()
    {
        Init();

        // カメラの位置と回転値をセット、カメラの位置は原点
        DxLib::SetCameraPositionAndAngle(
            DxLib::VGet(camera.x, camera.y, camera.z),
            camera.VRotate * DX_PI_F / 180.0f, camera.HRotate * DX_PI_F / 180.0f, camera.TRotate * DX_PI_F / 180.0f);
    }

    // まとめてUpdateしたいときはUpdateを使い、個別にUIやKeyやMouseを別々にタイミングを取りながらUpdateしたいときは Updateは呼ばない
    void Update()
    {
        Init();
        UpdateKeyInput();
        UpdateMouseInput();
        UpdateCamera();
    }

    void Draw()
    {
        // 原点からx軸(R:赤) y軸(G:緑) z軸(B:青)をライン表示する
        DxLib::DrawLine3D(VGet(0, 0, 0), VGet(1.0f * 5.0f, 0.0f * 5.0f, 0.0f * 5.0f), GetColor(255, 0, 0));
        DxLib::DrawLine3D(VGet(0, 0, 0), VGet(0.0f * 5.0f, 1.0f * 5.0f, 0.0f * 5.0f), GetColor(0, 255, 0));
        DxLib::DrawLine3D(VGet(0, 0, 0), VGet(0.0f * 5.0f, 0.0f * 5.0f, 1.0f * 5.0f), GetColor(0, 0, 255));
        // 画面に回転量を描画
        DxLib::DrawFormatString(0, 0, DxLib::GetColor(255, 255, 255), "Cam(x:%f y:%f z:%f)", camera.x, camera.y, camera.z);
        DxLib::DrawFormatString(0, 20, DxLib::GetColor(255, 255, 255), "Rot(V:%f H:%f T:%f)", camera.VRotate, camera.HRotate, camera.TRotate);
    }

};

#endif

いかがでしょうか?カメラの視点の位置をキーボードのWSキーでカメラ視点を手前や奥に移動できるようになりましたか?
重要なのはGetCameraWorldDirByScreenXY関数でカメラ画面の視界(ワールド座標)の奥への方向のの単位ベクトル(ノルム)を求めることです
カメラの視点を移動するときはXYZ軸に対して+1するようなシンプルな計算ではうまくいきません
あてになるのは「カメラから見た奥行方向」の大きさ1の単位ベクトルです。このベクトルを+1方向で奥、-1方向で手前にカメラを移動できます
GetCameraWorldDirByScreenXY関数の中では DxLib::ConvScreenPosToWorldPos を使って
スクリーンの2D(X,Y)からマウスの指すワールド座標3D(X,Y,Z)を求めています
スクリーンの2D位置からワールドの3D位置を変換する処理が重 要なキーとなっているのでこの関数の存在は知っておきましょう




キーボードのDAキーとEQキーでカメラ視点を左右や上 下に移動できるようにする

Editor.hを変更してキーボードのDAキーとEQキーでカメラ視点を左右や上下に移動できるようにします。

#ifndef EDITOR_H_
#define EDITOR_H_

#include "DxLib.h"
#include "Input.h"
#include "Screen.h"

#include "Singleton.h"

class Editor : public Singleton<Editor>
{
public:

    struct Camera
    {
        float x = 0.f, y = 0.0f, z = -10.0f; // カメラはz軸マイナス位置からz軸プラス向きに原点を見る形で初期化される
        inline Camera& operator +=(VECTOR move) { x += move.x; y += move.y; z += move.z; return *this; } // VECTOR型を足したときはカメラを移動させる
        float VRotate = 0.0f, HRotate = 0.0f, TRotate = 0.0f; // X軸 V,Y軸 H,Z軸 T まわりのカメラのオイラー回転の角度
        float wheelSpeed = 1.0f; // マウスのホイールと連動した際のカメラのスピード

        // スクリーンの位置からカメラ画面の視界(ワールド座標)の奥への方向の単位ベクトル(ノルム)を求める
        VECTOR GetCameraWorldDirByScreenXY(float screenX = (float)Input::MouseX, float screenY = (float)Input::MouseY)
        {    // スクリーン(X,Y)からマウスの指すワールド座標(X,Y,Z)を求める
            VECTOR currentPos = DxLib::ConvScreenPosToWorldPos(VGet(screenX, screenY, 0.0f));
            // カメラからマウスの指す位置への方向の単位ベクトル(ノルム)を求める
            return DxLib::VNorm(DxLib::VGet(currentPos.x - x, currentPos.y - y, currentPos.z - z));
        }

        // スクリーンの2Dの start位置XY から end位置XY の方向 が ワールド座標3D においてどんなベクトルになるかを求める
        VECTOR GetWorldDirByCameraScreenXYDir(float screenStartX = (float)Input::MouseX, float screenStartY = (float)Input::MouseY,
            float screenEndX = (float)(Input::MouseX + 1), float screenEndY = (float)(Input::MouseY + 1))
        {    // スクリーン(X,Y)からマウスの指すワールド座標(X,Y,Z)を求める
            VECTOR startPos = DxLib::ConvScreenPosToWorldPos(VGet(screenStartX, screenStartY, 0.0f));
            VECTOR endPos = DxLib::ConvScreenPosToWorldPos(VGet(screenEndX, screenEndY, 0.0f));
            // ワールド座標におけるstartからendへ向かう方向のベクトルを求める
            return DxLib::VGet(endPos.x - startPos.x, endPos.y - startPos.y, endPos.z - startPos.z);
        }

    };
    Camera camera;

    int prevDragX = -1, prevDragY = -1; // 前フレームでのスクリーン上でのドラッグXY位置
   
    bool isInitFinished = false; // 一回でもInitされたかのフラグ
   
    void Init()
    {
        if (isInitFinished) return; // 最初の一回だけInit処理をするためのフラグ
        isInitFinished = true;
    }

    void UpdateKeyInput()
    {
        Init();

        float camXYZSpeed = 1.0f;
        // スクリーンの位置からカメラ画面の視界(ワールド座標)の奥への方向のの単位ベクトル(ノルム)を求める
        VECTOR camNorm = camera.GetCameraWorldDirByScreenXY((float)Input::MouseX, (float)Input::MouseY);
        // キーボードのWSキーでカメラ視点を手前や奥に移動できるようにする
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_W))
            camera += VGet(camNorm.x * camXYZSpeed, camNorm.y * camXYZSpeed, camNorm.z * camXYZSpeed);
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_S))
            camera += VGet(-camNorm.x * camXYZSpeed, -camNorm.y * camXYZSpeed, -camNorm.z * camXYZSpeed);

        // スクリーンの 2D上のX方向 が ワールド座標 3D ではどんな方向ベクトルになるかを求める
        VECTOR xCamDir = VNorm(camera.GetWorldDirByCameraScreenXYDir((float)Input::MouseX, (float)Input::MouseY, (float)(Input::MouseX + 10), (float)Input::MouseY));
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_D))
            camera += VGet(xCamDir.x * camXYZSpeed, xCamDir.y * camXYZSpeed, xCamDir.z * camXYZSpeed);
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_A))
            camera += VGet(-xCamDir.x * camXYZSpeed, -xCamDir.y * camXYZSpeed, -xCamDir.z * camXYZSpeed);

       
        // スクリーンの 2D上のY方向 が ワールド座標 3D ではどんな方向ベクトルになるかを求める
        VECTOR yCamDir = VNorm(camera.GetWorldDirByCameraScreenXYDir((float)Input::MouseX, (float)Input::MouseY, (float)Input::MouseX, (float)(Input::MouseY + 10)));
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_Q))
            camera += VGet(yCamDir.x * camXYZSpeed, yCamDir.y * camXYZSpeed, yCamDir.z * camXYZSpeed);
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_E))
            camera += VGet(-yCamDir.x * camXYZSpeed, -yCamDir.y * camXYZSpeed, -yCamDir.z * camXYZSpeed);

       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_V))
            camera.VRotate -= 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_B))
            camera.VRotate += 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_H))
            camera.HRotate -= 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_J))
            camera.HRotate += 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_T))
            camera.TRotate -= 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_Y))
            camera.TRotate += 1.0f;
    }

    void UpdateMouseInput()
    {
        Init();
        if (!SubScreen::isMouseOvered) // マウスオーバーしているSubscreenがないとき
        {
            if (Input::MouseWheel != 0.0f) // マウスのホイールをグリグリしたとき
            {
                // スクリーンの位置からカメラ画面の視界の奥への方向の単位ベクトル(ノルム)を求める
                VECTOR camNorm = camera.GetCameraWorldDirByScreenXY((float)Input::MouseX, (float)Input::MouseY);
                float camSpeed = camera.wheelSpeed;
                camera.wheelSpeed *= (camera.wheelSpeed < 100.0f) ? 1.5f : 1.0f; // カメラのズーム速度を加速する
                // 求めた単位ベクトル×カメラのスピードぶんカメラの位置を移動
                camera.x += camNorm.x * Input::MouseWheel * camSpeed;
                camera.y += camNorm.y * Input::MouseWheel * camSpeed;
                camera.z += camNorm.z * Input::MouseWheel * camSpeed;
            }
            else if (camera.wheelSpeed > 0.1f) camera.wheelSpeed *= 0.9f; // カメラのズーム速度を減衰する
            else camera.wheelSpeed = 0.1f; // カメラのズーム速度を減衰して最小0.1にすることで繊細なズーム調整もできるようにする


            if ((Input::Click[Mouse::DragL] || Input::Click[Mouse::DragR]) && Screen::IsInsideWindow())
            {
                if (prevDragX != -1 && prevDragY != -1)
                {   // 前の位置prevと現在の位置に移動があれば、その差ぶんだけ+=することでドラッグ移動を実現
                    if (Input::Click[Mouse::DragL]) // マウス左クリック
                    {
                        if (prevDragX != Input::Click[Mouse::DragLX] || prevDragY != Input::Click[Mouse::DragLY])
                        {    // マウスポインタがある画面上の座標に該当する3D空間上の座標を取得 https://dxlib.xsrv.jp/function/dxfunc_3d_camera.html#R12N11
                            float positionRatio = 0.9f;// 奥行の位置 Near 面 0.0f ~ 1.0f Far 面
                            VECTOR start = DxLib::ConvScreenPosToWorldPos(VGet((float)prevDragX, (float)prevDragY, positionRatio));
                            VECTOR end = DxLib::ConvScreenPosToWorldPos(VGet((float)Input::Click[Mouse::DragLX], (float)Input::Click[Mouse::DragLY], positionRatio));
                            float camSpeed = 400; // ドラッグに対するカメラの移動速度
                            camera.x -= (end.x - start.x) * camSpeed;
                            camera.y -= (end.y - start.y) * camSpeed;
                            camera.z -= (end.z - start.z) * camSpeed;
                        }
                    }
                    if (Input::Click[Mouse::DragR]) // マウス右クリック
                    {    // カメラの角度を変更
                        if (prevDragX != Input::Click[Mouse::DragRX])
                            camera.HRotate += (float)(Input::Click[Mouse::DragRX] - prevDragX);

                        if (prevDragY != Input::Click[Mouse::DragRY])
                            camera.VRotate += (float)(Input::Click[Mouse::DragRY] - prevDragY);

                        // ±360度を超えたら割り算の余りを設定して 例.365度 → 5度に戻してやる
                        if (camera.HRotate > 360.0f || camera.HRotate < -360.0f) camera.HRotate = std::fmodf(camera.HRotate, 360.0f);
                        if (camera.VRotate > 360.0f || camera.VRotate < -360.0f) camera.VRotate = std::fmodf(camera.VRotate, 360.0f);
                    }
                }

                prevDragX = (Input::Click[Mouse::DragL]) ? Input::Click[Mouse::DragLX] : Input::Click[Mouse::DragRX];
                prevDragY = (Input::Click[Mouse::DragL]) ? Input::Click[Mouse::DragLY] : Input::Click[Mouse::DragRY];
                return;
            }
            else
            {
                prevDragX = -1; prevDragY = -1; // マウスのドラッグがないときはリセット
            }
        }
    }

    void UpdateCamera()
    {
        Init();

        // カメラの位置と回転値をセット、カメラの位置は原点
        DxLib::SetCameraPositionAndAngle(
            DxLib::VGet(camera.x, camera.y, camera.z),
            camera.VRotate * DX_PI_F / 180.0f, camera.HRotate * DX_PI_F / 180.0f, camera.TRotate * DX_PI_F / 180.0f);
    }

    // まとめてUpdateしたいときはUpdateを使い、個別にUIやKeyやMouseを別々にタイミングを取りながらUpdateしたいときは Updateは呼ばない
    void Update()
    {
        Init();
        UpdateKeyInput();
        UpdateMouseInput();
        UpdateCamera();
    }

    void Draw()
    {
        // 原点からx軸(R:赤) y軸(G:緑) z軸(B:青)をライン表示する
        DxLib::DrawLine3D(VGet(0, 0, 0), VGet(1.0f * 5.0f, 0.0f * 5.0f, 0.0f * 5.0f), GetColor(255, 0, 0));
        DxLib::DrawLine3D(VGet(0, 0, 0), VGet(0.0f * 5.0f, 1.0f * 5.0f, 0.0f * 5.0f), GetColor(0, 255, 0));
        DxLib::DrawLine3D(VGet(0, 0, 0), VGet(0.0f * 5.0f, 0.0f * 5.0f, 1.0f * 5.0f), GetColor(0, 0, 255));
        // 画面に回転量を描画
        DxLib::DrawFormatString(0, 0, DxLib::GetColor(255, 255, 255), "Cam(x:%f y:%f z:%f)", camera.x, camera.y, camera.z);
        DxLib::DrawFormatString(0, 20, DxLib::GetColor(255, 255, 255), "Rot(V:%f H:%f T:%f)", camera.VRotate, camera.HRotate, camera.TRotate);
    }

};

#endif

いかがでしょう?キーボードのDAキーとEQキーでカメラ視点を左 右や上下に移動できるようになりましたか?
カメラの上下左右方向でも重要なのは 2Dのスクリーン から 3Dのワールド座標 での方向のベクトルを求めることです
GetWorldDirByCameraScreenXYDir関数の中でもDxLib::ConvScreenPosToWorldPos関数を使って 2D → 3D座標 への変換を行っています
カメラの左右移動方向は「スクリーンの枠」の左右方向、 カメラの上下移動方向は「スクリーンの枠」の上下方向と 平行になるベクトルです




Unityのアセットストアからとってきたテクスチャ1 枚のシンプルな3DモデルをDXのモデルビューアで.mv1形式に変換して表示させてみる

複雑な3Dモデルだとテクスチャリンクつけなおしやボーンが崩れた りして大変なので下記リンクのシンプルな無料3DモデルをUnityにインポートします
https://assetstore.unity.com/packages/3d/characters/humanoids/fantasy/devil-animated-character-60777?locale=ja-JP&srsltid=AfmBOor53ggeWTlrD6CMx0iQtZj3gZrHiPBdYgzpG2Ljhm4FIBT912Q3

Unityの[Window]→[Package Manager]を開いて、取得したデビルのキャラのアセットを[Download]



Unityへ[Import]ボタンでインポートする



Assetフォルダ→Devilフォルダ→Animationsフォルダの「devil@fly」を[右クリック]→ [Show in Explorer]で3DのモデルのあるフォルダをWindowsで開く



Unityのアセットフォルダの3DモデルをコピーしてDXのプロジェクトのImgaeフォルダへコピペする



DXライブラリをインストールしたフォルダの中のToolフォルダにDXモデルビューアがあるので起動する



[マテリアル]→[lambert1(ランバート反射マテリアル)]→[拡散光(ディフーズ反射)]→[ディフーズマッ プ]のテクスチャリンクの[参照]先にファイルがないので
3Dモデルが灰色で表示されてしまう



Unityの[Assetsフォルダ]→[Devilフォルダ]→[Textureフォルダ]のテクスチャを[右クリッ ク]→[Show in Explorer]でWindowsのフォルダ上で画像ファイルを見つける



DXのプロジェクトのImageフォルダへテクスチャ画像をコピペする



DXのモデルビューアで3Dモデルを開きなおすか、
[マテリアル]→[lambert1]→[拡散光]→[ディフーズマップ]のテクスチャリンクの[参照]を押してコピペし た画像に参照を貼りなおすと
3Dモデル表面にテクスチャ画像が反映される



[ファイル]→[名前を付けて保存]でDXのプロジェクトのImageフォルダに.mv1形式(DXで扱える3Dモデル形式)で保存する



main.cppのコードに.mv1形式の3Dモデルをロードして、描画表示させるプログラムを追加してみましょう。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include "Input.h"
#include "Screen.h"
#include "Ply.h"

#include "Editor.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズWidth×Heightのカラービット数32ビットで起動
    SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    SetDrawScreen(DX_SCREEN_BACK);

    // ゲームのwhileループを開始する前の初期化処理
    int devil3DModel = DxLib::MV1LoadModel("Image/devil@fly.mv1"); // 3Dモデルをmv1形式で読込み

    int texImage = -1;
    texImage = LoadGraph("Image/dice.png");
    assert(texImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる
    float imgWidth, imgHeight;
    GetGraphSizeF(texImage, &imgWidth, &imgHeight); // テクスチャ画像のサイズを得る

    Ply::Data Data; // .plyのデータのクラス
    Ply::load("Image/dice.ply", Data); // .plyデータをロード 軸を変えてdice1.plyや dice2.plyなどいろいろ試してみよう

    size_t numIndex = 0; // インデックスの数
    std::vector<uint32_t> Index; // 3Dモデルのポリゴンのインデックス配列
    std::vector<VERTEX3D> Vertex; // 3Dモデルのポリゴンの頂点配列

    auto vertex = Data["vertex"]; // 頂点へのアクセス
    assert(vertex->size() != 0 && "Plyデータの頂点サイズが0です。ファイルのパスは大丈夫ですか?");
    Vertex.resize(vertex->size()); // 配列の数の事前確保

    // [.plyの頂点のプロパティ情報] https://jp.mathworks.com/help/vision/ug/the-ply-format.html
    const float* p_x = vertex->properties["x"]->ptr<float>();
    const float* p_y = vertex->properties["y"]->ptr<float>();
    const float* p_z = vertex->properties["z"]->ptr<float>();
    const float* p_nx = (vertex->properties.has_key("nx")) ? vertex->properties["nx"]->ptr<float>() : nullptr;
    const float* p_ny = (vertex->properties.has_key("ny")) ? vertex->properties["ny"]->ptr<float>() : nullptr;
    const float* p_nz = (vertex->properties.has_key("nz")) ? vertex->properties["nz"]->ptr<float>() : nullptr;
    bool has_norm = !(p_nx == nullptr || p_ny == nullptr || p_nz == nullptr);
    const float* p_s = (vertex->properties.has_key("s")) ? vertex->properties["s"]->ptr<float>() : nullptr; // Blender書き出しでは(u,v)ではなく(s,t)として書き出されている
    const float* p_t = (vertex->properties.has_key("t")) ? vertex->properties["t"]->ptr<float>() : nullptr;

    const unsigned char* p_R = (vertex->properties.has_key("red")) ? vertex->properties["red"]->ptr<unsigned char>()
        : (vertex->properties.has_key("r")) ? vertex->properties["r"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_G = (vertex->properties.has_key("green")) ? vertex->properties["green"]->ptr<unsigned char>()
        : (vertex->properties.has_key("g")) ? vertex->properties["g"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_B = (vertex->properties.has_key("blue")) ? vertex->properties["blue"]->ptr<unsigned char>()
        : (vertex->properties.has_key("b")) ? vertex->properties["b"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_A = (vertex->properties.has_key("alpha")) ? vertex->properties["alpha"]->ptr<unsigned char>()
        : (vertex->properties.has_key("a")) ? vertex->properties["a"]->ptr<unsigned char>() : nullptr;
    bool has_color = !(p_R == nullptr || p_G == nullptr || p_B == nullptr || p_A == nullptr);

    for (size_t i = 0, iEnd = Vertex.size(); i < iEnd; i += 1)
    {
        // 頂点のデータをセット 各ツールの系一覧 https://zenn.dev/it_ks/articles/cbe27860548ea1
        //Vertex[i].pos = VGet(p_x[i], p_y[i], p_z[i]); // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        //Vertex[i].pos = VGet(p_x[i], -p_y[i], p_z[i]); // 右手Y↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        //Vertex[i].norm = (has_norm) ? VNorm(VGet(p_nx[i], -p_ny[i], p_nz[i])) : VGet(0.0f, 0.0f, 0.0f);

        // 右手Z↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        Vertex[i].pos = VGet(-(p_y[i]), p_z[i], p_x[i]);
        Vertex[i].norm = (has_norm) ? VNorm(VGet(-p_ny[i], p_nz[i], p_nx[i])) : VGet(0.0f, 0.0f, 0.0f);
        Vertex[i].dif = (has_color) ? GetColorU8(p_R[i], p_G[i], p_B[i], p_A[i]) : GetColorU8(255, 255, 255, 255);
        Vertex[i].spc = GetColorU8(255, 255, 255, 255);
        Vertex[i].u = (p_s == nullptr) ? 0.0f : p_s[i];
        Vertex[i].v = (p_t == nullptr) ? 0.0f : 1 - p_t[i];
        Vertex[i].su = -1.0f;
        Vertex[i].sv = 0.0f;
    }

    const auto& face_index = Data["face"]->properties["vertex_indices"];
    Index.resize(face_index->size() * 3);

    for (size_t i = 0, iEnd = face_index->size(), nFace = 0; i < iEnd; i += 3)
    {
        //for (size_t j = 0; j < 3; ++j) // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        for (size_t j = 2; j + 1 > 0; --j) // 左手系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        {
            unsigned int index = face_index->at<unsigned int>(i + j);
            const size_t uvIndex = 2 * (i + j); // uとv 2つ * ( i + j )

            //Index[i + j] = index;   // 右手系(裏面は時計回り)
            Index[i + 2 - j] = index; // 左手系(裏面は反時計回り)

            // 三角形の2辺のベクトルの外積から法線を計算(法線がないとライトの光の反射方向をシェーディングできないから)
            if (j == 0 && !has_norm && Vertex[index].norm.x == 0.0f && Vertex[index].norm.y == 0.0f && Vertex[index].norm.z == 0.0f)
            {
                const unsigned int index0 = Index[i];
                const unsigned int index1 = Index[i + 1];
                const unsigned int index2 = Index[i + 2];

                // 外積 a×b cross_a_b を求める https://examist.jp/mathematics/space-vector/vector-gaiseki/
                const VECTOR a = VSub(Vertex[index1].pos, Vertex[index0].pos);
                const VECTOR b = VSub(Vertex[index2].pos, Vertex[index0].pos);
                // 左手系(表面は時計回り) 右手系と左手系では裏面の時計回りと反時計回りが変わる
                const VECTOR cross1_2 = VCross(a, b);
                // 右手系(表面は反時計回り)
                //const VECTOR cross2_1 = VCross(b, a);

                // 単位ベクトル(ノルム)に直す [単位ベクトル]は√(x*x + y*y + z*z) = 1となる
                const VECTOR norm1_2 = VNorm(cross1_2);
                //const VECTOR norm2_1 = VNorm(cross2_1);
                Vertex[index0].norm = norm1_2; Vertex[index1].norm = norm1_2; Vertex[index2].norm = norm1_2;
                //Vertex[index0].norm = norm2_1; Vertex[index1].norm = norm2_1; Vertex[index2].norm = norm2_1;
            }

            ++numIndex;
        }
    }


    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 0.0f; keyControlXYZ.y = 0.0f; keyControlXYZ.z = 0;
    VECTOR keyControlAngle = VGet(0.0f,0.0f,0.0f); // 回転

        // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen1 = SubScreen::Create()->Set(600, 450, 600, 100, 255, 0, "サブスクリーン1")->SetIsBarFrameShow(true,true);
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen2 = SubScreen::Create()->Set(200, 100, 150, 100, 255, 1, "サブスクリーン2\nバーを30で太めに")->SetFrame(1,GetColor(255,255,255),true,30);
    // サブのスクリーンを生成する 透明度を128にして半透明にしてみる
    std::shared_ptr<SubScreen> subscreen3 = SubScreen::Create()->Set(150, 450, 300, 100, 128, 1, "サブスクリーン3")->SetIsBarFrameShow(true)->SetBarColor(GetColor(0, 0, 255));
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen4 = SubScreen::Create()->Set(300, 100, 600, 100, 255, 1, "サブスクリーン4");

    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    Editor& editor = Editor::GetInstance(); // エディタのシングルトン参照を得る

    //奥行0.1~5000までをカメラの描画範囲とする
    DxLib::SetCameraNearFar(0.1f, 5000.0f);

    ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Input::Update(); // マウスのドラッグなどを更新
        SubScreen::UpdateScreens(); // スクリーンの位置やドラッグによる移動を更新する

        editor.UpdateKeyInput(); // エディタのキーのインプットを更新

        editor.UpdateMouseInput(); // エディタのマウスのインプットを更新
        editor.UpdateCamera(); // エディタのカメラを更新
       
        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        VECTOR position = VGet(keyControlXYZ.x, keyControlXYZ.y, keyControlXYZ.z); // 3Dモデルの位置

        float width = imgWidth; float height = imgHeight; // テクスチャ画僧のサイズ
        VECTOR scale = VGet(1.0f, 1.0f, 1.0f); // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)とcキー(C)でx,y,z軸まわりに画像を回転
        if (PadInput & PAD_INPUT_A) keyControlAngle.x += speed;
        if (PadInput & PAD_INPUT_B) keyControlAngle.y += speed;
        if (PadInput & PAD_INPUT_C) keyControlAngle.z += speed;

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数
        const float xRotate = keyControlAngle.x * Deg2Rad;  const float yRotate = keyControlAngle.y * Deg2Rad; const float zRotate = keyControlAngle.z * Deg2Rad;


        MATRIX  matWorldDefault; // デフォルトのワールド行列
        DxLib::GetTransformToWorldMatrix(&matWorldDefault); // デフォルトのワールド行列を記憶しておく
        {
            //[ワールド行列] https://ny-program.hatenablog.com/entry/2019/09/15/114245
            MATRIX mat; // モデルのスケール×回転×移動を行列計算で設定する

            //[スケール]拡大縮小のスケール行列を得る
            mat = MGetScale(scale); // まずは拡大縮小のスケール行列からはじめる
            // [勉強]↓スケール行列は 斜めに x , y , zのスケールの倍率がならんでいる行列
            //mat.m[0][0] = scale.x;  mat.m[0][1] = 0.0f;    mat.m[0][2] = 0.0f;     mat.m[0][3] = 0.0f;
            //mat.m[1][0] = 0.0f;     mat.m[1][1] = scale.y; mat.m[1][2] = 0.0f;     mat.m[1][3] = 0.0f;
            //mat.m[2][0] = 0.0f;     mat.m[2][1] = 0.0f;    mat.m[2][2] = scale.z;  mat.m[2][3] = 0.0f;
            //mat.m[3][0] = 0.0f;     mat.m[3][1] = 0.0f;    mat.m[3][2] = 0.0f;     mat.m[3][3] = 1.0f;

            //[回転]
            MATRIX matRot;
            DxLib::CreateIdentityMatrix(&matRot);
            // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
            matRot = MMult(matRot, MGetRotZ(zRotate));
            matRot = MMult(matRot, MGetRotX(xRotate));
            matRot = MMult(matRot, MGetRotY(yRotate));
            mat = MMult(mat, matRot); //スケール × 回転

            //[移動]
            MATRIX matTrans = MGetTranslate(VGet(position.x, position.y, position.z)); // 位置positionをここで設定
            mat = MMult(mat, matTrans); //スケール×回転×移動

            DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4341

            DxLib::DrawPolygon32bitIndexed3D(Vertex.data(), Vertex.size(), Index.data(), numIndex / 3, texImage, FALSE); // 三角形ポリゴンをたくさん描画
        }
        DxLib::SetTransformToWorld(&matWorldDefault); // デフォルトのワールド行列に戻しておく

        DxLib::MV1DrawModel(devil3DModel); // 3Dモデル(.mv1形式)を描く

        DxLib::DrawFormatString(0, 100, GetColor(255, 255, 0), "x:%f y:%f Angle:%f %f %f", position.x, position.y, keyControlAngle.x, keyControlAngle.y, keyControlAngle.z);

        subscreen1->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen1->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // サブの別のスクリーンの(0,0)位置を起点に文字列を描く
            DxLib::DrawString(0, 0, "サブのスクリーンを使ってWindowsみたいな自作ウインドウを描画してみる", GetColor(255, 255, 255));
        }
        subscreen1->DrawEnd();

        subscreen3->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen3->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // 半透明のスクリーンも描ける
            DxLib::DrawBox(0, 0, subscreen3->Width, subscreen3->Height, GetColor(0, 255, 0), TRUE);
            DxLib::DrawString(0, 0, "アルファを設定すれば\n半透明スクリーンも可能", GetColor(255, 255, 255));
        }
        subscreen3->DrawEnd();

        subscreen4->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen4->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // サブの別のスクリーンの(0,0)位置を起点に文字列を描く
            DxLib::DrawString(0, 0, "枠の無いスクリーンを使えば2Pの分割画面などにも応用できる", GetColor(255, 255, 255));
        }
        subscreen4->DrawEnd();


        SubScreen::DrawScreens(); // サブのスクリーンを全部描く

        editor.Draw(); // エディタのDraw処理

        ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    WaitKey();
    // DXライブラリの後始末
    DxLib_End();
    // ソフトの終了
    return 0;
}



いかがでしょうか?MV1形式の3Dモデルがちゃんと表示されたでしょうか?
Unityのアセットは基本100倍のスケールで制作されているのでマウスをぐりぐりさせてズームアウトさせないとデカすぎて見えないはずです。
こういうトラブルを回避するために事前にエディタの直感的な操作を準備しておいたのです。



[練習問題] .plyのときに DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定]
したのと同じように.mv1形式の3Dモデルにもスケールや回転や移動の行列を反映させてみよ




MV1形式の3Dモデルをアニメーション描画する


main.cppのコードに.mv1形式の3Dモデルにデフォルトでアニメがついていたら、そのアニメをモデルにアタッチしてアニメーションさせるプログラムを追加してみましょう。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include "Input.h"
#include "Screen.h"
#include "Ply.h"

#include "Editor.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズWidth×Heightのカラービット数32ビットで起動
    SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    SetDrawScreen(DX_SCREEN_BACK);

    // ゲームのwhileループを開始する前の初期化処理
    int devil3DModel = DxLib::MV1LoadModel("Image/devil@fly.mv1"); // 3Dモデルをmv1形式で読込み

    float animTime = 0.0f; // アニメの現在時刻
    float animSpeed = 2.0f; // MV1のアニメの再生速度設定 2.0fなら2倍速
    float animStepTime = 1.0f; // MV1のアニメの毎時間の進む時間の設定
    int DefalutAnimNum = DxLib::MV1GetAnimNum(devil3DModel); // 3Dモデル内のアニメの数
    std::unordered_map<std::string, int> animNameToIndex; // アニメ名からアニメのインデックス番号への辞書
    std::unordered_map<std::string, float> animNameToTotalTime; // アニメ名からアニメの総再生時間への辞書
    for (int index = 0; index < DefalutAnimNum; ++index)
    {   // アニメの数ぶん順番にアタッチを設定
        const char* animName = DxLib::MV1GetAnimName(devil3DModel, index); // アニメーション名を取得
        // アニメ名からインデックス番号を得るために辞書に登録しておく
        animNameToIndex[animName] = DxLib::MV1AttachAnim(devil3DModel, index); // アニメをアタッチして、その番号を辞書に登録
        animNameToTotalTime[animName] = DxLib::MV1GetAttachAnimTotalTime(devil3DModel, index); // アニメをループさせるためにアニメの総再生時間を辞書に記録しておく
    }

   

    int texImage = -1;
    texImage = LoadGraph("Image/dice.png");
    assert(texImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる
    float imgWidth, imgHeight;
    GetGraphSizeF(texImage, &imgWidth, &imgHeight); // テクスチャ画像のサイズを得る

    Ply::Data Data; // .plyのデータのクラス
    Ply::load("Image/dice.ply", Data); // .plyデータをロード 軸を変えてdice1.plyや dice2.plyなどいろいろ試してみよう

    size_t numIndex = 0; // インデックスの数
    std::vector<uint32_t> Index; // 3Dモデルのポリゴンのインデックス配列
    std::vector<VERTEX3D> Vertex; // 3Dモデルのポリゴンの頂点配列

    auto vertex = Data["vertex"]; // 頂点へのアクセス
    assert(vertex->size() != 0 && "Plyデータの頂点サイズが0です。ファイルのパスは大丈夫ですか?");
    Vertex.resize(vertex->size()); // 配列の数の事前確保

    // [.plyの頂点のプロパティ情報] https://jp.mathworks.com/help/vision/ug/the-ply-format.html
    const float* p_x = vertex->properties["x"]->ptr<float>();
    const float* p_y = vertex->properties["y"]->ptr<float>();
    const float* p_z = vertex->properties["z"]->ptr<float>();
    const float* p_nx = (vertex->properties.has_key("nx")) ? vertex->properties["nx"]->ptr<float>() : nullptr;
    const float* p_ny = (vertex->properties.has_key("ny")) ? vertex->properties["ny"]->ptr<float>() : nullptr;
    const float* p_nz = (vertex->properties.has_key("nz")) ? vertex->properties["nz"]->ptr<float>() : nullptr;
    bool has_norm = !(p_nx == nullptr || p_ny == nullptr || p_nz == nullptr);
    const float* p_s = (vertex->properties.has_key("s")) ? vertex->properties["s"]->ptr<float>() : nullptr; // Blender書き出しでは(u,v)ではなく(s,t)として書き出されている
    const float* p_t = (vertex->properties.has_key("t")) ? vertex->properties["t"]->ptr<float>() : nullptr;

    const unsigned char* p_R = (vertex->properties.has_key("red")) ? vertex->properties["red"]->ptr<unsigned char>()
        : (vertex->properties.has_key("r")) ? vertex->properties["r"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_G = (vertex->properties.has_key("green")) ? vertex->properties["green"]->ptr<unsigned char>()
        : (vertex->properties.has_key("g")) ? vertex->properties["g"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_B = (vertex->properties.has_key("blue")) ? vertex->properties["blue"]->ptr<unsigned char>()
        : (vertex->properties.has_key("b")) ? vertex->properties["b"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_A = (vertex->properties.has_key("alpha")) ? vertex->properties["alpha"]->ptr<unsigned char>()
        : (vertex->properties.has_key("a")) ? vertex->properties["a"]->ptr<unsigned char>() : nullptr;
    bool has_color = !(p_R == nullptr || p_G == nullptr || p_B == nullptr || p_A == nullptr);

    for (size_t i = 0, iEnd = Vertex.size(); i < iEnd; i += 1)
    {
        // 頂点のデータをセット 各ツールの系一覧 https://zenn.dev/it_ks/articles/cbe27860548ea1
        //Vertex[i].pos = VGet(p_x[i], p_y[i], p_z[i]); // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        //Vertex[i].pos = VGet(p_x[i], -p_y[i], p_z[i]); // 右手Y↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        //Vertex[i].norm = (has_norm) ? VNorm(VGet(p_nx[i], -p_ny[i], p_nz[i])) : VGet(0.0f, 0.0f, 0.0f);

        // 右手Z↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        Vertex[i].pos = VGet(-(p_y[i]), p_z[i], p_x[i]);
        Vertex[i].norm = (has_norm) ? VNorm(VGet(-p_ny[i], p_nz[i], p_nx[i])) : VGet(0.0f, 0.0f, 0.0f);
        Vertex[i].dif = (has_color) ? GetColorU8(p_R[i], p_G[i], p_B[i], p_A[i]) : GetColorU8(255, 255, 255, 255);
        Vertex[i].spc = GetColorU8(255, 255, 255, 255);
        Vertex[i].u = (p_s == nullptr) ? 0.0f : p_s[i];
        Vertex[i].v = (p_t == nullptr) ? 0.0f : 1 - p_t[i];
        Vertex[i].su = -1.0f;
        Vertex[i].sv = 0.0f;
    }

    const auto& face_index = Data["face"]->properties["vertex_indices"];
    Index.resize(face_index->size() * 3);

    for (size_t i = 0, iEnd = face_index->size(), nFace = 0; i < iEnd; i += 3)
    {
        //for (size_t j = 0; j < 3; ++j) // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        for (size_t j = 2; j + 1 > 0; --j) // 左手系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        {
            unsigned int index = face_index->at<unsigned int>(i + j);
            const size_t uvIndex = 2 * (i + j); // uとv 2つ * ( i + j )

            //Index[i + j] = index;   // 右手系(裏面は時計回り)
            Index[i + 2 - j] = index; // 左手系(裏面は反時計回り)

            // 三角形の2辺のベクトルの外積から法線を計算(法線がないとライトの光の反射方向をシェーディングできないから)
            if (j == 0 && !has_norm && Vertex[index].norm.x == 0.0f && Vertex[index].norm.y == 0.0f && Vertex[index].norm.z == 0.0f)
            {
                const unsigned int index0 = Index[i];
                const unsigned int index1 = Index[i + 1];
                const unsigned int index2 = Index[i + 2];

                // 外積 a×b cross_a_b を求める https://examist.jp/mathematics/space-vector/vector-gaiseki/
                const VECTOR a = VSub(Vertex[index1].pos, Vertex[index0].pos);
                const VECTOR b = VSub(Vertex[index2].pos, Vertex[index0].pos);
                // 左手系(表面は時計回り) 右手系と左手系では裏面の時計回りと反時計回りが変わる
                const VECTOR cross1_2 = VCross(a, b);
                // 右手系(表面は反時計回り)
                //const VECTOR cross2_1 = VCross(b, a);

                // 単位ベクトル(ノルム)に直す [単位ベクトル]は√(x*x + y*y + z*z) = 1となる
                const VECTOR norm1_2 = VNorm(cross1_2);
                //const VECTOR norm2_1 = VNorm(cross2_1);
                Vertex[index0].norm = norm1_2; Vertex[index1].norm = norm1_2; Vertex[index2].norm = norm1_2;
                //Vertex[index0].norm = norm2_1; Vertex[index1].norm = norm2_1; Vertex[index2].norm = norm2_1;
            }

            ++numIndex;
        }
    }


    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 0.0f; keyControlXYZ.y = 0.0f; keyControlXYZ.z = 0;
    VECTOR keyControlAngle = VGet(0.0f,0.0f,0.0f); // 回転

        // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen1 = SubScreen::Create()->Set(600, 450, 600, 100, 255, 0, "サブスクリーン1")->SetIsBarFrameShow(true,true);
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen2 = SubScreen::Create()->Set(200, 100, 150, 100, 255, 1, "サブスクリーン2\nバーを30で太めに")->SetFrame(1,GetColor(255,255,255),true,30);
    // サブのスクリーンを生成する 透明度を128にして半透明にしてみる
    std::shared_ptr<SubScreen> subscreen3 = SubScreen::Create()->Set(150, 450, 300, 100, 128, 1, "サブスクリーン3")->SetIsBarFrameShow(true)->SetBarColor(GetColor(0, 0, 255));
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen4 = SubScreen::Create()->Set(300, 100, 600, 100, 255, 1, "サブスクリーン4");

    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    Editor& editor = Editor::GetInstance(); // エディタのシングルトン参照を得る

    //奥行0.1~5000までをカメラの描画範囲とする
    DxLib::SetCameraNearFar(0.1f, 5000.0f);

    ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Input::Update(); // マウスのドラッグなどを更新
        SubScreen::UpdateScreens(); // スクリーンの位置やドラッグによる移動を更新する

        editor.UpdateKeyInput(); // エディタのキーのインプットを更新

        editor.UpdateMouseInput(); // エディタのマウスのインプットを更新
        editor.UpdateCamera(); // エディタのカメラを更新
       
        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        VECTOR position = VGet(keyControlXYZ.x, keyControlXYZ.y, keyControlXYZ.z); // 3Dモデルの位置

        float width = imgWidth; float height = imgHeight; // テクスチャ画僧のサイズ
        VECTOR scale = VGet(1.0f, 1.0f, 1.0f); // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)とcキー(C)でx,y,z軸まわりに画像を回転
        if (PadInput & PAD_INPUT_A) keyControlAngle.x += speed;
        if (PadInput & PAD_INPUT_B) keyControlAngle.y += speed;
        if (PadInput & PAD_INPUT_C) keyControlAngle.z += speed;

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数
        const float xRotate = keyControlAngle.x * Deg2Rad;  const float yRotate = keyControlAngle.y * Deg2Rad; const float zRotate = keyControlAngle.z * Deg2Rad;


        MATRIX  matWorldDefault; // デフォルトのワールド行列
        DxLib::GetTransformToWorldMatrix(&matWorldDefault); // デフォルトのワールド行列を記憶しておく
        {
            //[ワールド行列] https://ny-program.hatenablog.com/entry/2019/09/15/114245
            MATRIX mat; // モデルのスケール×回転×移動を行列計算で設定する

            //[スケール]拡大縮小のスケール行列を得る
            mat = MGetScale(scale); // まずは拡大縮小のスケール行列からはじめる
            // [勉強]↓スケール行列は 斜めに x , y , zのスケールの倍率がならんでいる行列
            //mat.m[0][0] = scale.x;  mat.m[0][1] = 0.0f;    mat.m[0][2] = 0.0f;     mat.m[0][3] = 0.0f;
            //mat.m[1][0] = 0.0f;     mat.m[1][1] = scale.y; mat.m[1][2] = 0.0f;     mat.m[1][3] = 0.0f;
            //mat.m[2][0] = 0.0f;     mat.m[2][1] = 0.0f;    mat.m[2][2] = scale.z;  mat.m[2][3] = 0.0f;
            //mat.m[3][0] = 0.0f;     mat.m[3][1] = 0.0f;    mat.m[3][2] = 0.0f;     mat.m[3][3] = 1.0f;

            //[回転]
            MATRIX matRot;
            DxLib::CreateIdentityMatrix(&matRot);
            // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
            matRot = MMult(matRot, MGetRotZ(zRotate));
            matRot = MMult(matRot, MGetRotX(xRotate));
            matRot = MMult(matRot, MGetRotY(yRotate));
            mat = MMult(mat, matRot); //スケール × 回転

            //[移動]
            MATRIX matTrans = MGetTranslate(VGet(position.x, position.y, position.z)); // 位置positionをここで設定
            mat = MMult(mat, matTrans); //スケール×回転×移動

            DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4341

            DxLib::DrawPolygon32bitIndexed3D(Vertex.data(), Vertex.size(), Index.data(), numIndex / 3, texImage, FALSE); // 三角形ポリゴンをたくさん描画
        }
        DxLib::SetTransformToWorld(&matWorldDefault); // デフォルトのワールド行列に戻しておく

        DxLib::MV1DrawModel(devil3DModel); // 3Dモデル(.mv1形式)を描く

        DxLib::DrawFormatString(0, 100, GetColor(255, 255, 0), "x:%f y:%f Angle:%f %f %f", position.x, position.y, keyControlAngle.x, keyControlAngle.y, keyControlAngle.z);

        std::string animName = "Take 001"; // アニメ名:DXモデルビューアでアニメ名をきちんと確認したほうがよい↓
        assert(animNameToTotalTime.count(animName) > 0 && "指定されたアニメ名のアニメはMV1に付属していませんでした");
        animTime += animStepTime * animSpeed; // 1.0×2倍 なら2倍速でアニメ時刻が進む
        if (animTime > animNameToTotalTime[animName])
            animTime = 0.0f; // 0秒目へとアニメをループさせる

        // 再生時間をセットする
        DxLib::MV1SetAttachAnimTime(devil3DModel, animNameToIndex[animName], animTime);

      
        subscreen1->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen1->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // サブの別のスクリーンの(0,0)位置を起点に文字列を描く
            DxLib::DrawString(0, 0, "サブのスクリーンを使ってWindowsみたいな自作ウインドウを描画してみる", GetColor(255, 255, 255));
        }
        subscreen1->DrawEnd();

        subscreen3->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen3->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // 半透明のスクリーンも描ける
            DxLib::DrawBox(0, 0, subscreen3->Width, subscreen3->Height, GetColor(0, 255, 0), TRUE);
            DxLib::DrawString(0, 0, "アルファを設定すれば\n半透明スクリーンも可能", GetColor(255, 255, 255));
        }
        subscreen3->DrawEnd();

        subscreen4->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen4->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // サブの別のスクリーンの(0,0)位置を起点に文字列を描く
            DxLib::DrawString(0, 0, "枠の無いスクリーンを使えば2Pの分割画面などにも応用できる", GetColor(255, 255, 255));
        }
        subscreen4->DrawEnd();


        SubScreen::DrawScreens(); // サブのスクリーンを全部描く

        editor.Draw(); // エディタのDraw処理

        ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    WaitKey();
    // DXライブラリの後始末
    DxLib_End();
    // ソフトの終了
    return 0;
}


アニメ名をDXモデルビューアーの[アニメ]から確認



アニメの総再生時間とアニメのインデックス番号はここ


いかがですか?3Dキャラがアニメーションしたでしょうか?
アニメーションの時刻を進めるプログラムを書くには
アニメーション番号(No.)かアニメーション名(例.Take 001)を使う必要があるのでDXモデルビューアーで確認して間違わないようにしましょう


複数の3DモデルやアニメをResourceクラスで2 重ロード しないよう読み込んで複数アニメを切替えする

Resource.hを新規作成してResourceクラスで画像やテクスチャや3Dモデルなどのリソースを 2重にロー ドしないよう読み込んだときにstd::unordered_mapのfiles辞書に登録する機能を準備する。

#ifndef RESOURCE_H_
#define RESOURCE_H_

#include "DxLib.h"

#include <vector>
#include <string>
#include <memory> // 共有ポインタstd::shared_ptrを使う
#include <unordered_map> // 高速辞書<ファイルパス, 共有リソース>で一度読み込んだファイルはかぶらないように(2重ロード防止)する
#include <assert.h>

struct Resource : public std::enable_shared_from_this<Resource>
{    // [std::shared_ptrでthisポインタをつかうためには]↑ https://www.kabuku.co.jp/developers/cpp_enable_shared_from_this
    enum class Type { None, MV1, Ply, Texture, Sound, SE };
    Type m_type{ Type::None }; // データの種類
    std::vector<int> m_handles; // ロードしたリソースのハンドル番号
    inline operator int() { return (m_handles.size() > 0) ? m_handles[0] : -1; } // intでキャストした場合には先頭の0個目のハンドル番号を返す
    std::string m_FilePath{ "" }; // ロードしたリソースのファイルパス
    static std::unordered_map<std::string, std::shared_ptr<Resource>> files; // ロード済みのリソースファイルを保管する辞書

    virtual ~Resource() {}

protected: // コンストラクタ経由の生成経路を隠蔽してMakeShared関数経由でしか生成できないようにする
    Resource(const std::string& filePath = "", Type type = Type::None) : m_FilePath{ filePath }, m_type{ type } {}
public:
    //[protectedなコンストラクタでもstd::make_sharedで呼ぶテク] https://gintenlabo.hatenablog.com/entry/20131211/1386771626
    template<typename ResourceT>
    struct ProtectedCall : public ResourceT
    {
        template<class... ArgsT> // std::make_sharedはprotectedな継承クラスは呼べないのでそれを回避するテクニック
        explicit ProtectedCall(ArgsT&&... args)
            : ResourceT(std::forward<ArgsT>(args)...) { } //std::forwardで複数引数を完全転送で引き渡し https://proc-cpuinfo.fixstars.com/2016/03/c-html/
    };

    // コンストラクタをprotected:にしてあるのでこのMakeShared関数経由でしかResourceの新規生成はできないように縛ってある
    template<typename ResourceT, typename... ArgsT>
    static std::shared_ptr<ResourceT> MakeShared(const std::string& filePath = "", ArgsT... args)
    {
        if (files.find(filePath) == files.end()) // 辞書に未登録のファイルの場合は
            files[filePath] = std::make_shared<ProtectedCall<ResourceT>>(filePath, std::forward<ArgsT>(args)...); // 辞書へ登録

        return std::dynamic_pointer_cast<ResourceT>(files[filePath]); //[共有ポインタの派生先へのダウンキャスト] https://cdecrement.blog.fc2.com/blog-entry-15.html
    }

    template<typename ResourceT> // 指定されたfilepathのデータをResourceT型の共有ポインタとして返す
    static std::shared_ptr<ResourceT> at(const std::string& filepath)
    {
        assert(files.count(filepath) > 0 && "指定されたファイル名のファイルは未生成です。MakeShared関数で生成してからアクセスしてください");
        return std::dynamic_pointer_cast<ResourceT>(files.at(filepath)); //[共有ポインタの派生先へのダウンキャスト] https://komorinfo.com/blog/cast-of-smart-pointers/
    }

    static size_t count(const std::string& filepath) { return files.count(filepath); }

    virtual int Load(const std::string& filePath = "") = 0; //[ = 0 純粋仮想関数] ロード関数をoverrideして色んな種類のファイルのロードを実装する
};

struct Texture : public Resource
{
protected: // ファクトリーパターンでコンストラクタ経由の生成経路をprotected隠蔽(ResourceクラスのMakeShared関数経由でしか新規生成は できない)
    Texture(const std::string& filePath = "", int m_XNum = 1, int m_YNum = 1, int m_XSize = 1, int m_YSize = 1)
        : Resource(filePath, Type::Texture), m_XNum{ m_XNum }, m_YNum{ m_YNum }, m_XSize{ m_XSize }, m_YSize{ m_YSize } {}
public:
    int m_XNum = 1, m_YNum = 1, m_XSize = 1, m_YSize = 1; // XNum,YNumはx,y方向の分割画像の数 XSize,YSizeは画像の横×縦のサイズ(pixel数)

    inline int operator[](std::size_t index) const { //constは変更できないから添え字[]の 読み取り専用処理
        return m_handles[index]; // 読み取り専用(returnの型が &参照ついてないので書き換え不可能)
    }

    // すでにロードしてあるハンドルをDeleteGraphで破棄する
    virtual ~Texture() { DeleteGraph(); }

    virtual int DeleteGraph() // すでにロードしてあるハンドルをDxLib::DeleteGraphで破棄する
    {
        int result = 0;
        for (int handle : m_handles)
            result = (result == -1) ? -1 : DxLib::DeleteGraph(handle); // 一つでもDeleteに失敗したら-1
       
        std::vector<int>().swap(m_handles); // ハンドルの配列を完全に消去
        return result;
    }

    virtual int Load(const std::string& filePath = "") override
    {
        if (filePath == "" && m_FilePath == "") return -1;
        else if (files.count(filePath) > 0 && m_handles.size() > 0) return files[filePath]->m_handles[0]; // すでに辞書にロード済みハンドルがあった
        else if (filePath != "" && m_FilePath == "") m_FilePath = filePath; // 読み出しファイルパスを記録
        if (m_XNum > 1 || m_YNum > 1) return LoadDivGraph(m_FilePath, m_XNum, m_YNum, m_XSize, m_YSize); // 分割画像としてロード

        int loadHandle = DxLib::LoadGraph(m_FilePath.c_str()); // ファイルをロードする
        assert(loadHandle != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる
        if (loadHandle == -1) return -1; // ロード失敗
        m_handles.emplace_back(loadHandle); // ロード成功ならハンドル番号を記録
        // [std::shared_ptrへthis↓ポインタをつかうためには] https://cpprefjp.github.io/reference/memory/enable_shared_from_this.html
        files.emplace(m_FilePath, shared_from_this()); // 辞書へ登録
        DxLib::GetGraphSize(loadHandle, &m_XSize, &m_YSize); // 横XSize×縦YSizeを得る
        return loadHandle;
    }

    virtual int LoadDivGraph(const std::string& filePath = "", int XNum = -1, int YNum = -1, int XSize = -1, int YSize = -1)
    {
        if (filePath == "" && m_FilePath == "") return -1;
        if (files.count(filePath) > 0 && m_handles.size() > 0) return files[filePath]->m_handles[0]; // すでに辞書にロード済みのファイルのとき
        else if (filePath != "" && m_FilePath == "") m_FilePath = filePath; // 読み出しファイルパスを記録
        if (XNum > 0) m_XNum = XNum; // 数値を設定
        if (YNum > 0) m_YNum = YNum;
        if (XSize > 0) m_XSize = XSize;
        if (YSize > 0) m_YSize = YSize;
        m_handles.resize(m_XNum * m_YNum); // XNum×YNum個ぶんの配列を事前確保しておく
        int result = DxLib::LoadDivGraph(m_FilePath.c_str(), m_XNum * m_YNum, m_XNum, m_YNum, m_XSize, m_YSize, m_handles.data());
        assert(result != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる
        for (int handle : m_handles) assert(handle != -1); // 画像読込失敗、たてやよこのサイズの指定がおかしいのかも
        // [std::shared_ptrへthis↓ポインタをつかうためには] https://cpprefjp.github.io/reference/memory/enable_shared_from_this.html
        files.emplace(m_FilePath, shared_from_this()); // 辞書へ登録
        return m_handles[0]; // とりあえずは先頭配列を返す
    }
};

#endif

Resource.cppを新規作成してResourceクラスのstd::unordered_mapのfiles辞書の宣言をする。

#include "Resource.h"

std::unordered_map<std::string, std::shared_ptr<Resource>> Resource::files;


DataMV1.hを新規作成してResourceクラスを継承して3Dモデルのロードやアニメをアタッチして管理する機能を追加する。

#ifndef DATAMV1_H_
#define DATAMV1_H_

#include "DxLib.h"

#include "Resource.h"

#include <unordered_map> // 高速辞書配列

struct DataMV1 : public Resource
{
protected: // ファクトリーパターンでコンストラクタ経由の生成経路をprotected隠蔽(ResourceクラスのMakeShared関数経由でしか新規生成は できない)
    DataMV1(std::string filePath = "")
        : Resource(filePath, Type::MV1) {}
public:
    int m_DefalutAnimNum = -1; // 3Dモデル内のデフォルトのアニメの数
    int m_AttachAnimNum = 0; // この3Dモデルにアタッチされたアニメの数(デフォルトのアニメに加えて追加アタッチされたアニメの数も含む)

    struct AnimInfo // アニメに関する情報
    {
        int index = -1; // アニメのインデックス番号
        float endTime = 0.0f; // アニメの終了時刻(総再生時間):超えるとループかストップさせる
    };
    std::unordered_map<std::string, AnimInfo> m_AnimInfo; // 「ファイルパス + ":" + アニメ名」 から アニメの情報への辞書

    // ["devil@fly.fbx:Take 00"]のように m_FilePath + ":" + animName の形式にして
    //  ファイル1とファイル2で両方"Take 00"というアニメ名を使っていたときに辞書がかぶっちゃうことを予防する必要がある
    AnimInfo& operator [] (const std::string& animPathName)
    {
        return m_AnimInfo.at(animPathName);
    }
   
    size_t count(const std::string& animPathName) { return m_AnimInfo.count(animPathName); }

    bool isInitialized = false; //[継承2重ロード対策]ロード済みになったらtrueに falseのときにLoadするとリロード

    virtual ~DataMV1()
    {// 仮想デストラクタ
        clear();// データのお掃除
    };

    virtual int DeleteModels() // すでにロードしてあるハンドルをDxLib::MV1DeleteModelで破棄する
    {
        int result = 0;
        for (int handle : m_handles)
            result = (result == -1) ? -1 : DxLib::MV1DeleteModel(handle); // 一つでもDeleteに失敗したら-1

        std::vector<int>().swap(m_handles); // ハンドルの配列を完全に消去
        std::unordered_map<std::string, AnimInfo>().swap(m_AnimInfo); // アニメの辞書をクリア
        return result;
    }

    // データをクリアしてメモリを節約する
    virtual void clear()
    {
        DeleteModels(); // すでにロードしてあるハンドルをDxLib::MV1DeleteModelで破棄する
        isInitialized = false; //ロード済みフラグをOFF
    }

    // 3Dアニメのアタッチ、DXライブラリの機能をラッピング
    // https://dxlib.xsrv.jp/function/dxfunc_3d_model_1.html#R4N1
    virtual void AttachAnim(DataMV1& otherMV1Anim)
    {   // モデルに含まれるアニメーションの数を取得する
        int Num = 0;
        if (m_handles[0] == otherMV1Anim.m_handles[0])
            Num = m_AttachAnimNum = DxLib::MV1GetAnimNum(m_handles[0]); // 3Dモデル内のアニメ
        else // 追加の別mv1ファイルのアニメのアタッチのとき↓は Num = 追加側のアニメ数, m_AttachAnimNum = Num + m_AttachAnimNumを格納
            m_AttachAnimNum += (Num = DxLib::MV1GetAnimNum(otherMV1Anim.m_handles[0])); // アニメの追加アタッチ数だけ総アニメ数を加算

        for (int i = 0; i < Num; i++)
        {   // アニメの数ぶん順番にアタッチを設定
            const char* animName = DxLib::MV1GetAnimName(otherMV1Anim.m_handles[0], i); // アニメーション名を取得
            int animSrcHandle = (m_handles[0] == otherMV1Anim.m_handles[0]) ? -1 : otherMV1Anim.m_handles[0]; // 3Dモデル内アニメ(-1) or 追加アタッチアニメ
            int attachedIndex = m_AnimInfo[otherMV1Anim.m_FilePath + ":" + animName].index = DxLib::MV1AttachAnim(m_handles[0], i, animSrcHandle, FALSE); // アタッチとその番号を辞書に登録
            //アニメ総時間の取得と登録
            if (attachedIndex != -1)
                m_AnimInfo[otherMV1Anim.m_FilePath + ":" + animName].endTime = DxLib::MV1GetAttachAnimTotalTime(m_handles[0], attachedIndex);
            else
                m_AnimInfo[otherMV1Anim.m_FilePath + ":" + animName].endTime = 0.0f; //アニメアタッチ失敗時はアニメ進行を0.0に
        }
    }

    // ファイルの読み込み
    virtual int Load(const std::string& filePath = "") override
    {
        if (filePath == "" && m_FilePath == "") return -1; //ファイル名がない
        else if (files.count(filePath) > 0 && isInitialized) return 0; // すでに辞書にロード済みファイルがあった
        else if (filePath != "" && m_FilePath == "") m_FilePath = filePath; // 読み出しファイルパスを記録
        clear(); //データを一旦クリア

        int loadHandle = DxLib::MV1LoadModel(m_FilePath.c_str()); // 3Dモデルモデル読込み
        if (loadHandle == -1) assert("3Dモデルファイル見つからず読込み失敗!" == "");
        if (loadHandle == -1) return -1; // ロード失敗
        m_handles.emplace_back(loadHandle); // ロード成功ならハンドル番号を記録
        // [std::shared_ptrへthis↓ポインタをつかうためには] https://cpprefjp.github.io/reference/memory/enable_shared_from_this.html
        files.emplace(m_FilePath, shared_from_this()); // 辞書へ登録

        m_DefalutAnimNum = DxLib::MV1GetAnimNum(m_handles[0]); // 3Dモデル内にデフォルトでついていたアニメの数
        for (int index = 0; index < m_DefalutAnimNum; ++index)
        {   // アニメの数ぶん順番にアタッチを設定
            const char* animName = DxLib::MV1GetAnimName(m_handles[0], index); // アニメーション名を取得

            // アニメ名からアニメのインデックス番号を得るために辞書に登録しておく
            // アニメのアタッチする前の大元のファイルパス + アニメ名"例.Take 00"など を組み合わせないと
            // ファイル1とファイル2で両方"Take 00"というアニメ名を使っていたときに辞書がかぶっちゃって唯一性が保てない
            m_AnimInfo[m_FilePath + ":" + animName].index = DxLib::MV1AttachAnim(m_handles[0], index); // アニメをアタッチして、その番号を辞書に登録
            // アニメをループさせるためにアニメの総再生時間を辞書に記録しておく
            m_AnimInfo[m_FilePath + ":" + animName].endTime = DxLib::MV1GetAttachAnimTotalTime(m_handles[0], index);
            ++m_AttachAnimNum; // アニメの数を +1 カウントする
        }

        isInitialized = true;

        return 0;
    }
};

#endif


devilのデータをMV1形式に変換するにあたって、Blenderを通してリグ付けをしたいのだが、
Blenderで読み込めないバージョンのFBXとして書き出されたFBXモデル(devil)を、一旦UnityのFBXエクスポータで保存しなおして
FBXの保存バージョンを変えてしまうことでBlenderで読み込めるようにします。



UnityにFBXエクスポーターをインストールすると、
変換したいモデルをアセットのフォルダから選択した状態で[GameObject]→[Export To FBX]ができるようになっているので
クリックして出てきたウィンドウの[Export]ボタンをクリックします



デフォルトの保存先はUnityのアセットフォルダと違って
/ユーザー の /My projectフォルダ の中の /Assetsフォルダ の中
になっていて混乱しやすいのでUnityウィンドウの左下に表示された Successfully exported: C:○○○ ~ を注意深く見て、
エクスポータから出力されたファイルの保存先をたどって、出力されたFBXファイルをDXのImageフォルダにコピーしておきます



mixamoのサイトにアクセスして、
https://www.adobe.com/products/substance3d/plugins/mixamo-in-blender.html
Blender上でmixamoに対応したリグを3Dモデルに自動で入れてくれる「Mixamo Blender add-on」をダウンロードしておいてから、
Blenderを開いて、[編集]→[プリファレンス]から[アドオン]を開いて[インストール]ボタンを押して、mixamoのサイトからダ ウンロードしたファイルを開きます。



[アドオンをインストール]すると[プリファレンス]から[アドオン]でMixamo Rigで検索すると
(Blenderのバージョンが3.0台であれば)「アニメーション:Mixamo Rig」にチェックが入れられるようになっています。



[ファイル]から[インポート]で[FBX(.fbx)]からUnityのFBXエクスポータから書き出したdevilのFBXモデルを読み込んでから、
画面の右上のXYZ軸の横にある「 < 」をクリックするとMixamoのタブを出すことができます。



Mixamoのadd-onを使うと[Create Control Rig]を押せば自動でMixamo向けに対応したアニメのモーション用のリグを3Dモデルに埋め込んでくれます。



Mixamo向けのリグを埋め込んでから[ファイル]→[エクスポート]→[FBX(.fbx)]から「Mixamo対応リグの入っ たFBXモデル」を保存書き出しします。



保存するファイル名は他のファイルとごっちゃになって混乱しないように「devil@mixamo_addon.fbx」など区別しやすい名前を付けて保 存しておきます。



mixamoのモーションをいれるサイトにアクセスして
https://www.mixamo.com/#/?page=1&type=Motion%2CMotionPack
右端の[UPLOAD CHARACTER]ボタンを押して出てきたウィンドウに先ほど書き出した「devil@mixamo_addon.fbx」ファイルをドラッ グ&ドロップします。



Mixamo対応リグが入ったFBXモデルならばアップロードが成功してキャラクターが動いている画面が出てくるので[NEXT]を押します。



左側から好きなモーションを選択して、[DOWNLOAD]ボタンを押すと好きなモーションを付けたキャラをダウンロードできるようになります。



Format(ダウンロードするFBXのフォーマット)やSkin(3Dポリゴンの表面データも付属したデータか)などを設定して[DOWNLOAD]ボ タンでダウンロードします。



どのモーションを付けたかわかりやすいようにdevil@kick.fbxなどのファイル名に変えてから、DXのImageフォルダへコピーします。



DXのモデルビューワーから[ファイル]→[開く]で「devil@kick.fbx」を読み込んで、
[アニメーション]でmixamo.comのアニメがついていることを確認したら、
[ファイル]→[名前を付けて保存]から「devil@kick.mv1」というファイル名でMV1形式の3Dモデルを変換書き出しします。



複数のモーションの切替を試すためにキックのモーションだけでなく、ジャンプなどのモーションも「devil@jump.mv1」としてDXのImage フォルダに書き出しておきます。


書き出したMV1形式の3Dモデルをプログラム上で切り替えられるようにしてキーボードの「0」キーと「1」キーで切り替えられるようにしていきましょ う。

main.cppのコードにMV1形式のファイルを複数ロードしてキーボードの 0 1 などの数字キーでアニメを切替えできる機能を追加する。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include "Input.h"
#include "Screen.h"
#include "Ply.h"
#include "DataMV1.h"

#include "Editor.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズWidth×Heightのカラービット数32ビットで起動
    SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    SetDrawScreen(DX_SCREEN_BACK);
  
    // ゲームのwhileループを開始する前の初期化処理
    int devil3DModel = DxLib::MV1LoadModel("Image/devil@fly.mv1"); // 3Dモデルをmv1形式で読込み

    float animTime = 0.0f; // アニメの現在時刻     float animSpeed = 2.0f; // MV1のアニメの再生速度設定 2.0fなら2倍速
    float animStepTime = 1.0f; // MV1のアニメの毎時間の進む時間の設定
    int DefalutAnimNum = DxLib::MV1GetAnimNum(devil3DModel); // 3Dモデル内のアニメの数
    std::unordered_map<std::string, int> animNameToIndex; // アニメ名からアニメのインデックス番号への辞書
    std::unordered_map<std::string, float> animNameToTotalTime; // アニメ名からアニメの総再生時間への辞書
    for (int index = 0; index < DefalutAnimNum; ++index)
    {   // アニメの数ぶん順番にアタッチを設定
        const char* animName = DxLib::MV1GetAnimName(devil3DModel, index); // アニメーション名を取得
        // アニメ名からインデックス番号を得るために辞書に登録しておく
        animNameToIndex[animName] = DxLib::MV1AttachAnim(devil3DModel, index); // アニメをアタッチして、その番号を辞書に登録
        animNameToTotalTime[animName] = DxLib::MV1GetAttachAnimTotalTime(devil3DModel, index); // アニメをループさせるためにアニメの総再生時間を辞書に記録しておく
    }

    float animTime = 0.0f; // アニメの現在時刻
    float animSpeed = 2.0f; // MV1のアニメの再生速度設定 2.0fなら2倍速
    float animStepTime = 1.0f; // MV1のアニメの毎時間の進む時間の設定
    std::shared_ptr<DataMV1> pDevilData = Resource::MakeShared<DataMV1>("Image/devil@kick.mv1");
    pDevilData->Load(); // 3Dモデルをmv1形式で読込み
    std::shared_ptr<DataMV1> pDevilAttackAnimData = Resource::MakeShared<DataMV1>("Image/devil@jump.mv1");
    pDevilAttackAnimData->Load(); // 3Dの追加アニメデータをmv1形式で読込み
    pDevilData->AttachAnim(*pDevilAttackAnimData); // アニメを追加でflyのほうをベースとしたモデルにアタッチ
    int animeButton = 0; // ボタンでアニメを切り替えられるようにする


    int texImage = -1;
    texImage = LoadGraph("Image/dice.png");
    assert(texImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる
    float imgWidth, imgHeight;
    GetGraphSizeF(texImage, &imgWidth, &imgHeight); // テクスチャ画像のサイズを得る 

    std::shared_ptr<Texture> pTexImage = Resource::MakeShared<Texture>("Image/dice.png");
    pTexImage->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
    assert((int)*pTexImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる



    Ply::Data Data; // .plyのデータのクラス
    Ply::load("Image/dice.ply", Data); // .plyデータをロード 軸を変えてdice1.plyや dice2.plyなどいろいろ試してみよう

    size_t numIndex = 0; // インデックスの数
    std::vector<uint32_t> Index; // 3Dモデルのポリゴンのインデックス配列
    std::vector<VERTEX3D> Vertex; // 3Dモデルのポリゴンの頂点配列

    auto vertex = Data["vertex"]; // 頂点へのアクセス
    assert(vertex->size() != 0 && "Plyデータの頂点サイズが0です。ファイルのパスは大丈夫ですか?");
    Vertex.resize(vertex->size()); // 配列の数の事前確保

    // [.plyの頂点のプロパティ情報] https://jp.mathworks.com/help/vision/ug/the-ply-format.html
    const float* p_x = vertex->properties["x"]->ptr<float>();
    const float* p_y = vertex->properties["y"]->ptr<float>();
    const float* p_z = vertex->properties["z"]->ptr<float>();
    const float* p_nx = (vertex->properties.has_key("nx")) ? vertex->properties["nx"]->ptr<float>() : nullptr;
    const float* p_ny = (vertex->properties.has_key("ny")) ? vertex->properties["ny"]->ptr<float>() : nullptr;
    const float* p_nz = (vertex->properties.has_key("nz")) ? vertex->properties["nz"]->ptr<float>() : nullptr;
    bool has_norm = !(p_nx == nullptr || p_ny == nullptr || p_nz == nullptr);
    const float* p_s = (vertex->properties.has_key("s")) ? vertex->properties["s"]->ptr<float>() : nullptr; // Blender書き出しでは(u,v)ではなく(s,t)として書き出されている
    const float* p_t = (vertex->properties.has_key("t")) ? vertex->properties["t"]->ptr<float>() : nullptr;

    const unsigned char* p_R = (vertex->properties.has_key("red")) ? vertex->properties["red"]->ptr<unsigned char>()
        : (vertex->properties.has_key("r")) ? vertex->properties["r"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_G = (vertex->properties.has_key("green")) ? vertex->properties["green"]->ptr<unsigned char>()
        : (vertex->properties.has_key("g")) ? vertex->properties["g"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_B = (vertex->properties.has_key("blue")) ? vertex->properties["blue"]->ptr<unsigned char>()
        : (vertex->properties.has_key("b")) ? vertex->properties["b"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_A = (vertex->properties.has_key("alpha")) ? vertex->properties["alpha"]->ptr<unsigned char>()
        : (vertex->properties.has_key("a")) ? vertex->properties["a"]->ptr<unsigned char>() : nullptr;
    bool has_color = !(p_R == nullptr || p_G == nullptr || p_B == nullptr || p_A == nullptr);

    for (size_t i = 0, iEnd = Vertex.size(); i < iEnd; i += 1)
    {
        // 頂点のデータをセット 各ツールの系一覧 https://zenn.dev/it_ks/articles/cbe27860548ea1
        //Vertex[i].pos = VGet(p_x[i], p_y[i], p_z[i]); // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        //Vertex[i].pos = VGet(p_x[i], -p_y[i], p_z[i]); // 右手Y↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        //Vertex[i].norm = (has_norm) ? VNorm(VGet(p_nx[i], -p_ny[i], p_nz[i])) : VGet(0.0f, 0.0f, 0.0f);

        // 右手Z↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        Vertex[i].pos = VGet(-(p_y[i]), p_z[i], p_x[i]);
        Vertex[i].norm = (has_norm) ? VNorm(VGet(-p_ny[i], p_nz[i], p_nx[i])) : VGet(0.0f, 0.0f, 0.0f);
        Vertex[i].dif = (has_color) ? GetColorU8(p_R[i], p_G[i], p_B[i], p_A[i]) : GetColorU8(255, 255, 255, 255);
        Vertex[i].spc = GetColorU8(255, 255, 255, 255);
        Vertex[i].u = (p_s == nullptr) ? 0.0f : p_s[i];
        Vertex[i].v = (p_t == nullptr) ? 0.0f : 1 - p_t[i];
        Vertex[i].su = -1.0f;
        Vertex[i].sv = 0.0f;
    }

    const auto& face_index = Data["face"]->properties["vertex_indices"];
    Index.resize(face_index->size() * 3);

    for (size_t i = 0, iEnd = face_index->size(), nFace = 0; i < iEnd; i += 3)
    {
        //for (size_t j = 0; j < 3; ++j) // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        for (size_t j = 2; j + 1 > 0; --j) // 左手系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        {
            unsigned int index = face_index->at<unsigned int>(i + j);
            const size_t uvIndex = 2 * (i + j); // uとv 2つ * ( i + j )

            //Index[i + j] = index;   // 右手系(裏面は時計回り)
            Index[i + 2 - j] = index; // 左手系(裏面は反時計回り)

            // 三角形の2辺のベクトルの外積から法線を計算(法線がないとライトの光の反射方向をシェーディングできないから)
            if (j == 0 && !has_norm && Vertex[index].norm.x == 0.0f && Vertex[index].norm.y == 0.0f && Vertex[index].norm.z == 0.0f)
            {
                const unsigned int index0 = Index[i];
                const unsigned int index1 = Index[i + 1];
                const unsigned int index2 = Index[i + 2];

                // 外積 a×b cross_a_b を求める https://examist.jp/mathematics/space-vector/vector-gaiseki/
                const VECTOR a = VSub(Vertex[index1].pos, Vertex[index0].pos);
                const VECTOR b = VSub(Vertex[index2].pos, Vertex[index0].pos);
                // 左手系(表面は時計回り) 右手系と左手系では裏面の時計回りと反時計回りが変わる
                const VECTOR cross1_2 = VCross(a, b);
                // 右手系(表面は反時計回り)
                //const VECTOR cross2_1 = VCross(b, a);

                // 単位ベクトル(ノルム)に直す [単位ベクトル]は√(x*x + y*y + z*z) = 1となる
                const VECTOR norm1_2 = VNorm(cross1_2);
                //const VECTOR norm2_1 = VNorm(cross2_1);
                Vertex[index0].norm = norm1_2; Vertex[index1].norm = norm1_2; Vertex[index2].norm = norm1_2;
                //Vertex[index0].norm = norm2_1; Vertex[index1].norm = norm2_1; Vertex[index2].norm = norm2_1;
            }

            ++numIndex;
        }
    }


    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 0.0f; keyControlXYZ.y = 0.0f; keyControlXYZ.z = 0;
    VECTOR keyControlAngle = VGet(0.0f,0.0f,0.0f); // 回転

        // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen1 = SubScreen::Create()->Set(600, 450, 600, 100, 255, 0, "サブスクリーン1")->SetIsBarFrameShow(true,true);
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen2 = SubScreen::Create()->Set(200, 100, 150, 100, 255, 1, "サブスクリーン2\nバーを30で太めに")->SetFrame(1,GetColor(255,255,255),true,30);
    // サブのスクリーンを生成する 透明度を128にして半透明にしてみる
    std::shared_ptr<SubScreen> subscreen3 = SubScreen::Create()->Set(150, 450, 300, 100, 128, 1, "サブスクリーン3")->SetIsBarFrameShow(true)->SetBarColor(GetColor(0, 0, 255));
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen4 = SubScreen::Create()->Set(300, 100, 600, 100, 255, 1, "サブスクリーン4");

    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    Editor& editor = Editor::GetInstance(); // エディタのシングルトン参照を得る

    //奥行0.1~5000までをカメラの描画範囲とする
    DxLib::SetCameraNearFar(0.1f, 5000.0f);

    ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Input::Update(); // マウスのドラッグなどを更新
        SubScreen::UpdateScreens(); // スクリーンの位置やドラッグによる移動を更新する

        editor.UpdateKeyInput(); // エディタのキーのインプットを更新

        editor.UpdateMouseInput(); // エディタのマウスのインプットを更新
        editor.UpdateCamera(); // エディタのカメラを更新
       
        // キーボードの 0 1 キーで3Dアニメを切り替えられるようにする
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_0))
            animeButton = 0;
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_1))
            animeButton = 1;


        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        VECTOR position = VGet(keyControlXYZ.x, keyControlXYZ.y, keyControlXYZ.z); // 3Dモデルの位置

        float width =  pTexImage->m_XSize; float height =  pTexImage->m_YSize; // テクスチャ画像のサイズ
        VECTOR scale = VGet(1.0f, 1.0f, 1.0f); // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)とcキー(C)でx,y,z軸まわりに画像を回転
        if (PadInput & PAD_INPUT_A) keyControlAngle.x += speed;
        if (PadInput & PAD_INPUT_B) keyControlAngle.y += speed;
        if (PadInput & PAD_INPUT_C) keyControlAngle.z += speed;

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数
        const float xRotate = keyControlAngle.x * Deg2Rad;  const float yRotate = keyControlAngle.y * Deg2Rad; const float zRotate = keyControlAngle.z * Deg2Rad;


        MATRIX  matWorldDefault; // デフォルトのワールド行列
        DxLib::GetTransformToWorldMatrix(&matWorldDefault); // デフォルトのワールド行列を記憶しておく
        {
            //[ワールド行列] https://ny-program.hatenablog.com/entry/2019/09/15/114245
            MATRIX mat; // モデルのスケール×回転×移動を行列計算で設定する

            //[スケール]拡大縮小のスケール行列を得る
            mat = MGetScale(scale); // まずは拡大縮小のスケール行列からはじめる
            // [勉強]↓スケール行列は 斜めに x , y , zのスケールの倍率がならんでいる行列
            //mat.m[0][0] = scale.x;  mat.m[0][1] = 0.0f;    mat.m[0][2] = 0.0f;     mat.m[0][3] = 0.0f;
            //mat.m[1][0] = 0.0f;     mat.m[1][1] = scale.y; mat.m[1][2] = 0.0f;     mat.m[1][3] = 0.0f;
            //mat.m[2][0] = 0.0f;     mat.m[2][1] = 0.0f;    mat.m[2][2] = scale.z;  mat.m[2][3] = 0.0f;
            //mat.m[3][0] = 0.0f;     mat.m[3][1] = 0.0f;    mat.m[3][2] = 0.0f;     mat.m[3][3] = 1.0f;

            //[回転]
            MATRIX matRot;
            DxLib::CreateIdentityMatrix(&matRot);
            // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
            matRot = MMult(matRot, MGetRotZ(zRotate));
            matRot = MMult(matRot, MGetRotX(xRotate));
            matRot = MMult(matRot, MGetRotY(yRotate));
            mat = MMult(mat, matRot); //スケール × 回転

            //[移動]
            MATRIX matTrans = MGetTranslate(VGet(position.x, position.y, position.z)); // 位置positionをここで設定
            mat = MMult(mat, matTrans); //スケール×回転×移動

            DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4341

            DxLib::DrawPolygon32bitIndexed3D(Vertex.data(), Vertex.size(), Index.data(), numIndex / 3, (int)*pTexImage, FALSE); // 三角形ポリゴンをたくさん描画
        }
        DxLib::SetTransformToWorld(&matWorldDefault); // デフォルトのワールド行列に戻しておく

        DxLib::MV1DrawModel((int)*DevilData); // 3Dモデル(.mv1形式)を描く

        DxLib::DrawFormatString(0, 100, GetColor(255, 255, 0), "x:%f y:%f Angle:%f %f %f", position.x, position.y, keyControlAngle.x, keyControlAngle.y, keyControlAngle.z);

        // ファイルのパス + ":" + アニメ名でアクセス
        std::string animPathName = (animeButton == 0) ? "Image/devil@kick.mv1:mixamo.com" : "Image/devil@jump.mv1:mixamo.com";
        assert(pDevilData->count(animPathName) > 0 && "指定されたアニメ名のアニメはMV1に付属していませんでした");
        animTime += animStepTime * animSpeed; // 1.0×2倍 なら2倍速でアニメ時刻が進む
        if (animTime > (*pDevilData)[animPathName].endTime)
            animTime = 0.0f; // 0秒目へとアニメをループさせる

        // 再生時間をセットする
        DxLib::MV1SetAttachAnimTime((int)*pDevilData, (*pDevilData)[animPathName].index, animTime);
      
        subscreen1->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen1->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // サブの別のスクリーンの(0,0)位置を起点に文字列を描く
            DxLib::DrawString(0, 0, "サブのスクリーンを使ってWindowsみたいな自作ウインドウを描画してみる", GetColor(255, 255, 255));
        }
        subscreen1->DrawEnd();

        subscreen3->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen3->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // 半透明のスクリーンも描ける
            DxLib::DrawBox(0, 0, subscreen3->Width, subscreen3->Height, GetColor(0, 255, 0), TRUE);
            DxLib::DrawString(0, 0, "アルファを設定すれば\n半透明スクリーンも可能", GetColor(255, 255, 255));
        }
        subscreen3->DrawEnd();

        subscreen4->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen4->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // サブの別のスクリーンの(0,0)位置を起点に文字列を描く
            DxLib::DrawString(0, 0, "枠の無いスクリーンを使えば2Pの分割画面などにも応用できる", GetColor(255, 255, 255));
        }
        subscreen4->DrawEnd();


        SubScreen::DrawScreens(); // サブのスクリーンを全部描く

        editor.Draw(); // エディタのDraw処理

        ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    WaitKey();
    // DXライブラリの後始末
    DxLib_End();
    // ソフトの終了
    return 0;
}


いかがでしょう?キーボードの0キーと1キーでキャラのモーションを切替えできるようになりましたか?
0と1キー以外にもいろんなモーションを付けてみて切り替えられるか試してみましょう。




.ply形式の3DモデルデータをResourceクラスを継承したDataPly構造体で読み込む

DataPly.hを新規作成し てResourceクラスを継承して .ply形式の3Dモデルデータを読み込む機能を追加してmain.cppのコードをすっきりさせてしまいましょう。

#ifndef DATAPLY_H_
#define DATAPLY_H_

#include "Dxlib.h"

#include <memory>
#include <vector>
#include <string>

#include <assert.h> // 読込み失敗表示用

#include "Ply.h"

#include "Resource.h"

// .ply形式の3Dモデルデータを読み込む構造体
struct DataPly : public Resource
{
protected: // ファクトリーパターンでコンストラクタ経由の生成経路をprotected隠蔽(ResourceクラスのMakeShared関数経由でしか新規生成は できない)
    DataPly(std::string filePath = "", bool isGPUBufferRendering = false)
        : Resource(filePath, Type::Ply), isGPUBufferRendering{ isGPUBufferRendering } {}
public:
    size_t numIndex = 0;
    int VertexBufHandle{ -1 }; // GPU側の頂点バッファへのハンドル番号
    int IndexBufHandle{ -1 }; // GPU側のインデックスバッファへのハンドル番号
    bool isSendDataToGPU = false; // GPUへデータを転送済みか
    bool isGPUBufferRendering; // GPUバッファにVertexとIndexデータを送ってCPU側にはデータをもたないようにするか?
    std::vector<uint32_t> Index; // 3Dモデルのポリゴンのインデックス配列
    std::vector<VERTEX3D> Vertex; // 3Dモデルのポリゴンの頂点配列

    Ply::Data Data; //.plyのデータ

    bool isInitialized = false; //[継承2重ロード対策]ロード済みになったらtrueに falseのときにLoadするとリロード

    virtual ~DataPly()
    {// 仮想デストラクタ
        clear();// 2次元配列データのお掃除
    };

    virtual void clearCPUData() // CPU上の配列データをクリアする
    {   // [確実にメモリを空にするには] http://vivi.dyndns.org/tech/cpp/vector.html#shrink_to_fit
        std::vector<VERTEX3D>().swap(Vertex); // 空のテンポラリオブジェクトでリセット
        std::vector<unsigned int>().swap(Index); // 空のテンポラリオブジェクトでリセット
    }

    virtual void clearGPUData() // GPU上のバッファデータをクリアする
    {
        DeleteVertexBuffer(VertexBufHandle); // GPUの頂点バッファを削除する
        DeleteIndexBuffer(IndexBufHandle); // GPUのインデックスバッファを削除する
        VertexBufHandle = -1; IndexBufHandle = -1;
        isSendDataToGPU = false;
        isGPUBufferRendering = false;
    }

    // データをクリアしてメモリを節約する
    virtual void clear()
    {
        Data.clear();
        clearCPUData(); // CPU上の配列データをクリアする
        clearGPUData(); // GPUのインデックスバッファを削除する
        isInitialized = false; //ロード済みフラグをOFF
    }

    // ファイルの読み込み
    virtual int Load(const std::string& filePath = "") override
    {
        if (filePath == "" && m_FilePath == "") return -1; //ファイル名がない
        else if (files.count(filePath) > 0 && isInitialized) return 0; // すでに辞書にロード済みファイルがあった
        else if (filePath != "" && m_FilePath == "") m_FilePath = filePath; // 読み出しファイルパスを記録
        clear(); //データを一旦クリア

        Ply::load(m_FilePath, Data); // .plyデータをロード

        auto vertex = Data["vertex"]; // 頂点へのアクセス
        assert(vertex->size() != 0 && "Plyデータの頂点サイズが0です。ファイルのパスは大丈夫ですか?");
        Vertex.resize(vertex->size()); // 配列の数の事前確保

        // [std::shared_ptrへthis↓ポインタをつかうためには] https://www.kabuku.co.jp/developers/cpp_enable_shared_from_this
        files.emplace(m_FilePath, shared_from_this()); // リソースの辞書へ登録 https://cpprefjp.github.io/reference/memory/enable_shared_from_this.html

        // [.plyの頂点のプロパティ情報]https://jp.mathworks.com/help/vision/ug/the-ply- format.html
        const float* p_x = vertex->properties["x"]->ptr<float>();
        const float* p_y = vertex->properties["y"]->ptr<float>();
        const float* p_z = vertex->properties["z"]->ptr<float>();
        const float* p_nx = (vertex->properties.has_key("nx")) ? vertex->properties["nx"]->ptr<float>() : nullptr;
        const float* p_ny = (vertex->properties.has_key("ny")) ? vertex->properties["ny"]->ptr<float>() : nullptr;
        const float* p_nz = (vertex->properties.has_key("nz")) ? vertex->properties["nz"]->ptr<float>() : nullptr;
        bool has_norm = !(p_nx == nullptr || p_ny == nullptr || p_nz == nullptr);
        const float* p_s = (vertex->properties.has_key("s")) ? vertex->properties["s"]->ptr<float>() : nullptr; // Blender書き出しでは(u,v)ではなく(s,t)として書き出されている
        const float* p_t = (vertex->properties.has_key("t")) ? vertex->properties["t"]->ptr<float>() : nullptr;

        const unsigned char* p_R = (vertex->properties.has_key("red")) ? vertex->properties["red"]->ptr<unsigned char>()
            : (vertex->properties.has_key("r")) ? vertex->properties["r"]->ptr<unsigned char>() : nullptr;
        const unsigned char* p_G = (vertex->properties.has_key("green")) ? vertex->properties["green"]->ptr<unsigned char>()
            : (vertex->properties.has_key("g")) ? vertex->properties["g"]->ptr<unsigned char>() : nullptr;
        const unsigned char* p_B = (vertex->properties.has_key("blue")) ? vertex->properties["blue"]->ptr<unsigned char>()
            : (vertex->properties.has_key("b")) ? vertex->properties["b"]->ptr<unsigned char>() : nullptr;
        const unsigned char* p_A = (vertex->properties.has_key("alpha")) ? vertex->properties["alpha"]->ptr<unsigned char>()
            : (vertex->properties.has_key("a")) ? vertex->properties["a"]->ptr<unsigned char>() : nullptr;
        bool has_color = !(p_R == nullptr || p_G == nullptr || p_B == nullptr || p_A == nullptr);

        for (size_t i = 0, iEnd = Vertex.size(); i < iEnd; i += 1)
        {
            // 頂点のデータをセット 各ツールの系一覧 https://zenn.dev/it_ks/articles/cbe27860548ea1
            //Vertex[i].pos = VGet(p_x[i], p_y[i], p_z[i]); // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
            //Vertex[i].pos = VGet(p_x[i], -p_y[i], p_z[i]); // 右手Y↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
            //Vertex[i].norm = (has_norm) ? VNorm(VGet(p_nx[i], -p_ny[i], p_nz[i])) : VGet(0.0f, 0.0f, 0.0f);
           
            // 右手Z↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
            Vertex[i].pos = VGet(-(p_y[i]), p_z[i], p_x[i]);
            Vertex[i].norm = (has_norm) ? VNorm(VGet(-p_ny[i], p_nz[i], p_nx[i])) : VGet(0.0f, 0.0f, 0.0f);
            Vertex[i].dif = (has_color) ? GetColorU8(p_R[i], p_G[i], p_B[i], p_A[i]) : GetColorU8(255, 255, 255, 255);
            Vertex[i].spc = GetColorU8(255, 255, 255, 255);
            Vertex[i].u = (p_s == nullptr) ? 0.0f : p_s[i];
            Vertex[i].v = (p_t == nullptr) ? 0.0f : 1 - p_t[i];
            Vertex[i].su = -1.0f;
            Vertex[i].sv = 0.0f;
        }

        const auto& face_index = Data["face"]->properties["vertex_indices"];
        const float* face_uv = (Data["face"]->properties.has_key("texcoord")) ? Data["face"]->properties["texcoord"]->ptr<float>() : nullptr;
        Index.resize(face_index->size() * 3);

        for (size_t i = 0, iEnd = face_index->size(), nFace = 0; i < iEnd; i += 3)// i + face_index->listCounts[nFace], ++nFace)
        {
            //size_t count = face_index->listCounts[nFace]; // ポリゴンならインデックス数は3 四角形ならインデックス数は4 .. 5 .. 6
            //size_t nPolygon = count - 2; // 三角ポリゴンの数 = インデックスの数 - 2 (例) 3 - 2 = 1ポリゴン , 4 - 2 = 2ポリゴン ..
            //for (size_t n = 0; n < nPolygon; ++n)
            {
                //for (size_t j = 0; j < 3; ++j) // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
                for (size_t j = 2; j + 1 > 0; --j) // 左手系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
                {
                    unsigned int index = face_index->at<unsigned int>(i + j);
                    const size_t uvIndex = 2 * (i + j); // uとv 2つ * ( i + j )
                    if (Vertex[index].su != -1.0f) // su sv の suが初期値-1でなく設定済なら頂点の被りとして新しいnewIndexとして新規Index番号で点を新規登録
                    {
                        const unsigned int newIndex = Vertex.size();
                        Vertex.emplace_back(Vertex[index]);
                        index = newIndex;
                        Index.push_back(newIndex);
                    }
                    Vertex[index].su = 0.0f;

                    //Index[i + j] = index;   // 右手系(裏面は時計回り)
                    Index[i + 2 - j] = index; // 左手系(裏面は反時計回り)
                    if (face_uv != nullptr)
                    {
                        Vertex[index].u = face_uv[uvIndex];
                        Vertex[index].v = 1.0f - face_uv[uvIndex + 1];
                    }

                    // 三角形の2辺のベクトルの外積から法線を計算(法線がないとライトの光の反射方向をシェーディングできないから)
                    if (j == 0 && !has_norm && Vertex[index].norm.x == 0.0f && Vertex[index].norm.y == 0.0f && Vertex[index].norm.z == 0.0f)
                    {
                        const unsigned int index0 = Index[i];
                        const unsigned int index1 = Index[i + 1];
                        const unsigned int index2 = Index[i + 2];

                        // 外積 a×b cross_a_b を求める https://examist.jp/mathematics/space-vector/vector-gaiseki/
                        const VECTOR a = VSub(Vertex[index1].pos, Vertex[index0].pos);
                        const VECTOR b = VSub(Vertex[index2].pos, Vertex[index0].pos);
                        // 左手系(表面は時計回り) 右手系と左手系では裏面の時計回りと反時計回りが変わる
                        const VECTOR cross1_2 = VCross(a, b);
                        // 右手系(表面は反時計回り)
                        //const VECTOR cross2_1 = VCross(b, a);

                        // 単位ベクトル(ノルム)に直す [単位ベクトル]は√(x*x + y*y + z*z) = 1となる
                        const VECTOR norm1_2 = VNorm(cross1_2);
                        //const VECTOR norm2_1 = VNorm(cross2_1);
                        Vertex[index0].norm = norm1_2; Vertex[index1].norm = norm1_2; Vertex[index2].norm = norm1_2;
                        //Vertex[index0].norm = norm2_1; Vertex[index1].norm = norm2_1; Vertex[index2].norm = norm2_1;
                    }

                    ++numIndex;
                }
            }
        }
        if (isGPUBufferRendering)
            SendDataToGPU(); // GPUのバッファへVertexとIndexデータ配列を送信

        isInitialized = true;

        return 0;
    }

    void SendDataToGPU() // GPUのバッファへVertexとIndexデータ配列を送信
    {
        int result = false;
        VertexBufHandle = DxLib::CreateVertexBuffer(Vertex.size(), DX_VERTEX_TYPE_SHADER_3D); // 頂点バッファを作成
        result = DxLib::SetVertexBufferData(0, Vertex.data(), Vertex.size(), VertexBufHandle); // 頂点バッファに頂点データを転送する
        //UpdateVertexBuffer(VertexBufHandle, UpdateStartIndex, UpdateVertexNum); // 頂点バッファハンドルの頂点バッファへの変更を適用する( GetBufferVertexBuffer で取得したバッファへの変更を反映する )
        if (result == -1) return;
       
        IndexBufHandle = DxLib::CreateIndexBuffer(Index.size(), DX_INDEX_TYPE_32BIT); // インデックスバッファを作成
        result = DxLib::SetIndexBufferData(0, Index.data(), Index.size(), IndexBufHandle); // インデックスバッファにインデックスデータを転送する
        if (result == -1) return;

        isGPUBufferRendering = true;
        isSendDataToGPU = true;
    }
};

#endif


main.cppのコードを.ply形式の3DデータをDataPly構造体経由で読み込む形に変更する。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include "Input.h"
#include "Screen.h"
#include "Ply.h"
#include "DataMV1.h"
#include "DataPly.h"

#include "Editor.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズWidth×Heightのカラービット数32ビットで起動
    SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    SetDrawScreen(DX_SCREEN_BACK);
  
    // ゲームのwhileループを開始する前の初期化処理
    float animTime = 0.0f; // アニメの現在時刻
    float animSpeed = 2.0f; // MV1のアニメの再生速度設定 2.0fなら2倍速
    float animStepTime = 1.0f; // MV1のアニメの毎時間の進む時間の設定
    std::shared_ptr<DataMV1> pDevilData = Resource::MakeShared<DataMV1>("Image/devil@kick.mv1");
    pDevilData->Load(); // 3Dモデルをmv1形式で読込み
    std::shared_ptr<DataMV1> pDevilAttackAnimData = Resource::MakeShared<DataMV1>("Image/devil@jump.mv1");
    pDevilAttackAnimData->Load(); // 3Dの追加アニメデータをmv1形式で読込み
    pDevilData->AttachAnim(*pDevilAttackAnimData); // アニメを追加でflyのほうをベースとしたモデルにアタッチ
    int animeButton = 0; // ボタンでアニメを切り替えられるようにする

    std::shared_ptr<Texture> pTexImage = Resource::MakeShared<Texture>("Image/dice.png");
    pTexImage->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
    assert((int)*pTexImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる

    std::shared_ptr<DataPly> pPlyData = Resource::MakeShared<DataPly>("Image/dice.ply");
    pPlyData->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)

   
    Ply::Data Data; // .plyのデータのクラス
    Ply::load("Image/dice.ply", Data); // .plyデータをロード 軸を変えてdice1.plyや dice2.plyなどいろいろ試してみよう


    size_t numIndex = 0; // インデックスの数
    std::vector<uint32_t> Index; // 3Dモデルのポリゴンのインデックス配列
    std::vector<VERTEX3D> Vertex; // 3Dモデルのポリゴンの頂点配列

    auto vertex = Data["vertex"]; // 頂点へのアクセス
    assert(vertex->size() != 0 && "Plyデータの頂点サイズが0です。ファイルのパスは大丈夫ですか?");
    Vertex.resize(vertex->size()); // 配列の数の事前確保

    // [.plyの頂点のプロパティ情報] https://jp.mathworks.com/help/vision/ug/the-ply-format.html
    const float* p_x = vertex->properties["x"]->ptr<float>();
    const float* p_y = vertex->properties["y"]->ptr<float>();
    const float* p_z = vertex->properties["z"]->ptr<float>();
    const float* p_nx = (vertex->properties.has_key("nx")) ? vertex->properties["nx"]->ptr<float>() : nullptr;
    const float* p_ny = (vertex->properties.has_key("ny")) ? vertex->properties["ny"]->ptr<float>() : nullptr;
    const float* p_nz = (vertex->properties.has_key("nz")) ? vertex->properties["nz"]->ptr<float>() : nullptr;
    bool has_norm = !(p_nx == nullptr || p_ny == nullptr || p_nz == nullptr);
    const float* p_s = (vertex->properties.has_key("s")) ? vertex->properties["s"]->ptr<float>() : nullptr; // Blender書き出しでは(u,v)ではなく(s,t)として書き出されている
    const float* p_t = (vertex->properties.has_key("t")) ? vertex->properties["t"]->ptr<float>() : nullptr;

    const unsigned char* p_R = (vertex->properties.has_key("red")) ? vertex->properties["red"]->ptr<unsigned char>()
        : (vertex->properties.has_key("r")) ? vertex->properties["r"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_G = (vertex->properties.has_key("green")) ? vertex->properties["green"]->ptr<unsigned char>()
        : (vertex->properties.has_key("g")) ? vertex->properties["g"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_B = (vertex->properties.has_key("blue")) ? vertex->properties["blue"]->ptr<unsigned char>()
        : (vertex->properties.has_key("b")) ? vertex->properties["b"]->ptr<unsigned char>() : nullptr;
    const unsigned char* p_A = (vertex->properties.has_key("alpha")) ? vertex->properties["alpha"]->ptr<unsigned char>()
        : (vertex->properties.has_key("a")) ? vertex->properties["a"]->ptr<unsigned char>() : nullptr;
    bool has_color = !(p_R == nullptr || p_G == nullptr || p_B == nullptr || p_A == nullptr);

    for (size_t i = 0, iEnd = Vertex.size(); i < iEnd; i += 1)
    {
        // 頂点のデータをセット 各ツールの系一覧 https://zenn.dev/it_ks/articles/cbe27860548ea1
        //Vertex[i].pos = VGet(p_x[i], p_y[i], p_z[i]); // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        //Vertex[i].pos = VGet(p_x[i], -p_y[i], p_z[i]); // 右手Y↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        //Vertex[i].norm = (has_norm) ? VNorm(VGet(p_nx[i], -p_ny[i], p_nz[i])) : VGet(0.0f, 0.0f, 0.0f);

        // 右手Z↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        Vertex[i].pos = VGet(-(p_y[i]), p_z[i], p_x[i]);
        Vertex[i].norm = (has_norm) ? VNorm(VGet(-p_ny[i], p_nz[i], p_nx[i])) : VGet(0.0f, 0.0f, 0.0f);
        Vertex[i].dif = (has_color) ? GetColorU8(p_R[i], p_G[i], p_B[i], p_A[i]) : GetColorU8(255, 255, 255, 255);
        Vertex[i].spc = GetColorU8(255, 255, 255, 255);
        Vertex[i].u = (p_s == nullptr) ? 0.0f : p_s[i];
        Vertex[i].v = (p_t == nullptr) ? 0.0f : 1 - p_t[i];
        Vertex[i].su = -1.0f;
        Vertex[i].sv = 0.0f;
    }

    const auto& face_index = Data["face"]->properties["vertex_indices"];
    Index.resize(face_index->size() * 3);

    for (size_t i = 0, iEnd = face_index->size(), nFace = 0; i < iEnd; i += 3)
    {
        //for (size_t j = 0; j < 3; ++j) // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
        for (size_t j = 2; j + 1 > 0; --j) // 左手系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
        {
            unsigned int index = face_index->at<unsigned int>(i + j);
            const size_t uvIndex = 2 * (i + j); // uとv 2つ * ( i + j )

            //Index[i + j] = index;   // 右手系(裏面は時計回り)
            Index[i + 2 - j] = index; // 左手系(裏面は反時計回り)

            // 三角形の2辺のベクトルの外積から法線を計算(法線がないとライトの光の反射方向をシェーディングできないから)
            if (j == 0 && !has_norm && Vertex[index].norm.x == 0.0f && Vertex[index].norm.y == 0.0f && Vertex[index].norm.z == 0.0f)
            {
                const unsigned int index0 = Index[i];
                const unsigned int index1 = Index[i + 1];
                const unsigned int index2 = Index[i + 2];

                // 外積 a×b cross_a_b を求める https://examist.jp/mathematics/space-vector/vector-gaiseki/
                const VECTOR a = VSub(Vertex[index1].pos, Vertex[index0].pos);
                const VECTOR b = VSub(Vertex[index2].pos, Vertex[index0].pos);
                // 左手系(表面は時計回り) 右手系と左手系では裏面の時計回りと反時計回りが変わる
                const VECTOR cross1_2 = VCross(a, b);
                // 右手系(表面は反時計回り)
                //const VECTOR cross2_1 = VCross(b, a);

                // 単位ベクトル(ノルム)に直す [単位ベクトル]は√(x*x + y*y + z*z) = 1となる
                const VECTOR norm1_2 = VNorm(cross1_2);
                //const VECTOR norm2_1 = VNorm(cross2_1);
                Vertex[index0].norm = norm1_2; Vertex[index1].norm = norm1_2; Vertex[index2].norm = norm1_2;
                //Vertex[index0].norm = norm2_1; Vertex[index1].norm = norm2_1; Vertex[index2].norm = norm2_1;
            }

            ++numIndex;
        }
    }



    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 0.0f; keyControlXYZ.y = 0.0f; keyControlXYZ.z = 0;
    VECTOR keyControlAngle = VGet(0.0f,0.0f,0.0f); // 回転

        // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen1 = SubScreen::Create()->Set(600, 450, 600, 100, 255, 0, "サブスクリーン1")->SetIsBarFrameShow(true,true);
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen2 = SubScreen::Create()->Set(200, 100, 150, 100, 255, 1, "サブスクリーン2\nバーを30で太めに")->SetFrame(1,GetColor(255,255,255),true,30);
    // サブのスクリーンを生成する 透明度を128にして半透明にしてみる
    std::shared_ptr<SubScreen> subscreen3 = SubScreen::Create()->Set(150, 450, 300, 100, 128, 1, "サブスクリーン3")->SetIsBarFrameShow(true)->SetBarColor(GetColor(0, 0, 255));
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen4 = SubScreen::Create()->Set(300, 100, 600, 100, 255, 1, "サブスクリーン4");

    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    Editor& editor = Editor::GetInstance(); // エディタのシングルトン参照を得る

    //奥行0.1~5000までをカメラの描画範囲とする
    DxLib::SetCameraNearFar(0.1f, 5000.0f);

    ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Input::Update(); // マウスのドラッグなどを更新
        SubScreen::UpdateScreens(); // スクリーンの位置やドラッグによる移動を更新する

        editor.UpdateKeyInput(); // エディタのキーのインプットを更新

        editor.UpdateMouseInput(); // エディタのマウスのインプットを更新
        editor.UpdateCamera(); // エディタのカメラを更新
       
        // キーボードの 0 1 キーで3Dアニメを切り替えられるようにする
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_0))
            animeButton = 0;
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_1))
            animeButton = 1;

        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        VECTOR position = VGet(keyControlXYZ.x, keyControlXYZ.y, keyControlXYZ.z); // 3Dモデルの位置

        float width =  pTexImage->m_XSize; float height =  pTexImage->m_YSize; // テクスチャ画像のサイズ
        VECTOR scale = VGet(1.0f, 1.0f, 1.0f); // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)とcキー(C)でx,y,z軸まわりに画像を回転
        if (PadInput & PAD_INPUT_A) keyControlAngle.x += speed;
        if (PadInput & PAD_INPUT_B) keyControlAngle.y += speed;
        if (PadInput & PAD_INPUT_C) keyControlAngle.z += speed;

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数
        const float xRotate = keyControlAngle.x * Deg2Rad;  const float yRotate = keyControlAngle.y * Deg2Rad; const float zRotate = keyControlAngle.z * Deg2Rad;


        MATRIX  matWorldDefault; // デフォルトのワールド行列
        DxLib::GetTransformToWorldMatrix(&matWorldDefault); // デフォルトのワールド行列を記憶しておく
        {
            //[ワールド行列] https://ny-program.hatenablog.com/entry/2019/09/15/114245
            MATRIX mat; // モデルのスケール×回転×移動を行列計算で設定する

            //[スケール]拡大縮小のスケール行列を得る
            mat = MGetScale(scale); // まずは拡大縮小のスケール行列からはじめる
            // [勉強]↓スケール行列は 斜めに x , y , zのスケールの倍率がならんでいる行列
            //mat.m[0][0] = scale.x;  mat.m[0][1] = 0.0f;    mat.m[0][2] = 0.0f;     mat.m[0][3] = 0.0f;
            //mat.m[1][0] = 0.0f;     mat.m[1][1] = scale.y; mat.m[1][2] = 0.0f;     mat.m[1][3] = 0.0f;
            //mat.m[2][0] = 0.0f;     mat.m[2][1] = 0.0f;    mat.m[2][2] = scale.z;  mat.m[2][3] = 0.0f;
            //mat.m[3][0] = 0.0f;     mat.m[3][1] = 0.0f;    mat.m[3][2] = 0.0f;     mat.m[3][3] = 1.0f;

            //[回転]
            MATRIX matRot;
            DxLib::CreateIdentityMatrix(&matRot);
            // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
            matRot = MMult(matRot, MGetRotZ(zRotate));
            matRot = MMult(matRot, MGetRotX(xRotate));
            matRot = MMult(matRot, MGetRotY(yRotate));
            mat = MMult(mat, matRot); //スケール × 回転

            //[移動]
            MATRIX matTrans = MGetTranslate(VGet(position.x, position.y, position.z)); // 位置positionをここで設定
            mat = MMult(mat, matTrans); //スケール×回転×移動

            DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4341

            DxLib::DrawPolygon32bitIndexed3D(pPlyData->Vertex.data(), pPlyData->Vertex.size(), pPlyData->Index.data(), pPlyData->numIndex / 3, (int)*pTexImage, FALSE); // 三角形ポリゴンをたくさん描画
        }
        DxLib::SetTransformToWorld(&matWorldDefault); // デフォルトのワールド行列に戻しておく

        DxLib::MV1DrawModel(devil3DModel); // 3Dモデル(.mv1形式)を描く

        DxLib::DrawFormatString(0, 100, GetColor(255, 255, 0), "x:%f y:%f Angle:%f %f %f", position.x, position.y, keyControlAngle.x, keyControlAngle.y, keyControlAngle.z);

        // ファイルのパス + ":" + アニメ名でアクセス
        std::string animPathName = (animeButton == 0) ? "Image/devil@kick.mv1:mixamo.com" : "Image/devil@jump.mv1:mixamo.com";
        assert(pDevilData->count(animPathName) > 0 && "指定されたアニメ名のアニメはMV1に付属していませんでした");
        animTime += animStepTime * animSpeed; // 1.0×2倍 なら2倍速でアニメ時刻が進む
        if (animTime > (*pDevilData)[animPathName].endTime)
            animTime = 0.0f; // 0秒目へとアニメをループさせる

        // 再生時間をセットする
        DxLib::MV1SetAttachAnimTime((int)*pDevilData, (*pDevilData)[animPathName].index, animTime);
      
        subscreen1->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen1->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // サブの別のスクリーンの(0,0)位置を起点に文字列を描く
            DxLib::DrawString(0, 0, "サブのスクリーンを使ってWindowsみたいな自作ウインドウを描画してみる", GetColor(255, 255, 255));
        }
        subscreen1->DrawEnd();

        subscreen3->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen3->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // 半透明のスクリーンも描ける
            DxLib::DrawBox(0, 0, subscreen3->Width, subscreen3->Height, GetColor(0, 255, 0), TRUE);
            DxLib::DrawString(0, 0, "アルファを設定すれば\n半透明スクリーンも可能", GetColor(255, 255, 255));
        }
        subscreen3->DrawEnd();

        subscreen4->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen4->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // サブの別のスクリーンの(0,0)位置を起点に文字列を描く
            DxLib::DrawString(0, 0, "枠の無いスクリーンを使えば2Pの分割画面などにも応用できる", GetColor(255, 255, 255));
        }
        subscreen4->DrawEnd();


        SubScreen::DrawScreens(); // サブのスクリーンを全部描く

        editor.Draw(); // エディタのDraw処理

        ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    WaitKey();
    // DXライブラリの後始末
    DxLib_End();
    // ソフトの終了
    return 0;
}

いかがでしょう? devilのデータに隠れて見えにくいですが、ダイスの.ply形式のデータが今まで通り表示されていますでしょうか?


Vector3に色々な関数を用意して3D空間の当たり判定をする


Vector3.hを新規作成してxyzの3D座標を取り扱えるようにして、さらに様々な当たり判定に使うための関数(線などとの最短距離を計算する)もセットで導入しておきましょう。

#ifndef VECTOR3_H_
#define VECTOR3_H_

#include "DxLib.h"

#include <array> //vector配列と基本同じだが【有限】の個数である違いがあるxyzなど3つ固定の個数ならstd::vectorよりstd::array型がよい
#include <cmath>
#include <limits> // 無限大などをstd::numeric_limits<float>::infinity()で取得
#include <float.h> // FLT_EPSILONでfloat型のとりうる最小の極小値を得る
#include <memory> // 共有ポインタを使う

#ifndef GET_CLAMP // GET_CLAMPマクロの定義がなかったら定義して使う
// a と b 小さい方の値を求める
#define    GET_MIN( a, b )            ( ( (a) < (b) ) ? (a) : (b) )
// a と b 大きい方の値を求める
#define    GET_MAX( a , b )        ( ( (a) > (b) ) ? (a) : (b) )
// aを min から max の値に留める
#define    GET_CLAMP( a, min, max )    GET_MAX( min, GET_MIN( a, max ) )
#endif

// 軸のタイプ X軸:Axis3D::X, Y軸:Axis3D::Y, Z軸:Axis3D::Z
enum class Axis3D { X = 0, Y, Z };

//★【UnityのVector演算コード】
// https://github.com/Unity-Technologies/UnityCsReference/blob/02d565cf3dd0f6b15069ba976064c75dc2705b08/Runtime/Export/Math/Vector3.cs

// 3変数x,y,zを持つ構造体。3つの数字をまとめて扱うならXYZ座標以外にも使える。x,y,z別々に足したり引いたり面倒なときこそ
struct Vector3
{
    // struct型はpublic:がなくてもデフォルトがpublic:  private:を書けばプライベートにもできる
      //float x = 0; // x座標
      //float y = 0; // y座標
      //float z = 0; // z座標 3つの定義をunionで配列xyzと共用することでxyz[1]=5.5f;とすると yも5.5となる【運命共用体】(メモリ上は同じ位置になる)

    union { // ★共用体unionテクニック https://inemaru.hatenablog.com/entry/2016/03/02/005408
        struct { // [参考ビットサイズ] https://marycore.jp/prog/c-lang/data-type-ranges-and-bit-byte-sizes/
            float x;// x座標
            float y;// y座標
            float z;// z座標
        }; //[匿名共用体とは] https://zenn.dev/block/articles/beda29f11f05afc147ef
        std::array<float, 3> xyz; // float xyz[3];と同じ意味 float 3個ぶんのデータサイズでx,y,z 3個ぶんと一致するので★unionで共用
        struct { float r, g, b; }; // 赤緑青のRGB成分としてつかいたいとき
#ifdef DX_LIB_H // DXlibを#includeしていればDX_LIB_Hも定義済みなので下記も定義される VECTORの定義はDXライブラリ版のVector3
        VECTOR dxVec;
#endif
    }; // unionは異なる複数のものをメモリ上の同一の番地に割り当てられる⇒x,y,z分けて記述するの面倒なとき配列xyz[3]をfor文i=0~3で回せる

    // float型のポインタへ(float*)キャストすればx,y,zデータの配列の先頭のアドレスを返す
    inline operator float* () const { return (float*)this; }
    // float型のポインタへ(const float*)キャストすればx,y,zデータの配列の先頭のアドレスを返す
    inline operator const float* () const { return (const float*)this; }

    // コンストラクタ 初期化 z = 0.fでzのデフォルト値があるので2Dとしても使える
    inline Vector3(float x = 0.0f, float y = 0.0f, float z = 0.0f)
    {
        this->xyz = { x,y,z };// たった1行で書ける
    }
    // デストラクタ
    ~Vector3() {}

    // Unityのstatic変数を参考に https://docs.unity3d.com/ja/current/ScriptReference/Vector3.html
    static const Vector3 zero;// = { 0.f, 0.f, 0.f };
    static const Vector3 one; // = { 1.f, 1.f, 1.f };
    static const Vector3 forward;// = { 0.f, 0.f, 1.f };
    static const Vector3 back; // = { 0.f, 0.f, -1.f };
    static const Vector3 up; // = { 0.f, 1.f, 0.f };
    static const Vector3 down;// = { 0.f, -1.f, 0.f };
    static const Vector3 left; // = {-1.f, 0.f, 0.f };
    static const Vector3 right;// = { 1.f, 0.f, 0.f };
    //[負の値の最小値] https://stackoverflow.com/questions/20016600/negative-infinity
    static const Vector3 negativeInfinity;
    //[極大値]https://cpprefjp.github.io/reference/limits/numeric_limits/lowest.html
    static const Vector3 positiveInfinity;

    // destArrayにデータを保管しておく
    Vector3& copyToArray(std::array<float, 3>& destArray)
    {
        destArray = this->xyz;//配列としてコピー
        return *this;
    }
    // sourceArray配列からVector3データを生成
    Vector3 fromArray(std::array<float, 3>& sourceArray) const //[後ろにconstをつけないと..] https://www7b.biglobe.ne.jp/~robe/cpphtml/html02/cpp02010.html
    {
        return Vector3{ sourceArray[0], sourceArray[1], sourceArray[2] }; //配列から初期化
    }


    // ベクトルの長さ(原点0から点(x,y,z)までの距離)
    inline float magnitude() const //[後ろにconstをつけないと..] https://www7b.biglobe.ne.jp/~robe/cpphtml/html02/cpp02010.html
    {
        return std::sqrt(x * x + y * y + z * z); // √(x*x + y*y + z*z)
    };

    // ベクトルの 2 乗の長さを返します(平方根しないぶん計算速い)
    inline float sqrMagnitude() const //[後ろにconstをつけないと..] https://www7b.biglobe.ne.jp/~robe/cpphtml/html02/cpp02010.html
    {
        return x * x + y * y + z * z; // x*x + y*y + z*z
    };


    // 正規化したベクトルを返す
    inline Vector3 normalized() const //[後ろにconstをつけないと..] https://www7b.biglobe.ne.jp/~robe/cpphtml/html02/cpp02010.html
    {
        float mag = magnitude();
        if (mag < 0.00001f) // ほぼ0ベクトルか?
            return *this;
        else
            return Vector3{ x / mag, y / mag, z / mag }; // x / |x|, y / |y|, z / |z|
    }


    /*----- 演算子オーバーロード -----*/

    // 逆ベクトル
    inline Vector3 operator - () const
    {
        return Vector3(-this->x, -this->y, -this->z);
    }

    // Vectorをそのまま足し合わせる
    inline Vector3 operator + () const
    {
        return *this;
    }

    inline Vector3& operator += (const Vector3& add_v3)
    {
        this->x += add_v3.x;
        this->y += add_v3.y;
        this->z += add_v3.z;

        return *this; //[関数連鎖] *thisを返すことで v1 + v2 + v3見たく数珠繋ぎできる
    }

    inline Vector3& operator -= (const Vector3& minus_v3)
    {
        this->x -= minus_v3.x;
        this->y -= minus_v3.y;
        this->z -= minus_v3.z;

        return *this;
    }

    inline Vector3& operator *= (float multiply_num)
    {
        this->x *= multiply_num;
        this->y *= multiply_num;
        this->z *= multiply_num;

        return *this;
    }

    // 0.fで割ったときは±無限大を返す
    inline Vector3& operator /= (float divide_num)
    {
        if (divide_num == 0.0f) // 0で割ったら±無限大を返す
        {
            *this = Vector3{ ((this->x < 0) ? 1 : -1) * std::numeric_limits<float>::infinity(),
                             ((this->y < 0) ? 1 : -1) * std::numeric_limits<float>::infinity(),
                             ((this->z < 0) ? 1 : -1) * std::numeric_limits<float>::infinity() };
            return *this;
        }

        this->x /= divide_num;
        this->y /= divide_num;
        this->z /= divide_num;

        return *this;
    }

    // 代入演算子 x,y,zを全部 change_numに変える
    inline Vector3& operator = (float change_num)
    {
        this->x = change_num;
        this->y = change_num;
        this->z = change_num;

        return *this;
    }

    // 一致演算子 x,y,zが全部一致するか 一つでも違えばfalse
    inline bool operator == (const Vector3& v3_other) const
    {
        if (this->x != v3_other.x) return false;
        if (this->y != v3_other.y) return false;
        if (this->z != v3_other.z) return false;
        return true;
    }

    // 不一致演算子
    inline bool operator != (const Vector3& v3_other) const
    {
        return    !(*this == v3_other);
    }

    /*---- 以下 DXライブラリ向けにVector3とDX版Vectorの変換方法を用意 ---------------*/

  // DXlibを#includeしていればDX_LIB_Hも定義済みなので下記関数も定義される VECTORの定義はDXライブラリ版のVector3
#ifdef DX_LIB_H
  // Vector3からDxLibのVECTORへの変換
    inline VECTOR Vec3ToVec()
    {
        VECTOR Result;
        Result.x = this->x;
        Result.y = this->y;
        Result.z = this->z;
        return Result;
    }

    // 左辺値がDXのVECTOR型のときの=代入対応
    inline operator VECTOR() const
    {
        VECTOR Result;
        Result.x = this->x;
        Result.y = this->y;
        Result.z = this->z;
        return Result;
    }

    // 右辺値がDXのVECTOR型のときの=代入対応
    inline Vector3& operator = (const VECTOR& other)
    {
        this->x = other.x;
        this->y = other.y;
        this->z = other.z;
        return *this;
    }

    // コンストラクタ DXのVECTOR型から = 初期化できるように
    inline Vector3(VECTOR other) : x{ other.x }, y{ other.y }, z{ other.z } {}

#endif

#ifdef BT_VECTOR3_H
    // 右辺値が 物理エンジンBullet の btVector3型ときの = 代入対応 (btVector3型:右手→左手:DirectX系 位置変換)
    inline Vector3& operator = (const btVector3& right)
    {   // Z軸にはマイナスをつけて右手系を左手系に変換 http://dolpetticoat.blog.fc2.com/blog-entry-92.html
        x = (float)right.getX(), y = (float)right.getY(), z = -(float)right.getZ();
        return *this;
    }

    // コンストラクタ 物理エンジンBullet の btVector3型 から = 初期化できるように (btVector3型:右手→左手:DirectX系 位置変換)
    inline Vector3(const btVector3& other) : x{ (float)other.getX() }, y{ (float)other.getY() }, z{ -(float)other.getZ() } {}
   
    inline operator btVector3() const { return btVector3{x, y, -z}; }
#endif
};

#ifdef BT_VECTOR3_H

// btVector3型:右手→左手:DirectX系 オイラー回転を変換 http://dolpetticoat.blog.fc2.com/blog-entry-92.html
inline Vector3 rotationXYZ(const btVector3& eulerZYX) { return Vector3{ -(float)eulerZYX.getX(), -(float)eulerZYX.getY(),(float)eulerZYX.getZ() }; }

#endif


// Vector3どうしの足し算 割り算 掛け算 割り算などの 基本演算の グローバル定義


// Vector同士の足し算 x,y,z個別に足し合わせる
inline Vector3 operator + (const Vector3& left, const Vector3& right)
{
    Vector3 v3; //注意 vec1 = vec1 + vec2のとき vec1の数値が書き変わったら嫌だからv3を新たに用意
    v3.x = left.x + right.x; //[コレはダメ] left.x = left.x + right.x;
    v3.y = left.y + right.y;
    v3.z = left.z + right.z;

    return v3;
}

// Vector同士の引き算 x,y,z個別に足し合わせる
inline Vector3 operator - (const Vector3& left, const Vector3& right)
{
    Vector3 v3; //注意 vec1 = vec1 - vec2のとき vec1の数値が書き変わったら嫌だからv3を新たに用意
    v3.x = left.x - right.x; //[コレはダメ] left.x = left.x - right.x;
    v3.y = left.y - right.y;
    v3.z = left.z - right.z;

    return v3;
}

// Vectorと数値の掛け算 x,y,z個別に掛け合わせる
inline Vector3 operator * (const Vector3& left, float right)
{
    Vector3 v3;
    v3.x = left.x * right;
    v3.y = left.y * right;
    v3.z = left.z * right;

    return v3;
}


// 数値とVectorの掛け算 x,y,z個別に掛け合わせる
inline Vector3 operator * (float left, const Vector3& right)
{
    Vector3 v3;
    v3.x = left * right.x;
    v3.y = left * right.y;
    v3.z = left * right.z;

    return v3;
}

// Vectorと数値の割り算 x,y,z個別に割り合わせる
// 0.fで割ると±無限大を返す
inline Vector3 operator / (const Vector3& left, float right)
{
    if (right == 0.0f) // 0で割ったら±無限大を返す
        return Vector3{ ((left.x < 0) ? 1 : -1) * std::numeric_limits<float>::infinity(),
                        ((left.y < 0) ? 1 : -1) * std::numeric_limits<float>::infinity(),
                        ((left.z < 0) ? 1 : -1) * std::numeric_limits<float>::infinity() };

    Vector3 v3;
    v3.x = left.x / right;
    v3.y = left.y / right;
    v3.z = left.z / right;

    return v3;
}

// 比較不等号 < 演算子
inline bool operator < (const Vector3& left, const Vector3& right)
{
    if (left.x != right.x) return left.x < right.x;
    if (left.y != right.y) return left.y < right.y;
    return left.z < right.z;
}

// 比較不等号 > 演算子
inline bool operator > (const Vector3& left, const Vector3& right)
{
    if (left.x != right.x) return left.x > right.x;
    if (left.y != right.y) return left.y > right.y;
    return left.z > right.z;
}

// 比較不等号 <= 演算子
inline bool operator <= (const Vector3& left, const Vector3& right)
{
    return    !(left > right);
}

// 比較不等号 >= 演算子
inline bool operator >= (const Vector3& left, const Vector3& right)
{
    return    !(left < right);
}

// 2つのベクトルの内積
inline float dot(const Vector3& left, const Vector3& right)
{
    return left.x * right.x + left.y * right.y + left.z * right.z; //普通にx同士,y同士,z同士掛けるだけ
}

// 2つのベクトルの外積
inline Vector3 cross(const Vector3& left, const Vector3& right)
{
    return Vector3{
      left.y * right.z - left.z * right.y, // x = 左y * 右z - 左z * 右y
      left.z * right.x - left.x * right.z, // y = 左z * 右x - 左x * 右z
      left.x * right.y - left.y * right.x };// z = 左x * 右y - 左y * 右x //行列独特の特殊な掛け方
}

// ベクトルの 2 乗の長さを返します(平方根しないぶん計算速い)
inline float sqrMagnitude(const Vector3& vec)
{
    return vec.x * vec.x + vec.y * vec.y + vec.z * vec.z; // x*x + y*y + z*z
}

// ベクトルの長さ(原点0から点(x,y,z)までの距離)
inline float magnitude(const Vector3& vec)
{
    return std::sqrt(sqrMagnitude(vec)); // √x*x + y*y + z*z
}

// start と end の間の距離
inline float distance(const Vector3& start, const Vector3& end)
{
    return (start - end).magnitude();
}

// 直線上にある2つのベクトル間を補間(start:開始位置  end:終了位置  t = 補間の割合 tは0~1の範囲にクランプしない)
inline Vector3 lerpUnclamped(const Vector3& start, const Vector3& end, float t)
{
    return start + (end - start) * t;
}

// 線形補間する start:開始位置  end:終了位置  t = 補間の割合(start側が100%  0.0~1.0  end側が100% でブレンド) tは0~1の範囲にクランプする
inline Vector3 lerp(const Vector3& start, const Vector3& end, float t)
{
    return lerpUnclamped(start,end, GET_CLAMP(t, 0.0f, 1.0f) );
}


// 2つのベクトルのx,y,zのそれぞれ大きいほうの数値で構成したベクトルを求める 例. v1(1,2,3) v2(3,2,1) なら(3,2,3)になる
inline Vector3 maximize(const Vector3& v1, const Vector3& v2)
{
    Vector3 result;
    result.x = (v1.x > v2.x) ? v1.x : v2.x;
    result.y = (v1.y > v2.y) ? v1.y : v2.y;
    result.z = (v1.z > v2.z) ? v1.z : v2.z;
    return result;
}


// 2つのベクトルのx,y,zのそれぞれ小さいほうの数値で構成したベクトルを求める 例. v1(1,2,3) v2(3,2,1) なら(1,2,1)になる
inline Vector3 minimize(const Vector3& v1, const Vector3& v2)
{
    Vector3 result;
    result.x = (v1.x < v2.x) ? v1.x : v2.x;
    result.y = (v1.y < v2.y) ? v1.y : v2.y;
    result.z = (v1.z < v2.z) ? v1.z : v2.z;
    return result;
}

// 2次補間する start:開始位置 mid:中間制御点 end:終了位置  t = 補間の割合(start側が100%  0.0~1.0  end側が100% でブレンド)
inline Vector3 quadratic(const Vector3& start, const Vector3& mid, const Vector3& end, float t)
{    //[ベジェ曲線補間の3点ver.] https://ja.javascript.info/bezier-curve
    return start * (1.0f - t) * (1.0f - t) + 2 * mid * t * (1.0f - t) + end * t * t;
}


// ベジェ曲線の補間をする start:開始位置 mid1:中間制御点1  mid2:中間制御点2 end:終了位置  t = 補間の割合(start側が100%  0.0~1.0  end側が100%)
inline Vector3 bezier(const Vector3& start, const Vector3& mid1, const Vector3& mid2, const Vector3& end, float t)
{    //[ベジェ曲線補間の4点ver.] https://ja.javascript.info/bezier-curve
    Vector3 vec;
    vec.x = start.x * (1.0f - t) * (1.0f - t) * (1.0f - t) + 3 * (1.0f - t) * (1.0f - t) * t * mid1.x + 3 * (1.0f - t) * t * t * mid2.x + t * t * t * end.x;
    vec.y = start.y * (1.0f - t) * (1.0f - t) * (1.0f - t) + 3 * (1.0f - t) * (1.0f - t) * t * mid1.y + 3 * (1.0f - t) * t * t * mid2.y + t * t * t * end.y;
    vec.z = start.z * (1.0f - t) * (1.0f - t) * (1.0f - t) + 3 * (1.0f - t) * (1.0f - t) * t * mid1.z + 3 * (1.0f - t) * t * t * mid2.z + t * t * t * end.z;
    return vec;
}


// 2つのベクトルの各成分をかけ算
inline Vector3 scale(const Vector3& left, const Vector3& right)
{
    return Vector3{ left.x * right.x, left.y * right.y, left.z * right.z };
}

// ベクトルを別のベクトルに投影
inline Vector3 project(const Vector3& vector, const Vector3& on_normal)
{
    float sqrMag = on_normal.sqrMagnitude();
    if (sqrMag < 0.00001f)
        return Vector3::zero;
    return on_normal * dot(vector, on_normal) / sqrMag;
}

// 平面に垂直な法線ベクトルにより定義される平面上にベクトルを射影
inline Vector3 projectOnPlane(const Vector3& vector, const Vector3& planeNormal)
{
    return vector - project(vector, planeNormal);
}

// 仰角:radPolar[単位:ラジアン] 方位角:radAzimuth[単位:ラジアン]から方位ベクトル(極座標の位置の単位ベクトル)を求める
inline Vector3 polarDir(float radPolar, float radAzimuth)
{   //[球体表面上の点へ向かう方位ベクトル] https://www.pentacreation.com/blog/2020/12/201225.html
    Vector3 result;
    result.x =  std::cos(radPolar) * std::sin(radAzimuth);
    result.y = -std::sin(radPolar);
    result.z =  std::cos(radPolar) * std::cos(radAzimuth);
    return result;
}

// 2つのベクトルの角度θを求める(単位:ラジアン)
inline float radAngle(const Vector3& start, const Vector3& end)
{
    float cos_theta = dot(start, end) / (start.magnitude() * end.magnitude());
    return std::acos( GET_CLAMP(cos_theta, -1.0f, 1.0f) );
}

// 三角ポリゴンの面法線を求める p0,p1,p2:三角ポリゴンの3点の位置ベクトル
inline Vector3 polygonNorm(const Vector3& p0, const Vector3& p1, const Vector3& p2)
{   //[DirectX:左手系(時計回りの親指が上)]
    // 直線p1p0 と 直線p2p1 の外積を求めて、それを単位ベクトルNorm化する
    return cross(p0 - p1, p1 - p2).normalized();
}

// 反射ベクトルを求める input:入射ベクトル  eNorm:単位法線ベクトル
inline Vector3 reflection(const Vector3& input, const Vector3& eNorm)
{   //                               内積↓
    // [計算式]反射ベクトル = 2 * ( -入射・法線 ) * 法線 + 入射
    return 2 * dot(-input, eNorm) * eNorm + input;
}

// 壁ずりベクトル(単位化済)を求める input:入射ベクトル  eNorm:単位法線ベクトル
inline Vector3 wallscratch(const Vector3& input, const Vector3& eNorm)
{   // http://marupeke296.com/COL_Basic_No5_WallVector.html
    return (input - dot(input, eNorm) * eNorm).normalized();
}

// 屈折ベクトルを求める input:入射ベクトル eNorm:単位法線ベクトル 屈折比率: n = n1 / n2 (入射角度:θ1 屈折角度:θ2)
inline Vector3 refraction(const Vector3& input, const Vector3& eNorm, float n)
{
    //[ベクトルの屈折] https://tyfkda.github.io/blog/2009/07/19/refraction.html
    Vector3 inverse = -input; // 逆ベクトル
    float cos_theta1 = dot(inverse, eNorm); // cosθ1 = -入射ベクトル・法線ベクトル
    float cosT2 = 1.0f - n * n * (1.0f - cos_theta1 * cos_theta1); //
    if (cosT2 <= 0.0f) return Vector3{ 0.0f, 0.0f, 0.0f }; // 屈折光はでてこない
    float abs_cosT2 = (cosT2 < 0) ? -cosT2 : cosT2;

    // 屈折比率: n = n1 / n2 (入射角度:θ1 屈折角度:θ2)
    // 屈折ベクトル = n * -入射 + 法線 * (n * cosθ1 - √(1 - n * n * (1 - cosθ1 * cosθ1) )
    return n * inverse + eNorm * (n * cos_theta1 - std::sqrt(abs_cosT2));
}

// 円周率×2
#define PI_2 3.14159265358979323846264f * 2

// ラジアン: 0~2π (0°~ 360°)の範囲に変換
#define RAD_NORMALIZE( rad ) ( ( rad >= 0.0f ) ? std::fmod( rad, PI_2 ): std::fmod( rad, PI_2 ) + PI_2 )

// ベクトルから Pitch(ピッチ) を求める https://watako-lab.com/2019/01/23/roll_pitch_yaw/
inline float pitch(const Vector3& vec)
{
    float length = vec.magnitude();
    if (length == 0) return 0.0f; // ベクトルの長さが0のときは0°

    // こたえを 0~2π (0°~ 360°)の範囲に変換してreturn
    return RAD_NORMALIZE( std::asin(-vec.y / length) );
}

// ベクトルから Yaw(ヨー) を求める https://watako-lab.com/2019/01/23/roll_pitch_yaw/
inline float yaw(const Vector3& vec)
{
    float length = vec.magnitude();
    if (length == 0) return 0.0f; // ベクトルの長さが0のときは0°

    return RAD_NORMALIZE( std::atan2(vec.x / length, vec.z / length) );
}

// 一時定義したマクロを #undef で未定義に戻す
#undef PI_2
#undef RAD_NORMALIZE

// 重心座標を求める 点 p0:重心座標を求めたい位置 三角形の点: p1 p2 p3 https://zellij.hatenablog.com/entry/20131207/p1
inline Vector3 barycentric(const Vector3& p0, const Vector3& p1, const Vector3& p2, const Vector3& p3)
{
    Vector3 v2 = p2 - p1;
    Vector3 v3 = p3 - p1;
    Vector3 v0 = p0 - p1;
    float d22 = dot(v2, v2);
    float d23 = dot(v2, v3);
    float d33 = dot(v3, v3);
    float d02 = dot(v0, v2);
    float d03 = dot(v0, v3);
    float denom = d22 * d33 - d23 * d23;
    float v = (d33 * d02 - d23 * d03) / denom;
    float w = (d22 * d03 - d23 * d02) / denom;
    return  p1 * (1.0f - v - w) + p2 * v + p3 * w;
}

// 調べたい点point から一番近い直線上の点を求める linePos: 線の起点 lineVec:線のベクトル isInfinite:無限に延びる線か? result_t:接点までの直線パラメータ t
inline Vector3 nearestPointOnLine(const Vector3& point, const Vector3& lineStart, const Vector3& lineDir, bool isInfinite = true, float* result_t = nullptr)
{
    float t; // 直線のパラメータ t (tが0だと始点startの位置、tが増えると始点からより遠くに延長された位置になる)
    float lineLengthSq = lineDir.sqrMagnitude();
    if (lineLengthSq < FLT_EPSILON) // 線分のベクトルの長さがほぼ0なら
    {
        if (result_t != nullptr)
            *result_t = 0.0f; // 直線パラメータ t は起点の位置だから 0 の位置

        return lineStart; // 線分の始点が最も近い
    }

    Vector3    linePosToPoint = point - lineStart; // 線の起点linePos から 調べたい点pointへのベクトル

    t = dot(lineDir, linePosToPoint) / lineLengthSq; // 調べたい座標に最も近い lineVec線上の座標 への直線パラメータ t を計算

    if (isInfinite == false) // 有限線分のとき (線が無限に延びてないタイプのとき)
        t = GET_CLAMP(t, 0.0f, 1.0f); // 有限の線だから GET_CLAMP でtの値を 0.0f~1.0fの間に限定する

    if (result_t != nullptr) // result_tがnullptrじゃなければ 直線パラメータ t もついでに計算結果を返却しておく
        *result_t = t;

    return lineDir * t + lineStart; // lineVecの方向をt倍のばして一番近い直線上の点の位置を求める
}


// 平行で交わらない線分line0 と 線分line1 の一番近い点を求める isInfinite:無限の長さの線かどうか result0:line0上の最も近い点 result1:line1上の最も近い点
inline void nearestPointOfParallelLine(const Vector3& line0Start, const Vector3& line0End, const Vector3& line1Start, const Vector3& line1End,
                                       bool isInfinite = false, Vector3* result0 = nullptr, Vector3* result1 = nullptr)
{
    float t0, t1, t; // 直線のパラメータを求める

    Vector3 answer0 = nearestPointOnLine(line1Start, line0Start, line0End - line0Start, true, &t0); // 始点line1Start から一番近い直線line0上の点を求める
    if (result0 != nullptr) *result0 = answer0; // resultがnullptrじゃなければ答えを代入して返す

    if (isInfinite == true) // 無限の長さの線ならば
    {
        Vector3 answer1 = line1Start; // line1の起点が一番近い
        if (result1 != nullptr) *result1 = answer1; // resultがnullptrじゃなければ答えを代入して返す
    }
    else
    {
        Vector3    tmpPoint = nearestPointOnLine(line1End, line0Start, line0End - line0Start, true, &t1); // 終点line1end から一番近い直線line0上の点を求める
        if ((t0 < 0.0f) && (t1 < 0.0f)) // 最も近い点 が line0もline1も両方【始点】より外側の延長線上にあるとき
        {
            answer0 = line0Start; // 始点が答え(最も近い点)になる
            Vector3 answer1 = (t0 < t1) ? line1End : line1Start;
            if (result0 != nullptr) *result0 = answer0; // resultがnullptrじゃなければ答えを代入して返す
            if (result1 != nullptr) *result1 = answer1;
        }
        else if ((t0 > 1.0f) && (t1 > 1.0f)) // 最も近い点 が line0もline1も両方【終点】より外側の延長線上にあるとき
        {
            answer0 = line0End; // 終点が答え(最も近い点)になる
            Vector3 answer1 = (t0 < t1) ? line1Start : line1End;
            if (result0 != nullptr) *result0 = answer0; // resultがnullptrじゃなければ答えを代入して返す
            if (result1 != nullptr) *result1 = answer1;
        }
        else
        {
            t = 0.5f * (GET_CLAMP(t0, 0.0f, 1.0f) + GET_CLAMP(t1, 0.0f, 1.0f));
            answer0 = (line0End - line0Start) * t + line0Start; // line0の方向をt倍のばして一番近い直線上の点の位置を求める
            Vector3 answer1 = nearestPointOnLine(answer0, line1Start, line1End - line1Start, true); // 点answer0 から一番近い直線line1上の点を求める
            if (result0 != nullptr) *result0 = answer0; // resultがnullptrじゃなければ答えを代入して返す
            if (result1 != nullptr) *result1 = answer1;
        }
    }
}

// 線分line0 と 線分line1 の【有限の線上範囲内】での一番近い点を求める result0:line0上に収まる最も近い点 result1:line1上に収まる最も近い点
inline void nearestPointsOnFiniteLines(const Vector3& line0Start, const Vector3& line0End, const Vector3& line1Start, const Vector3& line1End,
                                       float t0, float t1, Vector3& result0, Vector3& result1)
{
    Vector3 v0 = line0End - line0Start; // line0 の start点 から end点 へ向かう ベクトル
    Vector3 v1 = line1End - line1Start; // line1 の start点 から end点 へ向かう ベクトル
    if ( (t0 < 0.0f || 1.0f < t0) && (t1 < 0.0f || 1.0f < t1) ) // t0 も t1 も両方 0~1.0の範囲外(line0とline1の始点から終点の範囲内におさまってない)
    {
        t0 = GET_CLAMP(t0, 0.0f, 1.0f); // まず t0 を 0~1.0以内に収める(0以下 や 1以上 でも 0 や 1 にする)
        Vector3 answer0 = v0 * t0 + line0Start; // v0の方向を t0倍 のばした直線line0上 の 範囲内の点 の位置を一旦、求める
        Vector3 answer1 = nearestPointOnLine(answer0, line1Start, v1, true, &t1); // 範囲内の点↑answer0 から 一番近い 直線line1(無限長)上の点 を求める
        if (t1 < 0.0f || 1.0f < t1) // t1が 直線line1 の 始点0 ~ 終点1.0 の↑範囲外ならば t1を0~1.0の範囲に収めてさらに再計算
        {
            t1 = GET_CLAMP(t1, 0.0f, 1.0f); // t1 = 0~1.0以内に収めて再計算
            answer1 = v1 * t1 + line1Start; // v1の方向を t1倍 のばして一番近い直線上の点の位置を求める
            answer0 = nearestPointOnLine(answer1, line0Start, v0, false); // 点answer1 から 一番近い 直線line0(有限)上の点 を求めなおす
            answer1 = nearestPointOnLine(answer0, line1Start, v1, false); // 点answer0 から 一番近い 直線line1(有限)上の点 を求めなおす
        }
        result0 = answer0; // 求めたanswer で 結果 result を確定して関数外へ返す
        result1 = answer1;
    }
    else if (t0 < 0.0f || 1.0f < t0) // t0 が 0~1.0の範囲におさまっていない(line0の始点から終点の範囲内におさまってない)
    {
        t0 = GET_CLAMP(t0, 0.0f, 1.0f); // t0 を 0~1.0以内に収める(0以下 や 1以上 でも 0 や 1 にする)
        Vector3 answer0 = v0 * t0 + line0Start; // v0の方向を t0倍 のばした直線line0上 の 範囲内の点 の位置を一旦、求める
        Vector3 answer1 = nearestPointOnLine(answer0, line1Start, v1, false); // 点answer0 から 一番近い 直線line1(有限)上の点 を求める

        result0 = answer0; // 求めたanswer で 結果 result を確定して関数外へ返す
        result1 = answer1;
    }
    else if (t1 < 0.0f || 1.0f < t1) // t1 が 0~1.0の範囲におさまっていない(line1の始点から終点の範囲内におさまってない)
    {
        t1 = GET_CLAMP(t1, 0.0f, 1.0f); // t1 を 0~1.0以内に収める(0以下 や 1以上 でも 0 や 1 にする)
        Vector3 answer1 = v1 * t1 + line1Start; // v0の方向を t0倍 のばした直線line0上 の 範囲内の点 の位置を一旦、求める
        Vector3 answer0 = nearestPointOnLine(answer1, line0Start, v0, false); // 点answer1 から 一番近い 直線line0(有限)上の点 を求める

        result0 = answer0; // 求めたanswer で 結果 result を確定して関数外へ返す
        result1 = answer1;
    }
}


// 点:point と box:AABB(軸並行境界ボックス:Axis-Aligned Bounding Box)の最短距離の2乗 http://marupeke296.com/COL_3D_No11_AABBvsPoint.html
inline float sqrNearestLengthPoint_AABB(const Vector3& point, const Vector3& boxMin, const Vector3& boxMax)
{
    float sqLength = 0.0f; // 長さのべき乗の値を足していく
    for (auto i = 0; i < 3; ++i)
    {   // 各軸で点が最小値以下もしくは最大値以上ならば、差を考慮
        if (point.xyz[i] < boxMin.xyz[i])  // i=0:X,,i=1:Y, i=2はZの意味です
            sqLength += (point.xyz[i] - boxMin.xyz[i]) * (point.xyz[i] - boxMin.xyz[i]); // 距離の2乗
        if (point.xyz[i] > boxMax.xyz[i])
            sqLength += (point.xyz[i] - boxMax.xyz[i]) * (point.xyz[i] - boxMax.xyz[i]); // 距離の2乗
    }
    return sqLength; //  // 距離の2乗を足していった合計を返す
}

// 点:point と box:AABB(軸並行境界ボックス:Axis-Aligned Bounding Box)の最短距離 http://marupeke296.com/COL_3D_No11_AABBvsPoint.html
inline float nearestLengthPoint_AABB(const Vector3& point, const Vector3& boxMin, const Vector3& boxMax)
{
    return std::sqrtf(sqrNearestLengthPoint_AABB(point, boxMin, boxMax)); // 距離の2乗を足していった合計の√を取る
}

// 点:point0 と box1:OBB(有向境界ボックス:Oriented Bounding Box)の各軸の超過ベクトル result:OBB上の一番近い点を返す
inline Vector3 overLengthPoint_OBB(const Vector3& point0,
    const Vector3& centerOBB1, const Vector3& sizeOBB1, const std::array<Vector3, 3>& axisOBB1,
    Vector3* result = nullptr)
{
    Vector3 v10 = point0 - centerOBB1; // OBBの中心→pointのベクトル
    Vector3 overLength{ 0.0f, 0.0f, 0.0f }; // 最終的な長さを求めるベクトル
    float dist;
    // 各軸についてはみ出た部分のベクトルを算出 http://marupeke296.com/COL_3D_No12_OBBvsPoint.html
    for (int i = 0; i < 3; ++i)
    {
        float L = sizeOBB1.xyz[i] / 2; // 軸の中心からはしっこまでの距離
        dist = dot(v10, axisOBB1[i]); // OBBの中心→pointのベクトルとOBBの軸の内積
        if (L > 0) // L=0は計算できない
        {
            float s = dist / L; // distをLで割る
            // sの値から、はみ出した部分があればそのベクトルを加算
            float abs_s = (s < 0) ? -s : s; // sの絶対値を求める
            if (abs_s > 1) // sの絶対値が1を超えた部分がはみ出したベクトル
                overLength += (1 - abs_s) * L * axisOBB1[i]; // はみ出した部分のベクトル算出
        }

        if (result != nullptr) // resultがnullptrじゃなければOBB上の一番近い点も求める
        {   // OBB上の一番近い点の算出 http://distancevector.web.fc2.com/collision.html#OBBPoint
            if (dist > L) dist = L; // distがLの長さを超えても L の長さに制限する
            else if (dist < -L) dist = -L;
            *result += dist * axisOBB1[i]; // OBB上の一番近い点に i の軸方向の距離を反映
        }
    }
    return overLength; // 超過ベクトルを返す
}

// 点:point と box:OBB(有向境界ボックス:Oriented Bounding Box)の最短距離 result:OBB上の一番近い点を返す
inline float nearestLengthPoint_OBB(const Vector3& point,
                                    const Vector3& centerOBB, const Vector3& sizeOBB, const std::array<Vector3,3>& axisOBB,
                                    Vector3* result = nullptr)
{   // 各軸の超過距離のベクトルの大きさを出力 http://marupeke296.com/COL_3D_No12_OBBvsPoint.html
    return overLengthPoint_OBB(point, centerOBB, sizeOBB, axisOBB, result).magnitude();
}

// 点:point と box:OBB(有向境界ボックス:Oriented Bounding Box)の最短距離の2乗 result:OBB上の一番近い点を返す
inline float sqrNearestLengthPoint_OBB(const Vector3& point,
                                       const Vector3& centerOBB, const Vector3& sizeOBB, const std::array<Vector3, 3>& axisOBB,
                                       Vector3* result = nullptr)
{   // 各軸の超過距離のベクトルの大きさの2乗を出力 http://marupeke296.com/COL_3D_No12_OBBvsPoint.html
    return overLengthPoint_OBB(point, centerOBB, sizeOBB, axisOBB, result).sqrMagnitude();
}

#endif


Vector3.cppを新規作成してstatic変数の初期化コードを実装します。

#include "Vector3.h"

// Unityのstatic変数を参考に https://docs.unity3d.com/ja/current/ScriptReference/Vector3.html
Vector3 const Vector3::zero = Vector3(0.f, 0.f, 0.f);
Vector3 const Vector3::one = Vector3(1.f, 1.f, 1.f);
Vector3 const Vector3::forward = Vector3(0.f, 0.f, 1.f);
Vector3 const Vector3::back = Vector3(0.f, 0.f, -1.f);
Vector3 const Vector3::up = Vector3(0.f, 1.f, 0.f);
Vector3 const Vector3::down = Vector3(0.f, -1.f, 0.f);
Vector3 const Vector3::left = Vector3(-1.f, 0.f, 0.f);
Vector3 const Vector3::right = Vector3(1.f, 0.f, 0.f);
//[負の値の最小値] https://stackoverflow.com/questions/20016600/negative-infinity
Vector3 const Vector3::negativeInfinity = Vector3(-std::numeric_limits<float>::infinity(), -std::numeric_limits<float>::infinity(), -std::numeric_limits<float>::infinity());
//[極大値]https://cpprefjp.github.io/reference/limits/numeric_limits/lowest.html
Vector3 const Vector3::positiveInfinity = Vector3(std::numeric_limits<float>::infinity(), std::numeric_limits<float>::infinity(), std::numeric_limits<float>::infinity());


つぎにVector3を使って、平面を表現するPlane4.hを準備します。
実は平面(無限)は、驚くことにデータ的には、 原点からの距離:dist と 平面に垂直な法線ベクトル:n のたった2つだけの要素で確定できてしまいます。


Plane4.hを新規作成して原点からの距離:dist と 平面に垂直な法線ベクトル:n である無限の平面を数学的に表現する準備をします。

#ifndef PLANE_H_
#define PLANE_H_

#include "Vector3.h"
#include <cmath>
#include <float.h> // 極小値ε FLT_EPSILON を使う

//[UnityのPlane平面の例] https://docs.unity3d.com/ja/2022.3/ScriptReference/Plane.html
// 内部的には内積や外積だけで計算処理が実装されている

struct Plane4 // 数学的な平面(無限)の定義 Plane4の4はVector3のような float型を4つ持つという意味
{
    union // 共用体unionで法線struct{nx, ny, nz} と 法線nを同じメモリ上の位置にする つまり nx = n.x, ny = n.y, nz = n.zになる
    {
        //struct { float nx, ny, nz; }; // 面法線x,y,z成分
        Vector3    n{0.0f,0.0f,0.0f};    // 面から出る法線ベクトル
    };
    float dist = 0.0f;  // 原点[まで]の距離 [参考] https://nyama41.hatenablog.com/entry/unity_plane_distance

    // float型のポインタへ(float*)キャストすればnx,ny,nz,distデータの配列の先頭のアドレスを返す
    inline operator float* () const { return (float*)this; }
    // float型のポインタへ(const float*)キャストすればnx,ny,nz,distデータの配列の先頭のアドレスを返す
    inline operator const float* () const { return (const float*)this; }

    Plane4(const Plane4& other) : n{ other.n }, dist{ other.dist } {} // コピーコンストラクタ return *thisを使うため

    Plane4(float normX = 0.0f, float normY = 0.0f, float normZ = 0.0f, float distance = 0.0f) : n{ Vector3{normX, normY, normZ } }, dist{ distance } {}
    Plane4(const Vector3& norm, float distance) : n{ norm }, dist{ distance } {}
    // 平面上の点point と 平面の法線ペクトルnorm から 平面方程式パラメータdist(原点からの距離)をdotで計算して 平面を生成する
    Plane4(const Vector3& point, const Vector3& norm) : n{ norm }, dist{ -dot(norm, point) } {}
    // 三角ポリゴン 3点p0,p1,p2 から平面を確定させて生成 dist(原点からの距離) は 法線normal と 点p0 から計算
    Plane4(const Vector3& p0, const Vector3& p1, const Vector3& p2) : n{ polygonNorm(p0, p1, p2) }, dist{ -dot(n, p0) } {}

    ~Plane4() {}

    // 平面を正規化する (平面の法線を単位ベクトル化する)
    inline Plane4 normalized() const
    {
        float mag = n.magnitude();
        if (mag < 0.00001f) // ほぼ0ベクトルか?
            return *this;
        else
            return Plane4{ n / mag, dist / mag};
    }

    // 原点から一番近い平面上の点を求める 法線ベクトルにマイナス方向のdist(原点からの距離)を掛けることで原点から一番近い平面上の1点になる
    inline Vector3 nearestFromOrigin() { return n * -dist; }

    // 平面 から 点point までの距離を求める
    inline float distance(const Vector3& point) const { return dot(n, point) + dist; }

    // 位置はそのままに表裏をひっくり返す https://nekojara.city/unity-plane-struct
    inline Plane4& flip() { n = -n; dist = -dist; return *this; }

    // 位置はそのままに表裏をひっくり返した平面を複製する(もとのPlane4はかわらない) https://nekojara.city/unity-plane-struct
    inline Plane4 flipedCopy() { return Plane4(-n, -dist); }

    /*----- 演算子オーバーロード -----*/

    // + 演算子のオペレータ
    inline Plane4 operator + () const { return *this; }

    // - 逆向き平面 (法線ベクトルが逆向き)
    inline Plane4 operator - () const
    {
        return Plane4(-n, -dist);
    }

    enum class Side
    {
        OnPlane, // 平面上にある
        InterPlane, // 平面を横切る
        Front, // 平面から見て前面にある
        Behind, // 平面から見て背面にある
    };

    // 点point が平面上、前面、背面側のどこにあるかを返し、resultDistにはその距離を返す
    inline Side pointSide(const Vector3& point, float* resultDist = nullptr)
    {
        float answerDist = this->distance(point); // 平面 から pointへの距離
        if (resultDist != nullptr) *resultDist = answerDist; // 答え(距離)を返す

        if (answerDist > FLT_EPSILON) return Side::Front; // 平面の前面の側に点pointがある
        if (answerDist < FLT_EPSILON) return Side::Behind; // 平面の背面の側に点pointがある
        return Side::OnPlane; // 点point が平面上にある
    }

    // 球(中心:center,半径:Radius) が 平面上、前面、背面側のどこにあるかを返し、resultDistにはその距離を返す
    inline Side sphereSide(const Vector3& center, const float Radius, float* resultDist = nullptr) const
    {
        float answerDist = distance(center); // 平面 から 球の中心:center の距離
        if (resultDist != nullptr) *resultDist = answerDist; // 答え(距離)を返す

        float absDist = (answerDist < 0) ? -answerDist : answerDist;
        if (absDist < Radius) return Side::OnPlane; // 球 が平面上にある

        if (answerDist >= Radius)
            return Side::Front; // 平面の前面の側に 球 がある
        else
            return Side::Behind; // 平面の背面の側に 球 がある
    }

    // 法線から一番近い点(AABBの正の頂点)を求める https://edom18.hateblo.jp/entry/2017/10/29/112908
    inline Vector3 positivePoint_AABB(const Vector3& boxMin, const Vector3& boxMax) const
    {
        Vector3 result = boxMin;
        Vector3 boxSize = boxMax - boxMin;
        if (n.x > 0) result.x += boxSize.x;
        if (n.y > 0) result.y += boxSize.y;
        if (n.z > 0) result.z += boxSize.z;
        return result;
    }

    // 法線から一番近い点(AABBの正の頂点)から平面までの距離を返す https://edom18.hateblo.jp/entry/2017/10/29/112908
    inline float positiveDistance_AABB(const Vector3& boxMin, const Vector3& boxMax) const
    {
        return distance(positivePoint_AABB(boxMin, boxMax));
    }

    // 法線から一番遠い点(AABBの負の頂点)を求める https://edom18.hateblo.jp/entry/2017/10/29/112908
    inline Vector3 negativePoint_AABB(const Vector3& boxMin, const Vector3& boxMax) const
    {
        Vector3 result = boxMin;
        Vector3 boxSize = boxMax - boxMin;
        if (n.x < 0) result.x += boxSize.x;
        if (n.y < 0) result.y += boxSize.y;
        if (n.z < 0) result.z += boxSize.z;
        return result;
    }

    // 法線から一番遠い点(AABBの正の頂点)から平面までの距離を返す https://edom18.hateblo.jp/entry/2017/10/29/112908
    inline float negativeDistance_AABB(const Vector3& boxMin, const Vector3& boxMax) const
    {
        return distance(negativePoint_AABB(boxMin, boxMax));
    }
};


// 一致 == 演算子オペレータ 法線ベクトルnorm と 距離 distが一致する
inline bool operator == (const Plane4& left, const Plane4& right)
{
    return    (left.n == right.n && left.dist == right.dist);
}

// 不一致 != 演算子オペレータ 法線ベクトルnorm か 距離 distが一致しない
inline bool operator != (const Plane4& left, const Plane4& right)
{
    return    !(left == right);
}

// 平面 と ベクトルの内積 (平面の法線ベクトル と 他のベクトルの内積)
inline float dot(const Plane4& plane, const Vector3& rightVec)
{
    return plane.n.x * rightVec.x + plane.n.y * rightVec.y + plane.n.z * rightVec.z; //普通にx同士,y同士,z同士掛けるだけ
}

// 線形補間する start:開始位置  end:終了位置  t = 補間の割合(start側が100%  0.0~1.0  end側が100% でブレンド)
inline Plane4 lerp(const Plane4& start, const Plane4& end, float t)
{
    Plane4 plane;
    plane.n = start.n * (1.0f - t) + end.n * t; // startを始点に tの割合ぶんendへ近づける
    plane.n = plane.n.normalized(); // 法線を単位ベクトル化(大きさ1)する
    plane.dist = start.dist * (1.0f - t) + end.dist * t;
    return plane;
}

// 2次補間する start:開始位置 mid:中間制御点 end:終了位置  t = 補間の割合(start側が100%  0.0~1.0  end側が100% でブレンド)
inline Plane4 quadratic(const Plane4& start, const Plane4& mid, const Plane4& end, float t)
{
    Plane4 plane;
    plane.n = start.n * (1.0f - t) * (1.0f - t) + 2 * mid.n * t * (1.0f - t) + end.n * t * t;
    plane.n = plane.n.normalized(); // 法線を単位ベクトル化(大きさ1)する
    plane.dist = start.dist * (1.0f - t) * (1.0f - t) + 2 * mid.dist * t * (1.0f - t) + end.dist * t * t;
    return plane;
}

#endif


そして、次はこのVector3.hとPlane4.hを駆使して、3D空間での当たり判定をする関数を定義するCollision3D.hを準備します。

Collision3D.hを新規作成して球、円柱、カプセル、線、三角ポリゴン、平面などや光線(レイ)との当たり判定をするためのisCollision系関数群を準備します。

#ifndef COLLISION3D_H_
#define COLLISION3D_H_

#include "Vector3.h"
#include "Plane4.h"

//-------------- 球、円柱、線分(レイ)、カプセル、ポリゴンのエッジなどの当たり判定---------------------------

// 軸並行境界ボックス同士 AABB 0 と AABB 1 の当たり判定 https://developer.mozilla.org/ja/docs/Games/Techniques/3D_collision_detection
inline bool isCollisionAABB_AABB(const Vector3& boxMin0, const Vector3& boxMax0, const Vector3& boxMin1, const Vector3& boxMax1)
{   // min と maxを比べればよい
    return boxMin0.x <= boxMax1.x && boxMax0.x >= boxMin1.x &&
           boxMin0.y <= boxMax0.y && boxMax0.y >= boxMin1.y &&
           boxMin0.z <= boxMax1.z && boxMax0.z >= boxMin1.z;
}

// 点:point と box:AABB(軸並行境界ボックス:Axis-Aligned Bounding Box)の当たり判定 http://marupeke296.com/COL_3D_No11_AABBvsPoint.html
inline bool isCollisionPoint_AABB(const Vector3& point, const Vector3& leftTop, const Vector3& rightBottom)
{
    return nearestLengthPoint_AABB(point, leftTop, rightBottom) > 0; // 最短距離が0以上なら当たっている
}

// 点:point と box:OBB(有向境界ボックス:Oriented Bounding Box)の当たり判定 result:OBB上の一番近い点を返す
inline bool isCollisionPoint_OBB(const Vector3& point,
                                 const Vector3& centerOBB, const Vector3& sizeOBB, const std::array<Vector3, 3>& axisOBB,
                                 Vector3* result = nullptr)
{   //[OBBと点の衝突判定] http://marupeke296.com/COL_3D_No12_OBBvsPoint.html
    return sqrNearestLengthPoint_OBB(point, centerOBB, sizeOBB, axisOBB, result) > 0; // 最短距離が0じゃなければ当たっている
}

// 球体 と OBB(有向境界ボックス)の衝突判定
inline bool isCollisionSphere_OBB(const Vector3& center0, float Radius0,
                                  const Vector3& centerOBB, const Vector3& sizeOBB, const std::array<Vector3, 3>& axisOBB)
{
    Vector3 nearestOnOBB; // OBB上の一番近い点
    overLengthPoint_OBB(center0, centerOBB, sizeOBB, axisOBB, &nearestOnOBB);
    // OBB上の一番近い点 と 半径の2乗を比べる http://distancevector.web.fc2.com/collision.html#OBBSphere
    Vector3 dist = nearestOnOBB - center0;
    return dot(dist, dist) <= Radius0 * Radius0;
}

// 光線:ray と box:AABB(軸並行境界ボックス:Axis-Aligned Bounding Box)の当たり判定 http://marupeke296.com/COL_3D_No18_LineAndAABB.html
inline bool isCollisionAABB_Ray(const Vector3& boxMin, const Vector3& boxMax,
    const Vector3& rayPos, const Vector3& rayDir, Vector3* result = nullptr, float* result_t = nullptr)
{
    float _t; // 変数tの一時参照先
    // result_tがnullptrじゃなければ   ↓参照先  : ↓nullptrなら
    float& t = (result_t != nullptr) ? *result_t : _t;
    t = -FLT_MAX; // 初期値はfloat型の最小値からはじめる
    float t_max = FLT_MAX; // 初期値はfloat型の最大値

    // 交差判定
    for (int i = 0; i < 3; ++i)
    {   // レイの方向ベクトルの絶対値がほぼ0のときは割り算の分母にできないので要チェック
        float absRayDir = (rayDir.xyz[i] < 0) ? -rayDir.xyz[i] : rayDir.xyz[i];
        if (absRayDir < FLT_EPSILON) // レイ方向の大きさがほぼ0なら
        {   // レイの始点がボックスの範囲外なら交差しない(レイ方向がほぼ0なので到達不可能)
            if (rayPos.xyz[i] < boxMin.xyz[i] || rayPos.xyz[i] > boxMax.xyz[i])
                return false; // 交差していない
        }
        else
        {   // スラブとの距離を算出
            // t1が近スラブ、t2が遠スラブとの距離
            float t1 = (boxMin.xyz[i] - rayPos.xyz[i]) / rayDir.xyz[i];
            float t2 = (boxMax.xyz[i] - rayPos.xyz[i]) / rayDir.xyz[i];
            if (t1 > t2) // t1のほうが大きければ
            {   // t1とt2を入れ替えてから判定
                float tmp = t1; t1 = t2; t2 = tmp;
            }

            if (t1 > t) t = t1; // t1が現在のtより大きければtを更新
            if (t2 < t_max) t_max = t2; // t2が現在のt_maxより小さければt_maxを更新

            if (t >= t_max) // スラブ交差チェック (t のほうが t_max より大きいか)
                return false; // 交差していない
        }
    }

    if (result != nullptr) // resultがnullptrじゃなければ結果を代入して返す
        *result = rayPos + t * rayDir;

    return (t >= 0); // t >=0 で rayDirのプラス方向に交差点があればtrue
}

// 線分(有限):line0→1 と box:OBB(有向境界ボックス:Oriented Bounding Box)の当たり判定 http://distancevector.web.fc2.com/collision.html#OBBSphere
inline bool isCollisionOBB_Line(const Vector3& centerOBB, const Vector3& sizeOBB, const std::array<Vector3, 3>& axisOBB,
    const Vector3& line0start, const Vector3& line1End, Vector3* result = nullptr, float* result_t = nullptr)
{
    Vector3 mid = (line0start + line1End) * 0.5f; // 線分line0→1の中間点01
    Vector3 end = line1End - mid; // 中間点01→line1Endのベクトル
    mid = mid - centerOBB; // OBBの中心→中間点01
    // OBBの3軸とのdotを取る
    mid = Vector3{ dot(axisOBB[0], mid), dot(axisOBB[1], mid), dot(axisOBB[2], mid) };
    end = Vector3{ dot(axisOBB[0], end), dot(axisOBB[1], end), dot(axisOBB[2], end) };

    Vector3 abs_end; // endの絶対値abs
    abs_end.x = std::abs(end.x);
    if (std::abs(mid.x) > sizeOBB.x / 2 + abs_end.x) return false;
    abs_end.y = std::abs(end.y);
    if (std::abs(mid.y) > sizeOBB.y / 2 + abs_end.y) return false;
    abs_end.z = std::abs(end.z);
    if (std::abs(mid.z) > sizeOBB.z / 2 + abs_end.z) return false;
    abs_end.x += FLT_EPSILON;
    abs_end.y += FLT_EPSILON;
    abs_end.z += FLT_EPSILON;

    if (std::abs(mid.y * end.z - mid.z * end.y) > sizeOBB.y * abs_end.z + sizeOBB.z * abs_end.y) return false;
    if (std::abs(mid.z * end.x - mid.x * end.z) > sizeOBB.x * abs_end.z + sizeOBB.z * abs_end.x) return false;
    if (std::abs(mid.x * end.y - mid.y * end.x) > sizeOBB.x * abs_end.y + sizeOBB.y * abs_end.x) return false;

    return true;
}


// 球体と球体の衝突判定
inline bool isCollisionSphere_Sphere(const Vector3& center0, float Radius0, const Vector3& center1, float Radius1)
{
    return (center1 - center0).sqrMagnitude() <= (Radius0 + Radius1) * (Radius0 + Radius1);
}

// 2つの球0(速度:v0)と球1(速度:v1)の衝突までの時間と位置を取得(双方の球は指定のベクトル方向に等速で進む場合)
// stepTime : 球0,球1が進む時間
// resultTime : 衝突時刻  resultPos : 衝突位置(接点)
// resultCenter0,1 : 衝突時の球0,球1の中心座標
inline bool isCollisionSphere_Sphere_At(float stepTime,
    const Vector3& center0, const float radius0, const Vector3& v0,
    const Vector3& center1, const float radius1, const Vector3& v1,
    float& resultTime, Vector3& resultPos,
    Vector3* resultCenter0 = nullptr, Vector3* resultCenter1 = nullptr)
{
    // http://marupeke296.com/COL_3D_No9_GetSphereColliTimeAndPos.html
    // 前位置及び到達位置におけるパーティクル間のベクトルを算出
    Vector3 prev01 = center1 - center0;
    Vector3 next01 = center1 + v1 * stepTime - center0 + v0 * stepTime;
    Vector3 dif01 = next01 - prev01;
    float radius01 = radius0 + radius1;
    float radius01Sq = radius01 * radius01;
    float dif01sqm = dif01.sqrMagnitude();

    if (dif01sqm == 0) // 衝突判定に衝突検知の解の公式を使えない場合
    {
        // 初期段階 t = 0ですでに衝突しているか?
        if ((center1 - center0).sqrMagnitude() > radius01Sq)
            return false;

        resultTime = 0.0f;
        // resultCenterがnullptrじゃなければ、center0と1を答えとしてreturnする
        if (resultCenter0 != nullptr) *resultCenter0 = center0;
        if (resultCenter1 != nullptr) *resultCenter1 = center1;

        if (center1 == center0) // 中心位置が一緒の場合は
            resultPos = center0; // 中心点を衝突点として返す
        else  // 球center0→center1のベクトル方向に長さradius0の所を衝突点とする
            resultPos = center0 + (radius0 / radius01) * prev01;
        return true; // 同じ方向に移動
    }
    // else 衝突検知可能

    // 最初から衝突しているか?
    if ((center1 - center0).sqrMagnitude() <= radius01Sq)
    {
        resultTime = 0.0f;
        resultPos = center0 + radius0 / radius01 * prev01;
        // resultCenterがnullptrじゃなければ、center0と1を答えとしてreturnする
        if (resultCenter0 != nullptr) *resultCenter0 = center0;
        if (resultCenter1 != nullptr) *resultCenter1 = center1;
        return true;
    }

    float Q = dot(prev01, dif01);
    float prev01sqm = prev01.sqrMagnitude();

    // 衝突判定式
    float judge = Q * Q - dif01sqm * (prev01sqm - radius01 * radius01);
    if (judge < 0) return false; // 衝突していない

    // 衝突時間の算出
    float judge_sqrt = std::sqrtf(judge);
    float t_plus = (-Q + judge_sqrt) / dif01sqm;
    float t_minus = (-Q - judge_sqrt) / dif01sqm;
    if (t_minus > t_plus)
    {   // t_minusを小さい方に入れ替え
        float tmp = t_minus;
        t_minus = t_plus;
        t_plus = tmp;
    }

    // 時間外衝突か?
    if (t_minus < 0.0f || t_minus > 1.0f) return false;

    // 衝突位置の決定
    resultTime = t_minus * stepTime;
    Vector3 answer0 = center0 + v0 * stepTime * t_minus;
    Vector3 answer1 = center1 + v1 * stepTime * t_minus;
    resultPos = answer0 + radius0 / radius01 * (answer1 - answer0);
    // resultCenterがnullptrじゃなければ、answer0と1を答えとしてreturnする
    if (resultCenter0 != nullptr) *resultCenter0 = answer0;
    if (resultCenter1 != nullptr) *resultCenter1 = answer1;
    return true; // 衝突した
}

// 衝突してtime時刻経過後の 球0,1(衝突時速度:v0,1 質量:weight0,1 反発係数:refRate0,1)の
// 反発後の位置result_center と 速度result_v を求める
inline bool getCollisionPosVecSphere_Sphere_After(float time,
    const Vector3& center0, const Vector3& v0, float weight0, float refRate0,
    const Vector3& center1, const Vector3& v1, float weight1, float refRate1,
    Vector3* result_center0 = nullptr, Vector3* result_v0 = nullptr,
    Vector3* result_center1 = nullptr, Vector3* result_v1 = nullptr)
{
    // http://marupeke296.com/COL_MV_No1_HowToCalcVelocity.html
    float totalWeight = weight0 + weight1; // 質量の合計
    float refRate = (1 + refRate0 * refRate1); // 反発率
    Vector3 c01 = center1 - center0; // 衝突軸ベクトル
    c01 = c01.normalized();
    float dot_v10_c01 = dot((v0 - v1), c01); // 内積算出
    const Vector3 ConstVec = refRate * dot_v10_c01 / totalWeight * c01; // 反発定数ベクトル

    // 衝突後速度ベクトルの算出
    Vector3 answer_v0 = -weight1 * ConstVec + v0;
    Vector3 answer_v1 =  weight0 * ConstVec + v1;
    if (result_v0 != nullptr) *result_v0 = answer_v0;
    if (result_v1 != nullptr) *result_v1 = answer_v1;

    // 衝突後位置の算出
    Vector3 answer_center0 = center0 + time * answer_v0;
    Vector3 answer_center1 = center1 + time * answer_v1;
    if (result_center0 != nullptr) *result_center0 = answer_center0;
    if (result_center1 != nullptr) *result_center1 = answer_center1;

    return true;
}

// 球体とレイ(光線)の当たり判定 rayPos:レイの起点 rayDir:レイの方向 result0:球体とレイの衝突開始点 result1:球体とレイの衝突終了点
inline bool isCollisionSphere_Ray(const Vector3& center, float Radius, const Vector3& rayPos, const Vector3& rayDir, Vector3* result0 = nullptr, Vector3* result1 = nullptr)
{
    Vector3    centerToRayPos = rayPos - center; // 球体の中心からレイの起点までを結ぶベクトル
    // 球体とレイの交差判定を行う
    float rayDistSq = rayDir.sqrMagnitude(); // レイの方向ベクトル大きさの2乗
    float rayDirDot = dot(centerToRayPos, rayDir); // レイと球の中心を結ぶ線とレイの方向の内積(レイの方向へ直角に下したベクトル)
    float rayToSphereSq = centerToRayPos.sqrMagnitude() - Radius * Radius; // レイ起点から球の表面までの距離の2乗
    float dif = rayDirDot * rayDirDot - rayDistSq * rayToSphereSq;
    if (dif < 0.0f) return false;

    float sqrt_dif = std::sqrt(dif);
    float t0 = (-rayDirDot - sqrt_dif) / rayDistSq; // 交点までt0倍のばせば球体とレイの衝突開始点になる
    float t1 = (-rayDirDot + sqrt_dif) / rayDistSq; // 交点までt1倍のばせば球体とレイの衝突終了点になる
   
    // t0の位置のほうがレイの起点から近くなり、rayDirのプラス方向の点がt0になるようにt0とt1を入れ替え
    if (t0 >= 0 && t1 >= 0 && t1 < t0 || t0 < 0 && t1 >= 0 || t0 < 0 && t1 < 0 && t0 < t1)
    {   // tmpに一時的にt1を保管して t0 と t1を入れ替え
        float tmp = t1; t1 = t0; t0 = tmp;
    }

    if (result0 != nullptr) // resultがnullptrじゃないときには球体とレイの交点も求める
        *result0 = rayDir * t0 + rayPos; // レイの方向をt0倍のばして球体との交点の位置を求める
    if (result1 != nullptr) // resultがnullptrじゃないときには球体とレイの交点も求める
        *result1 = rayDir * t1 + rayPos; // レイの方向をt1倍のばして球体との交点の位置を求める

    return (t0 >= 0) || (t1 >= 0); // rayDirのプラス方向に衝突点があるならtrue
}

// 無限円柱(シリンダー)がレイ(起点:rayPos 方向ベクトル:rayDir)と衝突するか? result0:円柱とレイが衝突した貫通開始点  result1:円柱とレイが衝突した貫通終了点
inline bool isCollisionCilinderInf_Ray(const Vector3& cilinderStart, const Vector3& cilinderEnd, float cilinderRadius, const Vector3& rayPos, const Vector3& rayDir, Vector3* result0 = nullptr, Vector3* result1 = nullptr)
{   //[円柱とレイの当たり判定] http://marupeke296.com/COL_3D_No25_RayToSilinder.html
    Vector3 p1 = cilinderStart - rayPos; // レイの起点からシリンダーの 始点 に向かうベクトル
    //Vector3 p2 = cilinderEnd - rayPos; // レイの起点からシリンダーの 終点 に向かうベクトル
    Vector3 s = cilinderEnd - cilinderStart; // シリンダーの軸に沿うベクトル

    // 各種内積値 v = rayDirとして考える
    float Dvv = rayDir.sqrMagnitude();
    float Dsv = dot(s, rayDir);
    float Dpv = dot(p1, rayDir);
    float Dss = s.sqrMagnitude();
    float Dps = dot(p1, s);
    float Dpp = p1.sqrMagnitude();
    float rr = cilinderRadius * cilinderRadius;

    if (Dss == 0.0f) return false; // 円柱が定義されない

    float A = Dvv - Dsv * Dsv / Dss;
    float B = Dpv - Dps * Dsv / Dss;
    float C = Dpp - Dps * Dps / Dss - rr;

    if (A == 0.0f) return false;

    float d = B * B - A * C;
    if (d < 0.0f) return false; // レイが円柱と衝突していない
    d = std::sqrtf(d);

    float t0 = (B - d) / A;
    float t1 = (B + d) / A;

    // t0の位置のほうがレイの起点から近くなり、rayDirのプラス方向の点がt0になるようにt0とt1を入れ替え
    if (t0 >= 0 && t1 >= 0 && t1 < t0 || t0 < 0 && t1 >= 0 || t0 < 0 && t1 < 0 && t0 < t1)
    {   // tmpに一時的にt1を保管して t0 と t1を入れ替え
        float tmp = t1; t1 = t0; t0 = tmp;
    }

    if (result0 != nullptr) // resultがnullptrじゃないときにはシリンダとレイの交点も求める
        *result0 = rayDir * t0 + rayPos; // レイの方向をt0倍のばしてシリンダとの交点の位置を求める
    if (result1 != nullptr) // resultがnullptrじゃないときには球体とシリンダの交点も求める
        *result1 = rayDir * t1 + rayPos; // レイの方向をt1倍のばしてシリンダとの交点の位置を求める

    return (t0 >= 0) || (t1 >= 0); // rayDirのプラス方向に衝突点があるならtrue
}

// カプセルがレイ(起点:rayPos 方向ベクトル:rayDir)と衝突するか? result0:カプセルとレイが衝突した貫通開始点  result1:カプセルとレイが衝突した貫通終了点
inline bool isCollisionCapsule_Ray(const Vector3& capsuleStart0, const Vector3& capsuleEnd1, float capsuleRadius,
    const Vector3& rayPos, const Vector3& rayDir, Vector3* result0 = nullptr, Vector3* result1 = nullptr)
{
    //[カプセルとレイの当たり判定] http://marupeke296.com/COL_3D_No26_RayToCapsule.html
    bool result0inSphere0 = false;
    bool result0inSphere1 = false;
    bool result0inCilinder01 = false;

    // Q1の検査
    if (isCollisionSphere_Ray(capsuleStart0, capsuleRadius, rayPos, rayDir, result0, result1) && dot(capsuleEnd1 - capsuleStart0, *result0 - capsuleStart0) <= 0.0f)
    {
        result0inSphere0 = true; // result0は球面上(capsuleStart0が中心の球)にある
    }
    else if (isCollisionSphere_Ray(capsuleEnd1, capsuleRadius, rayPos, rayDir, result0, result1) && dot(capsuleStart0 - capsuleEnd1, *result0 - capsuleEnd1) <= 0.0f)
    {
        result0inSphere1 = true; // result0は球面上(capsuleEnd1が中心の球)にある
    }
    else if (isCollisionCilinderInf_Ray(capsuleStart0, capsuleEnd1, capsuleRadius, rayPos, rayDir, result0, result1)
            && dot(capsuleEnd1 - capsuleStart0, *result0 - capsuleStart0) > 0.0f
            && dot(capsuleStart0 - capsuleEnd1, *result0 - capsuleEnd1) > 0.0f)
    {
        result0inCilinder01 = true; // result0は円柱面にある
    }
    else
        return false; // レイは衝突していない

    if (result1 != nullptr)
    {   // result1の検査
        float tx, ty, tz; // ダミー
        if (result0inSphere0 && dot(capsuleEnd1 - capsuleStart0, *result1 - capsuleStart0) <= 0.0f)
        {
            return true; // result0、result1 両方 球面上(capsuleStart0が中心の球)にある
        }
        else if (result0inSphere1 && dot(capsuleStart0 - capsuleEnd1, *result1 - capsuleEnd1) <= 0.0f)
        {
            return true; // result0、result1 両方 球面上(capsuleEnd1が中心の球)にある
        }
        else if (result0inCilinder01
            && dot(capsuleEnd1 - capsuleStart0, *result1 - capsuleStart0) > 0.0f
            && dot(capsuleStart0 - capsuleEnd1, *result1 - capsuleEnd1) > 0.0f)
        {
            return true; // result0、result1 両方 円柱面にある
        }
        else if (isCollisionSphere_Ray(capsuleStart0, capsuleRadius, rayPos, rayDir, nullptr, result1) && dot(capsuleEnd1 - capsuleStart0, *result1 - capsuleStart0) <= 0.0f)
        {
            return true; // result1 は 球面上(capsulStart0が中心の球)にある
        }
        else if (isCollisionSphere_Ray(capsuleEnd1, capsuleRadius, rayPos, rayDir, nullptr, result1) && dot(capsuleStart0 - capsuleEnd1, *result1 - capsuleEnd1) <= 0.0f)
        {
            return true; // result1 は 球面上(capsuleEnd1が中心の球)にある
        }
    }

    // result1 が円柱上にある事が確定したのでresult1を計算
    isCollisionCilinderInf_Ray(capsuleStart0, capsuleEnd1, capsuleRadius, rayPos, rayDir, nullptr, result1);
    return true; // result1 が円柱上にある
}


// 球体と有限の線分との当たり判定 lineStart:線の起点 lineEnd:線の終点 result:球体と線分の交点
inline bool isCollisionSphere_Line(const Vector3& center, float Radius,
                                   const Vector3& lineStart, const Vector3& lineEnd, Vector3* result0 = nullptr, Vector3* result1 = nullptr)
{
    Vector3    lineVec = lineEnd - lineStart; // endからstartを引いて有限の線分のベクトルを求める
    Vector3    nearestPoint = nearestPointOnLine(center, lineStart, lineVec, false); // 点center から一番近い直線lineVec上の点を求める

    float distSq = (center - nearestPoint).sqrMagnitude(); // 球体の中心と lineVec線上 の一番近い点の距離の2乗を求める

    if (distSq > Radius * Radius) return false; // 距離の2乗が球体の半径の2乗よりも長い場合は球と線は衝突していない
    // ↑有限の線なので半径との距離を比べてしまえばあとは 球体とレイ(無限の線)の判定と変わらない
    return isCollisionSphere_Ray(center, Radius, lineStart, lineVec.normalized(), result0, result1); // 球体とレイ(光線)の当たり判定
}

// 線分line0 と 線分line1 の当たり判定 isInfinite:無限の長さの線かどうか epsilon:判定の精度 result0:line0上の最も近い点 result1:line1上の最も近い点
inline bool isCollisionLine_Line(const Vector3& line0Start, const Vector3& line0End, const Vector3& line1Start, const Vector3& line1End,
                                 bool isInfinite = false, float epsilon = 0.001f, Vector3* result0 = nullptr, Vector3* result1 = nullptr)
{
    float t0, t1;
    Vector3 answer0, answer1; // answer0:line0上の最も近い点 answer1:line1上の最も近い点

    Vector3 v0 = line0End - line0Start; // line0 のベクトル
    Vector3 v1 = line1End - line1Start; // line1 のベクトル
    float lengthSq0 = v0.sqrMagnitude(); // ベクトルの大きさ(長さ)の2乗
    float lengthSq1 = v1.sqrMagnitude(); // ベクトルの大きさ(長さ)の2乗

    if (lengthSq0 < FLT_EPSILON) // 線分のベクトルの長さがほぼ0なら 実質、点line0Start と 線line1 との当たり判定でよい
    {
        answer0 = line0Start; // line0の起点が一番近い
        answer1 = nearestPointOnLine(line0Start, line1Start, v1, isInfinite); // 点line0Start から一番近い直線line1上の点を求める
        if (result0 != nullptr) *result0 = answer0; // resultがnullptrじゃなければ答えを代入しておく
        if (result1 != nullptr) *result1 = answer1;
    }
    else if (lengthSq1 < FLT_EPSILON)
    {
        answer1 = line1Start; // line1の起点が一番近い
        answer0 = nearestPointOnLine(line1Start, line0Start, v0, isInfinite); // 点line1Start から一番近い直線line0上の点を求める
        if (result0 != nullptr) *result0 = answer0; // resultがnullptrじゃなければ答えを代入しておく
        if (result1 != nullptr) *result1 = answer1;
    }
    else // line0 と line1 の大きさが 0 よりは充分に大きい なら 線どうしの当たり判定とみなして計算してもよい
    {
        //[参考動画:直線と直線の最短距離] https://youtu.be/b3fwHCKkro0?t=196
        // この動画の式を変形して dif01判定を事前分離し 0 での割り算を回避
#if 0
        float dv0v1 = -dot(v0, v1);// = dot(v0, -v1);
        float dv1v1 = lengthSq1;
        float dv0v0 = lengthSq0;
        Vector3    v01 = line1Start - line0Start;
        float _t1 = (dv0v1 * dot(v0, v01) - dv0v0 * dot(v1, v01)) / (dv1v1 * dv0v0 - dv0v1 * dv1v0);
        = (dv1v0 * dot(v0, v01) - lengthSq0 * dot(v1, v01)) / (lengthSq1 * lengthSq0 - dv0v1 * dv0v1);
        float _dif01 = lengthSq0 * lengthSq1 - dv0v1 * dv0v1;
        float _dot_v0v01 = -dot(v0, v01); // = dot(v0, -v01);
        float _dot_v1v01 = dot(v1, v01); // = -dot(v1, -v01);
        float ___t1 = (dv0v1 * dot(v0, -v01) - lengthSq0 * dot(v1, -v01)) / dif01;
        = (-dv0v1 * _dot_v1v01 + lengthSq0 * _dot_v1v01) / dif01;
        float _t0 = dot(v0, (line1Start + (-v1) * t1 - line0Start)) / lengthSq0
            = dot(v0, (v01 - v1 * t1)) / lengthSq0
            = (dot_v0v01 - dot_v0v1 * t1) / lengthSq0; // ムズイけど出た
#endif
        float dot_v0v1 = -dot(v0, v1);
        // aとbが同じ向きで平行なら |a||b| - a・b = 0に近づく https://www.nekonecode.com/math-lab/pages/collision2/point-and-segment/
        float dif01 = lengthSq0 * lengthSq1 - dot_v0v1 * dot_v0v1;
        float absDif01 = (dif01 < 0) ? -dif01 : dif01; // dif01の絶対値

        if (absDif01 < FLT_EPSILON) // 線が交差しないとき(最小誤差ε以内のとき)
        {
            // 交差しない線どうしの最も近い点を求める
            nearestPointOfParallelLine(line0Start, line0End, line1Start, line1End, isInfinite, &answer0, &answer1);

            if (result0 != nullptr) *result0 = answer0; // resultがnullptrじゃなければ答えを代入して関数の外に返す
            if (result1 != nullptr) *result1 = answer1;
        }
        else // 線が交差するとき(最小誤差εを超えるとき)
        {
            // 交差する線分の同士の最も近い点を求める
            Vector3    v01 = line1Start - line0Start;
            float dot_v0v01 = dot(v0, v01);
            float dot_v1v01 = -dot(v1, v01);
            t1 = (lengthSq0 * dot_v1v01 - dot_v0v01 * dot_v0v1) / dif01;
            t0 = (dot_v0v01 - dot_v0v1 * t1) / lengthSq0;

            answer0 = v0 * t0 + line0Start; // v0の方向をt0倍のばして一番近い直線上の点の位置を求める
            answer1 = v1 * t1 + line1Start; // v1の方向をt1倍のばして一番近い直線上の点の位置を求める

            // 有限の線の場合で、線を超えた位置(0~1.0の範囲外)に最も近い点が見つかった場合
            if ((isInfinite == false) && (t0 < 0.0f || 1.0f < t0 || t1 < 0.0f || 1.0f < t1))
            {   // 線分line0 と 線分line1 の【有限の線上範囲内】での一番近い点を求めなおす(0~1の範囲内に収まるもっとも近い点を求めなおす)
                nearestPointsOnFiniteLines(line0Start, line0End, line1Start, line1End, t0, t1, answer0, answer1);
            }

            if (result0 != nullptr) *result0 = answer0; // resultがnullptrじゃなければ答えを代入して関数の外に返す
            if (result1 != nullptr) *result1 = answer1;
        }
    }

    // 最も近い点どうしの距離の2乗 が epsilon(精度の2乗) 以下なら線どうしは衝突と判定
    if ((answer1 - answer0).sqrMagnitude() <= epsilon * epsilon) return true;

    return    false;
}

// カプセル0 capsule0 と カプセル1 capsule1 の当たり判定 result0:カプセル0の芯line0線上のline1に最も近い点 result1:カプセル1の芯line1線上のline0に最も近い点
inline bool isCollisionCapsule_Capsule(const Vector3& capsule0Start, const Vector3& capsule0End, float capsule0Radius,
                                       const Vector3& capsule1Start, const Vector3& capsule1End, float capsule1Radius,
                                       Vector3* result0 = nullptr, Vector3* result1 = nullptr)
{
    Vector3 nearest0;
    Vector3 nearest1;
    // カプセルどうしの当たり判定は、実質、線(有限)どうしの当たり判定(線の 距離epsilon が カプセル0半径 + カプセル1半径↓)
    bool isCollision = isCollisionLine_Line(capsule0Start, capsule0End, capsule1Start, capsule1End, false, capsule0Radius + capsule1Radius, &nearest0, &nearest1);   
    // カプセル軸上の最短距離をむすぶ 法線ベクトル:norm と 距離:mag を求める
    Vector3 v01 = nearest1 - nearest0; // 最短距離の2点 nearest0 から nearest1 へ向かうベクトル
    float mag01 = v01.magnitude(); // nearest0とnearest1の距離
    Vector3 norm01 = v01.normalized(); // 正規化normalizeする
    if (mag01 == 0.0f) // カプセルの軸が交差しているときはnearest0とnearest1が同じ点になっちゃう
    {
        Vector3 center0 = (capsule0Start + capsule0End) / 2.0f; // カプセル0の中心点
        Vector3 center1 = (capsule1Start + capsule1End) / 2.0f; // カプセル1の中心点
        norm01 = (center1 - center0).normalized(); // 中心同士の差ベクトルを法線に
        if (center0 == center1) // 中心位置が完全一致するときは
            norm01 = Vector3{ 1.0f,0.0f,0.0f }; // 仕方ないからx軸方向に引き離す(2DでもつじつまあうようにX軸で)
    }

    Vector3 answer0 = nearest0 + norm01 * capsule0Radius; // nearest0からnearest1の方向へ、カプセル半径0の半径 進んだ位置が衝突点
    Vector3 answer1 = nearest1 - norm01 * capsule1Radius; // nearest1からnearest0の方向へ、カプセル半径1の半径 進んだ位置が衝突点

    if (result0 != nullptr) *result0 = answer0;
    if (result1 != nullptr) *result1 = answer1;
    return isCollision;
}

// point がポリゴンpPolygonの内側にあるか判定 numVertices:ポリゴンの頂点の数 percent:99% の精度でポリゴンの内部
inline bool isInsidePolygon(const Vector3& point, const Vector3* pPoly, unsigned int numVertices, float percent = 0.99f)
{
    float cos_theta, length1, length2;
    Vector3    v1, v2;

    // ポリゴンの各頂点とpPointとの間の角度の合計が2πに近ければpPointはポリゴンの内部にある
    float angle = 0.0f;
    for (int i = 0; i < numVertices; i++)
    {
        v1 = pPoly[i] - point; // pointとi番目のポリゴンの頂点を結ぶベクトル
        v2 = pPoly[(i + 1) % numVertices] - point; // pPointとi+1番目の頂点を結ぶベクトル
        length1 = v1.magnitude();
        length2 = v2.magnitude();

        // 頂点と交点の距離が 0.0001f以下ならポリゴンの内部と判定
        if (length1 < 0.0001f || length2 < 0.0001f)
            return    true;

        // 二つのベクトルのあいだのcosθ
        cos_theta = dot(v1, v2) / (length1 * length2);

        // 角度の合計を足して合計を求める
        angle += std::acos(GET_CLAMP(cos_theta, -1.0f, 1.0f));
    }

    constexpr float pi2 = 2 * 3.14159265359; // 2π = 360°
    if (angle >= percent * pi2) return true; // 角度の合計が 2π×0.99f 以上ならばポリゴンの内部判定
    return    false;
}

// ポリゴン pPolyのエッジ と 球(中心:center 半径:Radius) が当たっているか? result0:ポリゴンに当たらない位置へ球を押し戻すベクトル
inline bool isCollisionPolyEdge_Sphere(const Vector3* pPoly, int numVertices, const Vector3& center, float Radius, Vector3* result0 = nullptr)
{
    for (int i = 0; i < numVertices; i++) // 全てのポリゴンの点について調べる
    {
        /* 球体の中心座標とポリゴンを結ぶ線分上の最も近い座標を求める */
        Vector3 polyEdgeVec = pPoly[(i + 1) % numVertices] - pPoly[i]; // ポリゴンの2点をつなぐ線(エッジ)
        Vector3 nearest = nearestPointOnLine(center, pPoly[i], polyEdgeVec, false); // 球の中心から最も近いポリゴン線上の 点nearset

        /* 線分上の座標と球体の中心座標の距離を求める */
        float nearestDistSq = (nearest - center).sqrMagnitude(); // 球の中心 から 点nearset までの距離の2乗

        // 球体から 1番近いポリゴン線上の点 と 球体中心 の距離 が球の半径以下なら当たっている
        if (nearestDistSq < Radius * Radius)
        {
            // ポリゴンと球体とが当たっているときは result0に ポリゴンに当たらないように球体を 押し戻すベクトル を求める
            if (result0 != NULL) // 一番近い点から球中心へのベクトルを norm(単位化) して それを (半径 - 半径から一番近い点)倍すればあたらない位置になる
                *result0 = (center - nearest).normalized() * (Radius - std::sqrt(nearestDistSq));

            return    true; // 当たっている
        }
    }

    return false; // 当たっていない
}


//-------------- 平面に関する当たり判定---------------------------

// 平面plane と 球(中心:center 半径:Radius)が衝突するか result:平面から球を押し戻した距離
inline bool isCollisionPlane_Sphere(const Plane4& plane,
                                    const Vector3& center, float Radius, float* resultDist = nullptr)
{
    float dist = plane.distance(center); // 平面と球の中心の距離
    float abs_dist = (dist < 0) ? -dist : dist; // 平面との距離の絶対値
    if (resultDist != nullptr) *resultDist = Radius - dist; // 半径 - 平面との距離をresultDistで返す
    return (abs_dist <= Radius); // 平面との距離が 半径以下なら当たっている:true
}

// 球(移動prev0→next1)が平面と衝突する時刻result_timeと位置result_posを求める
inline bool isCollisionPlane_Sphere_At(const Plane4& plane, //const Vector3& posOnPlane,
    const Vector3& prevCenter0, const Vector3& nextCenter1, float radius,
    float* result_time = nullptr, Vector3* result_pos = nullptr)
{
    // http://marupeke296.com/COL_MV_No2_SpherePlaneColliTimePos.html
    //Vector3 C0 = prevCenter0 - posOnPlane; // 平面上の一点から現在位置へのベクトル
    Vector3 v01 = nextCenter1 - prevCenter0; // 現在位置から予定位置までのベクトル

    // 平面と中心点の距離を算出
    //float Dot_C0 = dot(C0, plane.n);
    float dist0 = plane.distance(prevCenter0);
    float abs_dist0 = std::fabs(dist0);
    // 進行方向v01と法線nの関係をチェックするためdotを取る
    float dot_v01_n = dot(v01, plane.n);

    // 平面と平行に移動しながらめり込んでいる特殊なケース
    if ((0.00001f - std::fabs(dot_v01_n) > 0.0f) && (abs_dist0 < radius))
    {
        // 永遠にめり込みから抜け出せないので最大時刻を返す
        if (result_time != nullptr) *result_time = FLT_MAX;
        // 衝突位置は仕方ないので今の位置をresultとしてreturnする
        if (result_pos != nullptr) *result_pos = prevCenter0;
        return true;
    }

    // 交差時間の算出
    float answer_time = (radius - dist0) / dot_v01_n;
    if (result_time != nullptr) *result_time = answer_time;
   
    // 衝突位置の算出
    Vector3 answer_pos = prevCenter0 + answer_time * v01;
    if (result_pos != nullptr) *result_pos = answer_pos;

    // めり込んでいたら衝突として処理終了(距離が半径以下)
    if (abs_dist0 < radius) return true;

    // 平面の原点に向かう法線nに対して移動が逆向き(dotが0以上→cosθ>=0)なら衝突しない
    if (dot_v01_n >= 0) return false;

    // 時間が0~1の間にあれば衝突
    if (0 <= answer_time && answer_time <= 1) return true;

    return false;
}

// 平面plane(動いていない)に対する球0(反射率:refRate0)の反射後time時刻経過後の位置result_center0と速度result_v0を求める
inline bool getCollisionPosVecPlane_Sphere_After(float time, const Plane4& plane,
    const Vector3& center0, const Vector3& v0, float refRate0,
    Vector3* result_center0, Vector3* result_v0)
{
    // 反射後速度を算出
    Vector3 answer_v0 = v0 - (1 + refRate0) * dot(plane.n, v0) * plane.n;
    if (result_v0 != nullptr) *result_v0 = answer_v0;
    // 移動位置を計算
    Vector3 answer_center0 = center0 + answer_v0 * time;
    if (result_center0 != nullptr) *result_center0 = answer_center0;
    return true;
}

// 平面plane が AABB(軸並行境界ボックス) と 衝突するか result:平面からAABBを押し戻した距離
inline bool isCollisionPlane_AABB(const Plane4& plane,
    const Vector3& boxMin, const Vector3& boxMax, float* resultDist = nullptr)
{  
    //[平面とAABBの当たり判定] https://edom18.hateblo.jp/entry/2017/10/29/112908
    float positiveDistance = plane.positiveDistance_AABB(boxMin, boxMax);
    if (positiveDistance < 0)
    {
        if (resultDist != nullptr) *resultDist = -positiveDistance; // 平面までの距離の大きさを返す
        return false; // 平面の外側
    }

    float negativeDistance = plane.negativeDistance_AABB(boxMin, boxMax);
    if (negativeDistance < 0)
    {   // negativeDistanceは0以下なのでマイナスをつけて+の大きさにして返す
        if (resultDist != nullptr) *resultDist = -negativeDistance; // めり込んだ距離の大きさを返す
        return true; // 平面を横切る
    }

    if (resultDist != nullptr) *resultDist = negativeDistance; // めり込んだ距離の大きさを返す
    return false;
}

// 平面plane が OBB(有向境界ボックス) と 衝突するか result:平面からOBBを押し戻した距離
inline bool isCollisionPlane_OBB(const Vector3& planePos, const Plane4& plane,
                                 const Vector3& centerOBB, const Vector3& sizeOBB, const std::array<Vector3, 3>& axisOBB,
                                 float* result = nullptr)
{
    // 平面の法線に対するOBBの射影線の長さを算出 http://marupeke296.com/COL_3D_No14_OBBvsPlane.html
    float r = 0.0f; // 近接距離
    for (int i = 0; i < 3; ++i)
        r += std::abs(dot(axisOBB[i] * sizeOBB.xyz[i]/2, plane.n));

    // 平面とOBBの距離を算出
    float s = dot(centerOBB - planePos, plane.n);
    float abs_s = (s < 0) ? -s : s; // sの絶対値
   
    if (result != nullptr)
    {   // 平面からの押し戻し距離を算出
        if (s > 0)
            *result = r - abs_s;
        else
            *result = r + abs_s;
    }

    // 衝突判定
    if (abs_s - r < 0.0f) return true; // 衝突している
    return false; // 衝突していない
}


// 平面plane が レイ(起点:rayPos 方向ベクトル:rayDir) と 衝突するか result:平面とレイが衝突した場合の交点
inline bool isCollisionPlane_Ray(const Plane4& plane, const Vector3& rayPos, const Vector3& rayDir, Vector3* result = nullptr)
{
    Vector3 rayDirNorm = rayDir.normalized();
    Vector3 planeRay = rayPos - (-plane.n * plane.dist); // planeの起点からレイの始点へのベクトル
    float dot_Plane_RayNorm = dot(rayDirNorm, plane.n);
    if (dot_Plane_RayNorm == 0.0f) return false; // dot が 0の場合は平面と直線が平行になるのでレイと平面は交差しない

    if (result != nullptr)
    {
        float t = -dot(planeRay, plane.n) / dot_Plane_RayNorm; //[tの式] https://knzw.tech/raytracing/?page_id=78
        Vector3 answer = rayDirNorm * t + rayPos; // t倍にレイの方向を延長して 平面との交点を求める
        *result = answer;
    }
    return true;
}


// 平面 plane と 線(始点lineStart と 終点lineEnd を結ぶ線) が 接触するか
inline bool isCollisionPlane_Line(const Plane4& plane, const Vector3& lineStart, const Vector3& lineEnd, Vector3* result = nullptr)
{
    // 線の始点から平面までの距離 と 線の終点から平面までの距離 の符号が 両方+ または 両方- なら
    if (plane.distance(lineStart) * plane.distance(lineEnd) > 0)
    {    // 始点も終点も平面から見て 同じ サイドにあるので 線 と 平面はクロス しない
        return false;
    }
    else
    {    // 始点と終点が平面から見て  逆  サイドにあるので 線 と 平面はクロス する

        // 平面plane が レイ(起点:lineStart 方向ベクトル:lineEnd - lineStart) と 衝突するか
        return isCollisionPlane_Ray(plane, lineStart, lineEnd - lineStart, result); // resultに交点が求まる
    }
}

// 平面plane とカプセルの当たり判定 resultDist:押し戻し距離を返す
inline bool isCollisionPlane_Capsule(const Plane4& plane,
    const Vector3& capsuleStart, const Vector3& capsuleEnd, float capsuleRadius, float* resultDist = nullptr)
{
    float distStart = plane.distance(capsuleStart);
    float absDistStart = (distStart < 0) ? -distStart : distStart;
    float distEnd = plane.distance(capsuleEnd);
    float absDistEnd = (distEnd < 0) ? -distEnd : distEnd;
    // 線の始点から平面までの距離 と 線の終点から平面までの距離 の符号が 両方+ なら
    if (distStart > 0 && distEnd > 0)
    {   // 始点も終点も平面から見て法線の方向のサイドにあるので 線 と 平面はクロス しない
        if (resultDist != nullptr)
            *resultDist = (distStart < distEnd) ? capsuleRadius - distStart
                                                : capsuleRadius - distEnd; // 半径 - 平面との距離をresultDistで返す
        // startとendのうち平面との距離が近いほうが 半径以下なら当たっている
        if (distStart < distEnd)
            return (distStart <= capsuleRadius);
        else return (distEnd <= capsuleRadius);
    }
    if (resultDist != nullptr) // startとendでめり込みの距離が大きいほうの距離を押し戻す
        *resultDist = (distStart > 0) ? capsuleRadius - distEnd
                    : (distEnd > 0) ? capsuleRadius - distStart
                    : (distStart < distEnd) ? capsuleRadius - distStart : capsuleRadius - distEnd;
    return true;
}

// 三角ポリゴン pPoly(頂点数:numVertices 平面:plane上に存在) が 線分(始点:lineStart 終点:lineEnd) と 交わるか result:ポリゴンと線分の交点
inline bool isCollisionPoly_Line(const Vector3* pPoly, unsigned int numVertices, const Plane4& plane, const Vector3& lineStart, const Vector3& lineEnd, Vector3* result = nullptr)
{
    Vector3    answer; // 面と線分の交点
    // 面と線分の交点をまず求める
    if (isCollisionPlane_Line(plane, lineStart, lineEnd, &answer) == false) return false;

    if (isInsidePolygon(answer, pPoly, numVertices)) // 面と線の交わる交点 が ポリゴン内に存在する点なのかチェック
    {
        if (result != nullptr) *result = answer; // result に こたえ(ポリゴンと線分の交点) を設定し返す

        return true; // 交点がポリゴン内部にあった
    }
    return false; // 面と線が交わらない   もしくは   交わったとしても 交点がポリゴン内部になかった
}

// 三角ポリゴン pPoly(頂点数:numVertices 平面:plane上に存在) が レイ(始点:rayPos 方向:rayDir) と 交わるか result:ポリゴンとレイの交点
inline bool isCollisionPoly_Ray(const Vector3* pPoly, unsigned int numVertices, const Plane4& plane, const Vector3& rayPos, const Vector3& rayDir, Vector3* result = nullptr)
{
    //[この他にも色んなレイとポリゴンの交差判定法はある] https://pheema.hatenablog.jp/entry/ray-triangle-intersection
   
    Vector3    answer; // 面と線分の交点
    // 面とレイの交点をまず求める
    if (isCollisionPlane_Ray(plane, rayPos, rayDir, &answer) == false) return false;

    if (isInsidePolygon(answer, pPoly, numVertices)) // 面と線の交わる交点 が ポリゴン内に存在する点なのかチェック
    {
        if (result != nullptr) *result = answer; // result に こたえ(ポリゴンと線分の交点) を設定し返す

        return true; // 交点がポリゴン内部にあった
    }
    return false; // 面と線が交わらない   もしくは   交わったとしても 交点がポリゴン内部になかった
}

// 三角ポリゴン pPoly(頂点数:numVertices 平面:plane上に存在) が レイ(始点:rayPos 方向:rayDir) と 交わるか result:ポリゴンとレイの交点
inline bool isCollisionPoly_Sphere(const Vector3* pPoly, unsigned int numVertices, const Plane4& plane, const Vector3& center, float Radius, Vector3* result = nullptr)
{
    float distance;
    // まず、球とポリゴンのある平面が交差するかを求め、球の中心までの距離 distance をもとめる
    if (plane.sphereSide(center, Radius, &distance) != Plane4::Side::InterPlane) return false; // 交差していない

    Vector3 point = center - plane.n * distance; // 球の中心 - 面法線 * 距離 でポリゴン上の交点

    if (isInsidePolygon(point, pPoly, numVertices)) // 交点point が ポリゴン内に存在する点なのかチェック
    {
        if (result != nullptr)
        {
            float overDist = Radius - distance; // めり込みすぎた距離
            if (distance < 0) overDist = -overDist; // めり込みすぎた距離がマイナス方向の場合
            *result = plane.n * overDist; // result に こたえ(ポリゴンにめり込みすぎを押し戻すベクトル) を設定し返す
        }

        return true; // 交点がポリゴン内部にあった
    }
    return isCollisionPolyEdge_Sphere(pPoly, numVertices, center, Radius, result); // ポリゴンのエッジ部分が球体と交差しているか
}

#endif




では、準備したVector3.hとCollision3D.hを使って、3D空間の当たり判定を試してみましょう。

main.cppのコードにCollision3D.hのisCollision系関数を使って、3D空間の当たり判定をする処理を追加してみます。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include "Input.h"
#include "Screen.h"
#include "Ply.h"
#include "DataMV1.h"
#include "DataPly.h"
#include "Collision3D.h"

#include "Editor.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズWidth×Heightのカラービット数32ビットで起動
    SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    SetDrawScreen(DX_SCREEN_BACK);
  
    // ゲームのwhileループを開始する前の初期化処理
    float animTime = 0.0f; // アニメの現在時刻
    float animSpeed = 2.0f; // MV1のアニメの再生速度設定 2.0fなら2倍速
    float animStepTime = 1.0f; // MV1のアニメの毎時間の進む時間の設定
    std::shared_ptr<DataMV1> pDevilData = Resource::MakeShared<DataMV1>("Image/devil@kick.mv1");
    pDevilData->Load(); // 3Dモデルをmv1形式で読込み
    std::shared_ptr<DataMV1> pDevilAttackAnimData = Resource::MakeShared<DataMV1>("Image/devil@jump.mv1");
    pDevilAttackAnimData->Load(); // 3Dの追加アニメデータをmv1形式で読込み
    pDevilData->AttachAnim(*pDevilAttackAnimData); // アニメを追加でflyのほうをベースとしたモデルにアタッチ
    int animeButton = 0; // ボタンでアニメを切り替えられるようにする

    std::shared_ptr<Texture> pTexImage = Resource::MakeShared<Texture>("Image/dice.png");
    pTexImage->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
    assert((int)*pTexImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる

    std::shared_ptr<DataPly> pPlyData = Resource::MakeShared<DataPly>("Image/dice.ply");
    pPlyData->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
   


    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 0.0f; keyControlXYZ.y = 0.0f; keyControlXYZ.z = 0;
    VECTOR keyControlAngle = VGet(0.0f,0.0f,0.0f); // 回転

        // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen1 = SubScreen::Create()->Set(600, 450, 600, 100, 255, 0, "サブスクリーン1")->SetIsBarFrameShow(true,true);
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen2 = SubScreen::Create()->Set(200, 100, 150, 100, 255, 1, "サブスクリーン2\nバーを30で太めに")->SetFrame(1,GetColor(255,255,255),true,30);
    // サブのスクリーンを生成する 透明度を128にして半透明にしてみる
    std::shared_ptr<SubScreen> subscreen3 = SubScreen::Create()->Set(150, 450, 300, 100, 128, 1, "サブスクリーン3")->SetIsBarFrameShow(true)->SetBarColor(GetColor(0, 0, 255));
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen4 = SubScreen::Create()->Set(300, 100, 600, 100, 255, 1, "サブスクリーン4");

    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    Editor& editor = Editor::GetInstance(); // エディタのシングルトン参照を得る

    //奥行0.1~5000までをカメラの描画範囲とする
    DxLib::SetCameraNearFar(0.1f, 5000.0f);

    ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Input::Update(); // マウスのドラッグなどを更新
        SubScreen::UpdateScreens(); // スクリーンの位置やドラッグによる移動を更新する

        editor.UpdateKeyInput(); // エディタのキーのインプットを更新

        editor.UpdateMouseInput(); // エディタのマウスのインプットを更新
        editor.UpdateCamera(); // エディタのカメラを更新
       
        // キーボードの 0 1 キーで3Dアニメを切り替えられるようにする
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_0))
            animeButton = 0;
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_1))
            animeButton = 1;

        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        VECTOR position = VGet(keyControlXYZ.x, keyControlXYZ.y, keyControlXYZ.z); // 3Dモデルの位置

        float width =  pTexImage->m_XSize; float height =  pTexImage->m_YSize; // テクスチャ画像のサイズ
        VECTOR scale = VGet(1.0f, 1.0f, 1.0f); // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)とcキー(C)でx,y,z軸まわりに画像を回転
        if (PadInput & PAD_INPUT_A) keyControlAngle.x += speed;
        if (PadInput & PAD_INPUT_B) keyControlAngle.y += speed;
        if (PadInput & PAD_INPUT_C) keyControlAngle.z += speed;

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数
        const float xRotate = keyControlAngle.x * Deg2Rad;  const float yRotate = keyControlAngle.y * Deg2Rad; const float zRotate = keyControlAngle.z * Deg2Rad;


        MATRIX  matWorldDefault; // デフォルトのワールド行列
        DxLib::GetTransformToWorldMatrix(&matWorldDefault); // デフォルトのワールド行列を記憶しておく
        {
            //[ワールド行列] https://ny-program.hatenablog.com/entry/2019/09/15/114245
            MATRIX mat; // モデルのスケール×回転×移動を行列計算で設定する

            //[スケール]拡大縮小のスケール行列を得る
            mat = MGetScale(scale); // まずは拡大縮小のスケール行列からはじめる
            // [勉強]↓スケール行列は 斜めに x , y , zのスケールの倍率がならんでいる行列
            //mat.m[0][0] = scale.x;  mat.m[0][1] = 0.0f;    mat.m[0][2] = 0.0f;     mat.m[0][3] = 0.0f;
            //mat.m[1][0] = 0.0f;     mat.m[1][1] = scale.y; mat.m[1][2] = 0.0f;     mat.m[1][3] = 0.0f;
            //mat.m[2][0] = 0.0f;     mat.m[2][1] = 0.0f;    mat.m[2][2] = scale.z;  mat.m[2][3] = 0.0f;
            //mat.m[3][0] = 0.0f;     mat.m[3][1] = 0.0f;    mat.m[3][2] = 0.0f;     mat.m[3][3] = 1.0f;

            //[回転]
            MATRIX matRot;
            DxLib::CreateIdentityMatrix(&matRot);
            // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
            matRot = MMult(matRot, MGetRotZ(zRotate));
            matRot = MMult(matRot, MGetRotX(xRotate));
            matRot = MMult(matRot, MGetRotY(yRotate));
            mat = MMult(mat, matRot); //スケール × 回転

            //[移動]
            MATRIX matTrans = MGetTranslate(VGet(position.x, position.y, position.z)); // 位置positionをここで設定
            mat = MMult(mat, matTrans); //スケール×回転×移動

            DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4341

            DxLib::DrawPolygon32bitIndexed3D(pPlyData->Vertex.data(), pPlyData->Vertex.size(), pPlyData->Index.data(), pPlyData->numIndex / 3, (int)*pTexImage, FALSE); // 三角形ポリゴンをたくさん描画
        }
        DxLib::SetTransformToWorld(&matWorldDefault); // デフォルトのワールド行列に戻しておく

        DxLib::MV1DrawModel(devil3DModel); // 3Dモデル(.mv1形式)を描く

        DxLib::DrawFormatString(0, 100, GetColor(255, 255, 0), "x:%f y:%f Angle:%f %f %f", position.x, position.y, keyControlAngle.x, keyControlAngle.y, keyControlAngle.z);

        // ファイルのパス + ":" + アニメ名でアクセス
        std::string animPathName = (animeButton == 0) ? "Image/devil@kick.mv1:mixamo.com" : "Image/devil@jump.mv1:mixamo.com";
        assert(pDevilData->count(animPathName) > 0 && "指定されたアニメ名のアニメはMV1に付属していませんでした");
        animTime += animStepTime * animSpeed; // 1.0×2倍 なら2倍速でアニメ時刻が進む
        if (animTime > (*pDevilData)[animPathName].endTime)
            animTime = 0.0f; // 0秒目へとアニメをループさせる

        // 画面奥へ向かう光線レイを求める
        Vector3 rayStart = DxLib::ConvScreenPosToWorldPos(VGet(Input::MouseX, Input::MouseY, 0.0f));
        Vector3 rayEnd = DxLib::ConvScreenPosToWorldPos(VGet(Input::MouseX, Input::MouseY, 1.0f));
        DxLib::DrawLine3D(rayStart, rayEnd, GetColor(255, 255, 0));
       
        // 球0
        Vector3 sphere0Center{ 10,10,10 };
        float sphere0Radius = 10;
        Vector3 collision0;
        bool isCollision0 = isCollisionSphere_Line(sphere0Center, sphere0Radius, rayStart, rayEnd, &collision0); // 球と光線(レイ)の当たり判定
        DxLib::DrawSphere3D(sphere0Center, sphere0Radius, 16, (isCollision0) ? GetColor(255, 0, 0) : GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);
        if (isCollision0) DxLib::DrawSphere3D(collision0, 0.5, 16, GetColor(255, 0, 0), GetColor(255, 255, 255), TRUE); // レイと当たった部分に赤い球を表示

        // 球1
        Vector3 sphere1Center{ 10,-10,10 };
        float sphere1Radius = 10;
        Vector3 collision1;
        bool isCollision1 = isCollisionSphere_Ray(sphere1Center, sphere1Radius, rayStart, rayEnd - rayStart, &collision1); // 球と光線(レイ)の当たり判定
        DxLib::DrawSphere3D(sphere1Center, sphere1Radius, 16, (isCollision1) ? GetColor(0, 255, 0) : GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);
        if (isCollision1) DxLib::DrawSphere3D(collision1, 0.5, 16, GetColor(0, 255, 0), GetColor(255, 255, 255), TRUE); // レイと当たった部分に緑の球を表示


       
        // 再生時間をセットする
        DxLib::MV1SetAttachAnimTime((int)*pDevilData, (*pDevilData)[animPathName].index, animTime);
      
        subscreen1->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen1->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // サブの別のスクリーンの(0,0)位置を起点に文字列を描く
            DxLib::DrawString(0, 0, "サブのスクリーンを使ってWindowsみたいな自作ウインドウを描画してみる", GetColor(255, 255, 255));
        }
        subscreen1->DrawEnd();

        subscreen3->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen3->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // 半透明のスクリーンも描ける
            DxLib::DrawBox(0, 0, subscreen3->Width, subscreen3->Height, GetColor(0, 255, 0), TRUE);
            DxLib::DrawString(0, 0, "アルファを設定すれば\n半透明スクリーンも可能", GetColor(255, 255, 255));
        }
        subscreen3->DrawEnd();

        subscreen4->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen4->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // サブの別のスクリーンの(0,0)位置を起点に文字列を描く
            DxLib::DrawString(0, 0, "枠の無いスクリーンを使えば2Pの分割画面などにも応用できる", GetColor(255, 255, 255));
        }
        subscreen4->DrawEnd();


        SubScreen::DrawScreens(); // サブのスクリーンを全部描く

        editor.Draw(); // エディタのDraw処理

        ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    WaitKey();
    // DXライブラリの後始末
    DxLib_End();
    // ソフトの終了
    return 0;
}

いかがでしょう? レイと球の当たり判定が表示されたでしょうか?


ドラッグできるオブジェクトのコントロール点を用意して当たり判定の球やカプセルなどの幅や高さを調整できるようにする

DragObject.hを新規作成してVector3を継承して、ドラッグできる機能を追加しましょう。

#ifndef DRAG_OBJECT_H_
#define DRAG_OBJECT_H_

#include "DxLib.h"
#include "Vector3.h"
#include "Collision3D.h"

#include <memory>

// ドラッグできるVector3の点の基底クラス、Vector3との違いはvirtual仮想関数群を持ちUpdateなどの関数をoverrideできる設計になっていること
struct DragPoint : public Vector3
{
    DragPoint(float x = 0.0f, float y = 0.0f, float z = 0.0f) : Vector3(x, y, z) {}
    DragPoint(const Vector3& other) : Vector3(other.x, other.y, other.z) {}
    DragPoint(VECTOR other) : Vector3(other) {}
    virtual ~DragPoint() {};

    // 継承overrideして親や子などを持たせれば親の変更を子に伝搬させる設計も可能
    virtual void Update() {}

    // 継承overrideして所有オーナーなどを持たせれば半径などの変更をオーナーに伝搬させる設計も可能
    virtual void UpdateOwner() {}

    // screenXY:スクリーン上のXY位置 が ドラッグ点から幅:centerWidthの範囲内ならドラッグ当たり判定がtrue
    virtual bool isCollisionOnScreenXY(Vector3& resultCenter, const VECTOR& screenXY, const float centerWidth = 6.0f)
    {
        VECTOR pos2D = DxLib::ConvWorldPosToScreenPos(*this);
        if (pos2D.z < 0.0f || 1.0f < pos2D.z) return false; // カメラ視野の奥行クリップ値(近:0.0f~1.0f:遠)の範囲外ならfalse
        if (pos2D.x - centerWidth / 2 <= screenXY.x && screenXY.x <= pos2D.x + centerWidth / 2
            && pos2D.y - centerWidth / 2 <= screenXY.y && screenXY.y <= pos2D.y + centerWidth / 2)
        {
            resultCenter = *this; // ドラッグ点の中心点の3D位置を返す
            return true;
        }
        return false;
    }

    // override継承してscreenXYの位置にあるコントロール点を返す(なかったら空のweak_ptrがreturnされる)
    virtual bool GetControlPointOnScreenXY(std::weak_ptr<DragPoint>& pResult, const VECTOR& screenXY, const float centerWidth = 6.0f)
    {
        pResult = std::weak_ptr<DragPoint>(); // 観測対象のない空のweak_ptrを返す
        return false;
    }

    // rayPosの位置からrayDirの方向にレイを飛ばして衝突判定 resultCollision:衝突した点の衝突位置 resultCenter:衝突点の中心位置
    virtual bool isCollision(const Vector3& rayPos, const Vector3& rayDir, Vector3& resultCollision, Vector3& resultCenter)
    {
        return false; // overrideで継承して球やカプセルなどの当たり判定を実装する
    }

    // 点をドラッグできる点として中心位置をスクリーン上に四角形で描く
    virtual void Draw(const float centerWidth = 6.0f, unsigned int rectColor = GetColor(255, 255, 255), int FillFlag = TRUE)
    {
        VECTOR screenXY = DxLib::ConvWorldPosToScreenPos(*this);
        if (0 <= screenXY.z && screenXY.z <= 1.0f) // zは奥行クリップ値(near:0~1.0:far)⇒範囲外ならカメラ視野外
            DxLib::DrawBox(screenXY.x - centerWidth / 2, screenXY.y - centerWidth / 2,
                screenXY.x + centerWidth / 2, screenXY.y + centerWidth / 2, rectColor, FillFlag);
    }
};

// DrogPointの点を継承してドラッグできるカプセルや球体などのオブジェクトの基底オブジェクトを定義する
struct DragObject : public DragPoint
{
    unsigned int m_color; // オブジェクトを描画する色
    std::list<std::shared_ptr<DragPoint>> m_controlPoints; // 半径などを変えるためのコントロール点
    DragObject(float x = 0.0f, float y = 0.0f, float z = 0.0f, unsigned int color = GetColor(255, 255, 255))
        : DragPoint(x, y, z), m_color{ color } {}
    DragObject(const Vector3& other, unsigned int color = GetColor(255, 255, 255))
        : DragPoint(other.x, other.y, other.z), m_color{ color } {}
    DragObject(VECTOR other, unsigned int color = GetColor(255, 255, 255)) : DragPoint(other), m_color{ color } {}
    virtual ~DragObject() {};

    virtual void Update() override // 配下のコントロール点をすべて更新
    {
        for (auto itr = m_controlPoints.begin(); itr != m_controlPoints.end(); )
        {
            auto pControlPoint = *itr; // コントロール点への共有ポインタ
            pControlPoint->Update(); // 配下のコントロール点をすべて更新
            ++itr; // for文のイテレータを次の点へすすめる
        }
    }

    // screenXYの位置にあるコントロール点を返す なかったら空のweak_ptrがreturnされる
    virtual bool GetControlPointOnScreenXY(std::weak_ptr<DragPoint>& pResult, const VECTOR& screenXY, const float centerWidth = 6.0f) override
    {
        for (auto itr = m_controlPoints.begin(); itr != m_controlPoints.end(); )
        {
            auto pControlPoint = *itr; // コントロール点への共有ポインタ
            ++itr; // for文のイテレータを次の点へすすめる

            Vector3 collisionCenter; // 衝突点を探す
            if (pControlPoint->isCollisionOnScreenXY(collisionCenter, screenXY, centerWidth)) // スクリーン上の四角のドラッグ点
            {
                pResult = pControlPoint;
                return true;
            }
        }
        pResult = std::weak_ptr<DragPoint>(); // 見つからなかったら観測対象のない空のweak_ptrを返す
        return false;
    }
};


// 球体の当たり判定をもつドラッグ中心点
struct DragSphere : public DragObject
{
public:
    float m_height; // ドラッグ球の当たり判定の大きさ(球の高さ)

    // 球の高さ(=半径の1/2)をコントロールする点
    struct ControlPoint : public DragObject
    {
        DragSphere& m_owner; // コントロール点のオーナー所有者
        ControlPoint(DragSphere& owner, unsigned int color = GetColor(255, 255, 255))
            : m_owner{ owner }, DragObject(owner.x + owner.m_height / 2, owner.y, owner.z, color) {}
        virtual ~ControlPoint() {}
        virtual void Update() override { this->xyz = (m_owner + Vector3{ m_owner.m_height / 2, 0.0f, 0.0f }).xyz; }
        virtual void UpdateOwner() override
        {
            m_owner.m_height = (*this - m_owner).magnitude() * 2; // コントロール点との距離の2倍=高さ
            m_owner.xyz = (*this + (m_owner - *this).normalized() * m_owner.m_height / 2).xyz;
            m_owner.Update(); // オーナーの配下のコントロール点すべてに更新を伝搬
        }
    };

    DragSphere(float x = 0.0f, float y = 0.0f, float z = 0.0f, float height = 1.0f, unsigned int color = GetColor(255, 255, 255))
        : DragObject(x, y, z, color), m_height{ height }
    {
        m_controlPoints.emplace_back(std::make_shared<ControlPoint>(*this, color));
    }
    virtual ~DragSphere() {}

    // 描画などに必要なパラメータを得る
    inline void GetParams(Vector3& center, float& radius)
    {
        center = *this; // x,y,zのデータを読み出し
        radius = m_height / 2; // 半径は高さの 1/2
    }

    // rayPosの位置からrayDirの方向にレイを飛ばして球との衝突判定 resultCollision:衝突した点の衝突位置 resultCenter:衝突点の中心位置
    virtual bool isCollision(const Vector3& rayPos, const Vector3& rayDir, Vector3& resultCollision, Vector3& resultCenter) override
    {
        if (isCollisionSphere_Ray(*this, m_height / 2, rayPos, rayDir, &resultCollision))
        {
            resultCenter = *this; // 衝突した点の中心位置をresultで返す
            return true; // ドラッグ点とスクリーンのマウスカーソルから飛ばしたレイが衝突した
        }
        return false; // ドラッグ点とスクリーンのマウスカーソルから飛ばしたレイが衝突しなかった
    }

    // ドラッグ点の中心位置と球体を描く
    virtual void Draw(const float centerWidth = 6.0f, unsigned int rectColor = GetColor(255, 255, 255), int FillFlag = TRUE) override
    {
        Vector3 center = *this; // 中心位置のxyzのデータを読み出し
        int DivNum = 8; // 球面ポリゴンの分割数
        // 球体を描く
        DxLib::DrawSphere3D(center, m_height / 2, DivNum, m_color, GetColor(255, 255, 255), FALSE);
        // x軸(R:赤) y軸(G:緑) z軸(B:青)をライン表示する
        DxLib::DrawLine3D(center, center + Vector3{ m_height / 2, 0.0f, 0.0f }, GetColor(255, 0, 0));
        DxLib::DrawLine3D(center, center + Vector3{ 0.0f, m_height / 2, 0.0f }, GetColor(0, 255, 0));
        DxLib::DrawLine3D(center, center + Vector3{ 0.0f, 0.0f, m_height / 2 }, GetColor(0, 0, 255));

        for (auto controlPoint : m_controlPoints) // 配下のコントロール点を描く
            controlPoint->Draw(centerWidth, rectColor);

        DragPoint::Draw(centerWidth, rectColor, FillFlag); // スクリーン上のドラッグ中心点を描く
    }
};

// カプセルの当たり判定をもつドラッグ中心点
struct DragCapsule : public DragObject
{
protected:
    float m_height{ 0.0f }; // カプセルの高さ
    Axis3D m_direction{ Axis3D::Y }; // カプセルの軸の方向
public:
    // カプセルの半径
    float m_radius{ 1.0f };
    // カプセルの高さ
    inline Vector3 height() const { return m_height; }
    // カプセルの向く方向軸(height高さの方向軸)
    inline Axis3D direction() const { return m_direction; }
    // カプセルの軸の長さ(高さ)の半分(ハーフ)の位置
    inline Vector3 axisHalf() const {
        return Vector3{ (m_direction == Axis3D::X) ? m_height / 2.0f : 0.0f,
                        (m_direction == Axis3D::Y) ? m_height / 2.0f : 0.0f,
                        (m_direction == Axis3D::Z) ? m_height / 2.0f : 0.0f };
    }
    // カプセルの軸の始点
    inline Vector3 start() const { return *this - axisHalf(); }
    // カプセルの軸の終点
    inline Vector3 end() const { return *this + axisHalf(); }

    // カプセルの半径をコントロールする点
    struct ControlPointRadius : public DragObject
    {
        DragCapsule& m_owner; // コントロール点のオーナー所有者
        ControlPointRadius(DragCapsule& owner, unsigned int color = GetColor(255, 255, 255))
            : m_owner{ owner }, DragObject(owner.x + owner.m_radius, owner.y, owner.z, color) {}
        virtual ~ControlPointRadius() {}
        virtual void Update() override { this->xyz = (m_owner + Vector3{ m_owner.m_radius, 0.0f, 0.0f }).xyz; }
        virtual void UpdateOwner() override
        {
            m_owner.m_radius = (*this - m_owner).magnitude(); // オーナーの中心点との距離が半径
            m_owner.xyz = (*this + (m_owner - *this).normalized() * m_owner.m_radius).xyz;
            m_owner.Update(); // オーナーの配下のコントロール点すべてに更新を伝搬
        }
    };

    // カプセルの高さをコントロールする点
    struct ControlPointHeight : public DragObject
    {
        DragCapsule& m_owner; // コントロール点のオーナー所有者
        ControlPointHeight(DragCapsule& owner, unsigned int color = GetColor(255, 255, 255))
            : m_owner{ owner }, DragObject(owner.end(), color) {}
        virtual ~ControlPointHeight() {}
        virtual void Update() override { this->xyz = m_owner.end().xyz; }
        virtual void UpdateOwner() override
        {
            m_owner.m_height = (*this - m_owner).magnitude() * 2; // コントロール点との距離の2倍=高さ
            m_owner.xyz = (*this + (m_owner - *this).normalized() * m_owner.m_height / 2).xyz;
            m_owner.Update(); // オーナーの配下のコントロール点すべてに更新を伝搬
        }
    };

    DragCapsule(float x = 0.0f, float y = 0.0f, float z = 0.0f, float radius = 1.0f, float height = 0.0f, Axis3D direction = Axis3D::Y, unsigned int color = GetColor(255, 255, 255))
        : DragObject(x, y, z, color), m_radius{ radius }, m_height{ height }, m_direction{ direction }
    {
        m_controlPoints.emplace_back(std::make_shared<ControlPointRadius>(*this, color));
        m_controlPoints.emplace_back(std::make_shared<ControlPointHeight>(*this, color));
    }
    virtual ~DragCapsule() {}

    // 描画などに必要なパラメータを得る
    inline void GetParams(Vector3& center, Vector3& start, Vector3& end, float& radius)
    {
        center = *this; // x,y,zのデータを読み出し
        Vector3 axis_half = axisHalf(); // カプセルの軸の長さ(高さ)の半分(ハーフ)の位置
        start = center - axis_half; // カプセルの軸の始点(-なので下側)
        end = center + axis_half; // カプセルの軸の始点(+なので上側)
        radius = m_radius;
    }

    // rayPosの位置からrayDirの方向にレイを飛ばしてカプセルとの衝突判定 resultCollision:衝突した点の衝突位置 resultCenter:衝突点の中心位置
    virtual bool isCollision(const Vector3& rayPos, const Vector3& rayDir, Vector3& resultCollision, Vector3& resultCenter) override
    {
        float radius;
        Vector3 center,axisStart, axisEnd;
        GetParams(center, axisStart, axisEnd, radius); // 必要なパラメータを得る
        // カプセルとレイの当たり判定
        if (isCollisionCapsule_Ray(axisStart, axisEnd, m_radius, rayPos, rayDir, &resultCollision))
        {
            resultCenter = center; // 衝突した点の中心位置をresultで返す
            return true; // ドラッグ点を中心とするカプセルと飛ばしたレイが衝突した
        }
        return false; // ドラッグ点を中心とするカプセルと飛ばしたレイが衝突しなかった
    }

    // ドラッグ点の中心位置とカプセルを描く
    virtual void Draw(const float centerWidth = 6.0f, unsigned int rectColor = GetColor(255, 255, 255), int FillFlag = TRUE) override
    {
        float radius;
        Vector3 center, axisStart, axisEnd;
        GetParams(center, axisStart, axisEnd, radius); // 必要なパラメータを得る
        int DivNum = 8; // カプセル表面ポリゴンの分割数
        // カプセルを描く
        DxLib::DrawCapsule3D(axisStart, axisEnd, m_radius, DivNum, m_color, GetColor(255, 255, 255), FALSE);
        // x軸(R:赤) y軸(G:緑) z軸(B:青)をライン表示する
        float axisX = (m_direction == Axis3D::X) ? m_height / 2.0f : m_radius;
        float axisY = (m_direction == Axis3D::Y) ? m_height / 2.0f : m_radius;
        float axisZ = (m_direction == Axis3D::Z) ? m_height / 2.0f : m_radius;
        DxLib::DrawLine3D(center, center + Vector3{ axisX, 0.0f, 0.0f }, GetColor(255, 0, 0));
        DxLib::DrawLine3D(center, center + Vector3{ 0.0f, axisY, 0.0f }, GetColor(0, 255, 0));
        DxLib::DrawLine3D(center, center + Vector3{ 0.0f, 0.0f, axisZ }, GetColor(0, 0, 255));

        for (auto controlPoint : m_controlPoints)
            controlPoint->Draw(centerWidth, rectColor);

        DragPoint::Draw(centerWidth, rectColor, FillFlag); // スクリーン上のドラッグ中心点を描く
    }
};

// ボックスの当たり判定をもつドラッグ中心点
struct DragBox : public DragObject
{
public:
    Vector3 m_Size{ 1.0f,1.0f,1.0f }; // ボックスのサイズ

    // ボックスの幅をコントロールする点
    struct ControlPointWidth : public DragObject
    {
        DragBox& m_owner; // コントロール点のオーナー所有者
        ControlPointWidth(DragBox& owner, unsigned int color = GetColor(255, 255, 255))
            : m_owner{ owner }, DragObject(owner.x + owner.m_Size.x / 2, owner.y, owner.z, color) {}
        virtual ~ControlPointWidth() {}
        virtual void Update() override { this->xyz = Vector3{ m_owner.x + m_owner.m_Size.x / 2,m_owner.y,m_owner.z }.xyz; }
        virtual void UpdateOwner() override
        {
            m_owner.m_Size.x = (*this - m_owner).magnitude() * 2; // コントロール点との距離の2倍=高さ
            m_owner.xyz = (*this + (m_owner - *this).normalized() * m_owner.m_Size.x / 2).xyz;
            m_owner.Update(); // オーナーの配下のコントロール点すべてに更新を伝搬
        }
    };

    // ボックスの高さをコントロールする点
    struct ControlPointHeight : public DragObject
    {
        DragBox& m_owner; // コントロール点のオーナー所有者
        ControlPointHeight(DragBox& owner, unsigned int color = GetColor(255, 255, 255))
            : m_owner{ owner }, DragObject(owner.x, owner.y + owner.m_Size.y / 2, owner.z, color) {}
        virtual ~ControlPointHeight() {}
        virtual void Update() override { this->xyz = Vector3{ m_owner.x, m_owner.y + m_owner.m_Size.y / 2, m_owner.z }.xyz; }
        virtual void UpdateOwner() override
        {
            m_owner.m_Size.y = (*this - m_owner).magnitude() * 2; // コントロール点との距離の2倍=高さ
            m_owner.xyz = (*this + (m_owner - *this).normalized() * m_owner.m_Size.y / 2).xyz;
            m_owner.Update(); // オーナーの配下のコントロール点すべてに更新を伝搬
        }
    };

    // ボックスの奥行zをコントロールする点
    struct ControlPointDepth : public DragObject
    {
        DragBox& m_owner; // コントロール点のオーナー所有者
        ControlPointDepth(DragBox& owner, unsigned int color = GetColor(255, 255, 255))
            : m_owner{ owner }, DragObject(owner.x, owner.y, owner.z + owner.m_Size.z / 2, color) {}
        virtual ~ControlPointDepth() {}
        virtual void Update() override { this->xyz = Vector3{ m_owner.x, m_owner.y, m_owner.z + m_owner.m_Size.z / 2 }.xyz; }
        virtual void UpdateOwner() override
        {
            m_owner.m_Size.z = (*this - m_owner).magnitude() * 2; // コントロール点との距離の2倍=高さ
            m_owner.xyz = (*this + (m_owner - *this).normalized() * m_owner.m_Size.z / 2).xyz;
            m_owner.Update(); // オーナーの配下のコントロール点すべてに更新を伝搬
        }
    };

    DragBox(float x = 0.0f, float y = 0.0f, float z = 0.0f, Vector3 size = Vector3{ 1.0f, 1.0f, 1.0f }, unsigned int color = GetColor(255, 255, 255))
        : DragObject(x, y, z, color), m_Size{ size }
    {
        m_controlPoints.emplace_back(std::make_shared<ControlPointWidth>(*this, color));
        m_controlPoints.emplace_back(std::make_shared<ControlPointHeight>(*this, color));
        m_controlPoints.emplace_back(std::make_shared<ControlPointDepth>(*this, color));
    }
    virtual ~DragBox() {}

    // サイズのx,y,zのどれかがマイナスならプラスに直す
    inline Vector3 sizeMinMax()
    {
        return Vector3{ (m_Size.x < 0) ? -m_Size.x : m_Size.x,
                         (m_Size.y < 0) ? -m_Size.y : m_Size.y,
                         (m_Size.z < 0) ? -m_Size.z : m_Size.z };
    }

    // 描画などに必要なパラメータを得る
    inline void GetParams(Vector3& center, Vector3& boxMin, Vector3& boxMax)
    {
        center = *this; // x,y,zのデータを読み出し
        Vector3 boxHalfSize = sizeMinMax() / 2;
        boxMin = center - boxHalfSize;
        boxMax = center + boxHalfSize;
    }

    // rayPosの位置からrayDirの方向にレイを飛ばしてカプセルとの衝突判定 resultCollision:衝突した点の衝突位置 resultCenter:衝突点の中心位置
    virtual bool isCollision(const Vector3& rayPos, const Vector3& rayDir, Vector3& resultCollision, Vector3& resultCenter) override
    {
        Vector3 center, boxMin, boxMax;
        GetParams(center, boxMin, boxMax); // 必要なパラメータを得る
        // ボックスとレイの当たり判定
        if (isCollisionAABB_Ray(boxMin, boxMax, rayPos, rayDir, &resultCollision))
        {
            resultCenter = center; // 衝突した点の中心位置をresultで返す
            return true; // ドラッグ点を中心とするカプセルと飛ばしたレイが衝突した
        }
        return false; // ドラッグ点を中心とするボックスと飛ばしたレイが衝突しなかった
    }

    // ドラッグ点の中心位置とボックスを描く
    virtual void Draw(const float centerWidth = 6.0f, unsigned int rectColor = GetColor(255, 255, 255), int FillFlag = TRUE) override
    {
        Vector3 center, boxMin, boxMax;
        GetParams(center, boxMin, boxMax); // 必要なパラメータを得る
        // ボックスを描く
        DxLib::DrawCube3D(boxMin, boxMax, m_color, GetColor(255, 255, 255), FALSE);
        // x軸(R:赤) y軸(G:緑) z軸(B:青)をライン表示する
        DxLib::DrawLine3D(center, center + Vector3{ m_Size.x / 2, 0.0f, 0.0f }, GetColor(255, 0, 0));
        DxLib::DrawLine3D(center, center + Vector3{ 0.0f, m_Size.y / 2, 0.0f }, GetColor(0, 255, 0));
        DxLib::DrawLine3D(center, center + Vector3{ 0.0f, 0.0f, m_Size.z / 2 }, GetColor(0, 0, 255));

        for (auto controlPoint : m_controlPoints) // 配下のコントロール点を描く
            controlPoint->Draw(centerWidth, rectColor);

        DragPoint::Draw(centerWidth, rectColor, FillFlag); // スクリーン上のドラッグ中心点を描く
    }
};

#endif


Editor.hを変更してドラッグ点のリストを移動できるようにします。

#ifndef EDITOR_H_
#define EDITOR_H_

#include "DxLib.h"
#include "Input.h"
#include "Screen.h"
#include "Vector3.h"
#include "DragObject.h"


#include "Singleton.h"

#include <list> // 連結リストstd::listを使う
#include <memory>
#include <cmath>


class Editor : public Singleton<Editor>
{
public:

    struct Camera
    {
        float x = 0.f, y = 0.0f, z = -10.0f; // カメラはz軸マイナス位置からz軸プラス向きに原点を見る形で初期化される
        inline operator VECTOR() { return VGet(x, y, z); }
        inline operator Vector3() const { return Vector3{ x, y, z }; }

        inline Camera& operator +=(VECTOR move) { x += move.x; y += move.y; z += move.z; return *this; } // VECTOR型を足したときはカメラを移動させる
        float VRotate = 0.0f, HRotate = 0.0f, TRotate = 0.0f; // X軸 V,Y軸 H,Z軸 T まわりのカメラのオイラー回転の角度
        float wheelSpeed = 1.0f; // マウスのホイールと連動した際のカメラのスピード

        // スクリーンの位置からカメラ画面の視界(ワールド座標)の奥への方向の単位ベクトル(ノルム)を求める
        VECTOR GetCameraWorldDirByScreenXY(float screenX = (float)Input::MouseX, float screenY = (float)Input::MouseY)
        {    // スクリーン(X,Y)からマウスの指すワールド座標(X,Y,Z)を求める
            VECTOR currentPos = DxLib::ConvScreenPosToWorldPos(VGet(screenX, screenY, 0.0f));
            // カメラからマウスの指す位置への方向の単位ベクトル(ノルム)を求める
            return DxLib::VNorm(DxLib::VGet(currentPos.x - x, currentPos.y - y, currentPos.z - z));
        }

        // スクリーンの2Dの start位置XY から end位置XY の方向 が ワールド座標3D においてどんなベクトルになるかを求める
        VECTOR GetWorldDirByCameraScreenXYDir(float screenStartX = (float)Input::MouseX, float screenStartY = (float)Input::MouseY,
            float screenEndX = (float)(Input::MouseX + 1), float screenEndY = (float)(Input::MouseY + 1))
        {    // スクリーン(X,Y)からマウスの指すワールド座標(X,Y,Z)を求める
            VECTOR startPos = DxLib::ConvScreenPosToWorldPos(VGet(screenStartX, screenStartY, 0.0f));
            VECTOR endPos = DxLib::ConvScreenPosToWorldPos(VGet(screenEndX, screenEndY, 0.0f));
            // ワールド座標におけるstartからendへ向かう方向のベクトルを求める
            return DxLib::VGet(endPos.x - startPos.x, endPos.y - startPos.y, endPos.z - startPos.z);
        }
    };
    Camera camera;

    bool isDraggingPoints = false; // 点をドラッグ中かどうか
    std::weak_ptr<DragPoint> pDraggingPoint; // ドラッグ中の点への弱共有ポインタ
    int startDragX = -1, startDragY = -1; // ドラッグを開始したスクリーン上の点のXY位置
    Vector3 startDrag2D; // ドラッグを開始したスクリーン位置(zはクリップ面位置near:0.0f~1.0f:far)
    Vector3 startDrag3D; // ドラッグを開始した3D位置

   
    int prevDragX = -1, prevDragY = -1; // 前フレームでのスクリーン上でのドラッグXY位置
   
    bool isInitFinished = false; // 一回でもInitされたかのフラグ
   
    void Init()
    {
        if (isInitFinished) return; // 最初の一回だけInit処理をするためのフラグ
        isInitFinished = true;
    }

    void UpdateKeyInput()
    {
        Init();

        float camXYZSpeed = 1.0f;
        // スクリーンの位置からカメラ画面の視界(ワールド座標)の奥への方向のの単位ベクトル(ノルム)を求める
        VECTOR camNorm = camera.GetCameraWorldDirByScreenXY((float)Input::MouseX, (float)Input::MouseY);
        // キーボードのWSキーでカメラ視点を手前や奥に移動できるようにする
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_W))
            camera += VGet(camNorm.x * camXYZSpeed, camNorm.y * camXYZSpeed, camNorm.z * camXYZSpeed);
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_S))
            camera += VGet(-camNorm.x * camXYZSpeed, -camNorm.y * camXYZSpeed, -camNorm.z * camXYZSpeed);

        // スクリーンの 2D上のX方向 が ワールド座標 3D ではどんな方向ベクトルになるかを求める
        VECTOR xCamDir = VNorm(camera.GetWorldDirByCameraScreenXYDir((float)Input::MouseX, (float)Input::MouseY, (float)(Input::MouseX + 10), (float)Input::MouseY));
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_D))
            camera += VGet(xCamDir.x * camXYZSpeed, xCamDir.y * camXYZSpeed, xCamDir.z * camXYZSpeed);
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_A))
            camera += VGet(-xCamDir.x * camXYZSpeed, -xCamDir.y * camXYZSpeed, -xCamDir.z * camXYZSpeed);
       
        // スクリーンの 2D上のY方向 が ワールド座標 3D ではどんな方向ベクトルになるかを求める
        VECTOR yCamDir = VNorm(camera.GetWorldDirByCameraScreenXYDir((float)Input::MouseX, (float)Input::MouseY, (float)Input::MouseX, (float)(Input::MouseY + 10)));
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_Q))
            camera += VGet(yCamDir.x * camXYZSpeed, yCamDir.y * camXYZSpeed, yCamDir.z * camXYZSpeed);
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_E))
            camera += VGet(-yCamDir.x * camXYZSpeed, -yCamDir.y * camXYZSpeed, -yCamDir.z * camXYZSpeed);
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_V))
            camera.VRotate -= 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_B))
            camera.VRotate += 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_H))
            camera.HRotate -= 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_J))
            camera.HRotate += 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_T))
            camera.TRotate -= 1.0f;
       
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_Y))
            camera.TRotate += 1.0f;
    }

    void UpdateDragPoints(std::list<std::weak_ptr<DragPoint>>* pDragPoints = nullptr)
    {
        bool isMouseDragging = (Input::Click[Mouse::DragL] || Input::Click[Mouse::DragR]) && Screen::IsInsideWindow();
        if (!isDraggingPoints && SubScreen::isMouseOvered) { isDraggingPoints = false; return; }
       
        VECTOR nearestCollision; // 一番近い衝突点を探す
        float nearestDistSq = DxLib::GetCameraFar() * DxLib::GetCameraFar(); // カメラ視野のクリップ設定のFar(最遠)の2乗
        float nearestDist = DxLib::GetCameraFar(); // 一番近い点までの距離を探す
       
        if (!isDraggingPoints)
        {
            for (auto itr = pDragPoints->begin(); itr != pDragPoints->end(); )
            {
                auto pDragPoint = itr->lock(); // ドラッグ点への共有ポインタ
                if (!pDragPoint) // 弱リンク先の点本体がすでに削除済の点だったら
                {    //[for文でたどっている途中の削除] https://piy.hatenadiary.org/entry/20090616/1245173713
                    itr = pDragPoints->erase(itr); // ドラッグ点リンクをリストから削除して
                    continue; // ループで次の点へ進む
                }
                ++itr; // for文のイテレータを次の点へすすめる

                VECTOR screenXY = VGet(Input::MouseX, Input::MouseY, 0.0f); // スクリーン上のマウス位置
                // マウスポインタの位置から画面奥へのレイ
                VECTOR rayPos = DxLib::ConvScreenPosToWorldPos(screenXY);
                VECTOR rayDir = camera.GetCameraWorldDirByScreenXY(Input::MouseX, Input::MouseY);

                std::weak_ptr<DragPoint> pControlPoint; // コントロール点への弱共有ポインタ
                Vector3 collisionPoint, collisionCenter; // 衝突点を探す
                if (pDragPoint->isCollisionOnScreenXY(collisionCenter, screenXY, 6.0f)) // スクリーン上の四角のドラッグ点
                {
                    pDraggingPoint = pDragPoint;
                    startDrag3D = collisionCenter;
                    startDrag2D = DxLib::ConvWorldPosToScreenPos(collisionCenter);
                    startDragX = Input::MouseX, startDragY = Input::MouseY;
                    isDraggingPoints = true;
                    break;
                }
                else if (pDragPoint->GetControlPointOnScreenXY(pControlPoint, screenXY, 6.0f))
                {
                    pDraggingPoint = pControlPoint;
                    startDrag3D = *pControlPoint.lock();
                    startDrag2D = DxLib::ConvWorldPosToScreenPos(startDrag3D);
                    startDragX = Input::MouseX, startDragY = Input::MouseY;
                    isDraggingPoints = true;
                    break;
                }
                else if (pDragPoint->isCollision(rayPos, rayDir, collisionPoint, collisionCenter))
                {
                    float distSq = (collisionPoint - camera).sqrMagnitude(); // カメラと衝突位置の2乗
                    if (distSq < nearestDistSq) // 現在の最短距離の2乗(nearestDistSq)以下か?
                    {
                        nearestDistSq = distSq; // 一番近い距離の2乗を更新
                        //nearestDist = (collisionCenter - camera).magnitude(); // 一番近い距離を更新
                        nearestCollision = collisionPoint; // 一番近い衝突点を更新して一番手前を探す
                        pDraggingPoint = pDragPoint;
                        startDrag2D = DxLib::ConvWorldPosToScreenPos(collisionCenter);
                        startDragX = Input::MouseX, startDragY = Input::MouseY;
                        isDraggingPoints = true;
                    }
                }
            }
        }

        auto pDraggingPos = pDraggingPoint.lock(); // ドラッグ中の点をlock
        if (!isMouseDragging || !pDraggingPos)
        {
            startDragX = startDragY = -1;
            pDraggingPoint = std::weak_ptr<DragPoint>(); // pDraggingPointの観測先を空へとリセットする
            isDraggingPoints = false;
        }
        else if (pDraggingPos)
        {
            VECTOR nowDrag3D = DxLib::ConvScreenPosToWorldPos(startDrag2D + VGet(Input::MouseX - startDragX, Input::MouseY - startDragY, 0));

            if (startDragX != -1 && startDragY != -1)
            {   // ドラッグ開始位置startDragと現在の位置に移動があれば、その差ぶん移動した位置の3D位置を設定してドラッグ移動を実現
                if (Input::Click[Mouse::DragL]) // マウス左クリック
                {
                    //DxLib::DrawSphere3D(nowDrag3D, 0.05, 8, GetColor(255, 0, 255), GetColor(255, 255, 255), FALSE);
                    *pDraggingPos = nowDrag3D;
                    pDraggingPos->UpdateOwner(); // オーナーの半径などを連動して更新
                    pDraggingPos->Update();
                    isDraggingPoints = true;
                }
            }
        }
    }


    void UpdateMouseInput()
    {
        Init();
        if (!SubScreen::isMouseOvered) // マウスオーバーしているSubscreenがないとき
        {
            if (Input::MouseWheel != 0.0f) // マウスのホイールをグリグリしたとき
            {
                // スクリーンの位置からカメラ画面の視界の奥への方向の単位ベクトル(ノルム)を求める
                VECTOR camNorm = camera.GetCameraWorldDirByScreenXY((float)Input::MouseX, (float)Input::MouseY);
                float camSpeed = camera.wheelSpeed;
                camera.wheelSpeed *= (camera.wheelSpeed < 100.0f) ? 1.5f : 1.0f; // カメラのズーム速度を加速する
                // 求めた単位ベクトル×カメラのスピードぶんカメラの位置を移動
                camera.x += camNorm.x * Input::MouseWheel * camSpeed;
                camera.y += camNorm.y * Input::MouseWheel * camSpeed;
                camera.z += camNorm.z * Input::MouseWheel * camSpeed;
            }
            else if (camera.wheelSpeed > 0.1f) camera.wheelSpeed *= 0.9f; // カメラのズーム速度を減衰する
            else camera.wheelSpeed = 0.1f; // カメラのズーム速度を減衰して最小0.1にすることで繊細なズーム調整もできるようにする


            if ((Input::Click[Mouse::DragL] || Input::Click[Mouse::DragR]) && Screen::IsInsideWindow())
            {
                if (prevDragX != -1 && prevDragY != -1)
                {   // 前の位置prevと現在の位置に移動があれば、その差ぶんだけ+=することでドラッグ移動を実現
                    if (Input::Click[Mouse::DragL]) // マウス左クリック
                    {
                        if (prevDragX != Input::Click[Mouse::DragLX] || prevDragY != Input::Click[Mouse::DragLY])
                        {    // マウスポインタがある画面上の座標に該当する3D空間上の座標を取得 https://dxlib.xsrv.jp/function/dxfunc_3d_camera.html#R12N11
                            float positionRatio = 0.9f;// 奥行の位置 Near 面 0.0f ~ 1.0f Far 面
                            VECTOR start = DxLib::ConvScreenPosToWorldPos(VGet((float)prevDragX, (float)prevDragY, positionRatio));
                            VECTOR end = DxLib::ConvScreenPosToWorldPos(VGet((float)Input::Click[Mouse::DragLX], (float)Input::Click[Mouse::DragLY], positionRatio));
                            float camSpeed = 400; // ドラッグに対するカメラの移動速度
                            camera.x -= (end.x - start.x) * camSpeed;
                            camera.y -= (end.y - start.y) * camSpeed;
                            camera.z -= (end.z - start.z) * camSpeed;
                        }
                    }
                    if (Input::Click[Mouse::DragR]) // マウス右クリック
                    {    // カメラの角度を変更
                        if (prevDragX != Input::Click[Mouse::DragRX])
                            camera.HRotate += (float)(Input::Click[Mouse::DragRX] - prevDragX);

                        if (prevDragY != Input::Click[Mouse::DragRY])
                            camera.VRotate += (float)(Input::Click[Mouse::DragRY] - prevDragY);

                        // ±360度を超えたら割り算の余りを設定して 例.365度 → 5度に戻してやる
                        if (camera.HRotate > 360.0f || camera.HRotate < -360.0f) camera.HRotate = std::fmodf(camera.HRotate, 360.0f);
                        if (camera.VRotate > 360.0f || camera.VRotate < -360.0f) camera.VRotate = std::fmodf(camera.VRotate, 360.0f);
                    }
                }

                prevDragX = (Input::Click[Mouse::DragL]) ? Input::Click[Mouse::DragLX] : Input::Click[Mouse::DragRX];
                prevDragY = (Input::Click[Mouse::DragL]) ? Input::Click[Mouse::DragLY] : Input::Click[Mouse::DragRY];
                return;
            }
            else
            {
                prevDragX = -1; prevDragY = -1; // マウスのドラッグがないときはリセット
            }
        }
    }

    void UpdateCamera()
    {
        Init();

        // カメラの位置と回転値をセット、カメラの位置は原点
        DxLib::SetCameraPositionAndAngle(
            DxLib::VGet(camera.x, camera.y, camera.z),
            camera.VRotate * DX_PI_F / 180.0f, camera.HRotate * DX_PI_F / 180.0f, camera.TRotate * DX_PI_F / 180.0f);
    }

    // まとめてUpdateしたいときはUpdateを使い、個別にUIやKeyやMouseを別々にタイミングを取りながらUpdateしたいときは Updateは呼ばない
    void Update()
    {
        Init();
        UpdateKeyInput();
        UpdateMouseInput();
        UpdateCamera();
    }

    void Draw()
    {
        // 原点からx軸(R:赤) y軸(G:緑) z軸(B:青)をライン表示する
        DxLib::DrawLine3D(VGet(0, 0, 0), VGet(1.0f * 5.0f, 0.0f * 5.0f, 0.0f * 5.0f), GetColor(255, 0, 0));
        DxLib::DrawLine3D(VGet(0, 0, 0), VGet(0.0f * 5.0f, 1.0f * 5.0f, 0.0f * 5.0f), GetColor(0, 255, 0));
        DxLib::DrawLine3D(VGet(0, 0, 0), VGet(0.0f * 5.0f, 0.0f * 5.0f, 1.0f * 5.0f), GetColor(0, 0, 255));
        // 画面に回転量を描画
        DxLib::DrawFormatString(0, 0, DxLib::GetColor(255, 255, 255), "Cam(x:%f y:%f z:%f)", camera.x, camera.y, camera.z);
        DxLib::DrawFormatString(0, 20, DxLib::GetColor(255, 255, 255), "Rot(V:%f H:%f T:%f)", camera.VRotate, camera.HRotate, camera.TRotate);
    }

};

#endif


MyDraw.hを新規作成して、平面を描く機能を準備します。

#ifndef MYDRAW_H_
#define MYDRAW_H_

#include "DxLib.h"
#include "Vector3.h"
#include "Plane4.h"

class MyDraw
{
public:
    // 3Dの矢印の先っぽのタイプ None:無し Cone:三角錐 Cube:立方体 Sphere:球体
    enum class ArrowType { None, Cone, Cube, Sphere };

    // 3Dの矢印(位置:start→end)を描く arrowWidth:矢印の先端の円錐などの幅(球の半径) arrowHeight:矢印の先端の円錐などの高さ(球の時は使わない)
    static int DrawArrow3D(const Vector3& start, const Vector3& end, ArrowType startType = ArrowType::None, ArrowType endType = ArrowType::Cone, float arrowWidth = 0.1f, float arrowHeight = 0.2f, unsigned int color = GetColor(255, 255, 255))
    {
        unsigned int difColor = color; // 拡散反射の色
        unsigned int spcColor = GetColor(0, 0, 0); // 鏡面反射の色(0,0,0)で鏡面反射は黒(反射無し)
        int DivNum = 8; // ポリゴン分割数(三角錐や球体などを例.8分割したポリゴンで表示する)

        if (startType == ArrowType::Cone) // start側の矢印のタイプが三角錐のとき
            DxLib::DrawCone3D(start, start + (end - start).normalized() * arrowHeight, arrowWidth / 2.0f, DivNum, difColor, spcColor, TRUE);
        if (endType == ArrowType::Cone) // end側の矢印のタイプが三角錐のとき
            DxLib::DrawCone3D(end, end + (start - end).normalized() * arrowHeight, arrowWidth / 2.0f, DivNum, difColor, spcColor, TRUE);
        if (startType == ArrowType::Cube) // start側の矢印のタイプがキューブ(立方体)のとき
            DxLib::DrawCube3D(start, start + (end - start).normalized() * arrowHeight, difColor, spcColor, TRUE);
        if (endType == ArrowType::Cube) // end側の矢印のタイプがキューブ(立方体)のとき
            DxLib::DrawCube3D(end, end + (start - end).normalized() * arrowHeight, difColor, spcColor, TRUE);
        if (startType == ArrowType::Sphere) // start側の矢印のタイプが球体のとき
            DxLib::DrawSphere3D(start, arrowWidth / 2.0f, DivNum, difColor, spcColor, TRUE);
        if (endType == ArrowType::Sphere) // end側の矢印のタイプが球体のとき
            DxLib::DrawSphere3D(end, arrowWidth / 2.0f, DivNum, difColor, spcColor, TRUE);

        return DxLib::DrawLine3D(start, end, color);
    }

    // 平面のグリッドを描く(平面;plane を end の位置まで Divnum分割して描く)
    static int DrawPlane(const Plane4& plane, const Vector3& end, unsigned int DivNum, unsigned int color = GetColor(255, 255, 255))
    {
        if (DivNum == 0) return -1;
        Vector3 planePos = -plane.dist * plane.n;
        Vector3 vPlaneEnd = end - planePos;
        Vector3 vPlaneEndNorm = vPlaneEnd.normalized();
        float mag = vPlaneEnd.magnitude();
        float divMag = mag / DivNum;
        Vector3 crossPlaneEndNorm = cross(vPlaneEnd, plane.n).normalized();
        for (unsigned int i = 1; i <= DivNum; ++i)
            for(unsigned int j = 1; j <= DivNum; ++j)
            {
                DxLib::DrawLine3D(planePos - divMag * i * vPlaneEndNorm - divMag * i * crossPlaneEndNorm,
                                  planePos + divMag * i * vPlaneEndNorm - divMag * i * crossPlaneEndNorm, color);
                DxLib::DrawLine3D(planePos - divMag * i * vPlaneEndNorm + divMag * i * crossPlaneEndNorm,
                                  planePos + divMag * i * vPlaneEndNorm + divMag * i * crossPlaneEndNorm, color);
                DxLib::DrawLine3D(planePos - divMag * i * crossPlaneEndNorm - divMag * i * vPlaneEndNorm,
                                  planePos + divMag * i * crossPlaneEndNorm - divMag * i * vPlaneEndNorm, color);
                DxLib::DrawLine3D(planePos - divMag * i * crossPlaneEndNorm + divMag * i * vPlaneEndNorm,
                                  planePos + divMag * i * crossPlaneEndNorm + divMag * i * vPlaneEndNorm, color);
            }
    }
};

#endif


main.cppのコードを一旦、ちらかった部分を削除して整理します。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include "Input.h"
#include "Screen.h"
#include "Ply.h"
#include "DataMV1.h"
#include "DataPly.h"
#include "Collision3D.h"

#include "Editor.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズWidth×Heightのカラービット数32ビットで起動
    SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    SetDrawScreen(DX_SCREEN_BACK);
  
    // ゲームのwhileループを開始する前の初期化処理
    float animTime = 0.0f; // アニメの現在時刻
    float animSpeed = 2.0f; // MV1のアニメの再生速度設定 2.0fなら2倍速
    float animStepTime = 1.0f; // MV1のアニメの毎時間の進む時間の設定
    std::shared_ptr<DataMV1> pDevilData = Resource::MakeShared<DataMV1>("Image/devil@kick.mv1");
    pDevilData->Load(); // 3Dモデルをmv1形式で読込み
    std::shared_ptr<DataMV1> pDevilAttackAnimData = Resource::MakeShared<DataMV1>("Image/devil@jump.mv1");
    pDevilAttackAnimData->Load(); // 3Dの追加アニメデータをmv1形式で読込み
    pDevilData->AttachAnim(*pDevilAttackAnimData); // アニメを追加でflyのほうをベースとしたモデルにアタッチ
    int animeButton = 0; // ボタンでアニメを切り替えられるようにする

    std::shared_ptr<Texture> pTexImage = Resource::MakeShared<Texture>("Image/dice.png");
    pTexImage->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
    assert((int)*pTexImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる

    std::shared_ptr<DataPly> pPlyData = Resource::MakeShared<DataPly>("Image/dice.ply");
    pPlyData->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
   


    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 0.0f; keyControlXYZ.y = 0.0f; keyControlXYZ.z = 0;
    VECTOR keyControlAngle = VGet(0.0f,0.0f,0.0f); // 回転

        // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen1 = SubScreen::Create()->Set(600, 450, 600, 100, 255, 0, "サブスクリーン1")->SetIsBarFrameShow(true,true);
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen2 = SubScreen::Create()->Set(200, 100, 150, 100, 255, 1, "サブスクリーン2\nバーを30で太めに")->SetFrame(1,GetColor(255,255,255),true,30);
    // サブのスクリーンを生成する 透明度を128にして半透明にしてみる
    std::shared_ptr<SubScreen> subscreen3 = SubScreen::Create()->Set(150, 450, 300, 100, 128, 1, "サブスクリーン3")->SetIsBarFrameShow(true)->SetBarColor(GetColor(0, 0, 255));
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen4 = SubScreen::Create()->Set(300, 100, 600, 100, 255, 1, "サブスクリーン4");

    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    Editor& editor = Editor::GetInstance(); // エディタのシングルトン参照を得る

    //奥行0.1~5000までをカメラの描画範囲とする
    DxLib::SetCameraNearFar(0.1f, 5000.0f);

    ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Input::Update(); // マウスのドラッグなどを更新
        SubScreen::UpdateScreens(); // スクリーンの位置やドラッグによる移動を更新する

        editor.UpdateKeyInput(); // エディタのキーのインプットを更新

        editor.UpdateMouseInput(); // エディタのマウスのインプットを更新
        editor.UpdateCamera(); // エディタのカメラを更新
       
        // キーボードの 0 1 キーで3Dアニメを切り替えられるようにする
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_0))
            animeButton = 0;
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_1))
            animeButton = 1;

        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        VECTOR position = VGet(keyControlXYZ.x, keyControlXYZ.y, keyControlXYZ.z); // 3Dモデルの位置

        float width =  pTexImage->m_XSize; float height =  pTexImage->m_YSize; // テクスチャ画像のサイズ
        VECTOR scale = VGet(1.0f, 1.0f, 1.0f); // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)とcキー(C)でx,y,z軸まわりに画像を回転
        if (PadInput & PAD_INPUT_A) keyControlAngle.x += speed;
        if (PadInput & PAD_INPUT_B) keyControlAngle.y += speed;
        if (PadInput & PAD_INPUT_C) keyControlAngle.z += speed;

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数
        const float xRotate = keyControlAngle.x * Deg2Rad;  const float yRotate = keyControlAngle.y * Deg2Rad; const float zRotate = keyControlAngle.z * Deg2Rad;


        MATRIX  matWorldDefault; // デフォルトのワールド行列
        DxLib::GetTransformToWorldMatrix(&matWorldDefault); // デフォルトのワールド行列を記憶しておく
        {
            //[ワールド行列] https://ny-program.hatenablog.com/entry/2019/09/15/114245
            MATRIX mat; // モデルのスケール×回転×移動を行列計算で設定する

            //[スケール]拡大縮小のスケール行列を得る
            mat = MGetScale(scale); // まずは拡大縮小のスケール行列からはじめる
            // [勉強]↓スケール行列は 斜めに x , y , zのスケールの倍率がならんでいる行列
            //mat.m[0][0] = scale.x;  mat.m[0][1] = 0.0f;    mat.m[0][2] = 0.0f;     mat.m[0][3] = 0.0f;
            //mat.m[1][0] = 0.0f;     mat.m[1][1] = scale.y; mat.m[1][2] = 0.0f;     mat.m[1][3] = 0.0f;
            //mat.m[2][0] = 0.0f;     mat.m[2][1] = 0.0f;    mat.m[2][2] = scale.z;  mat.m[2][3] = 0.0f;
            //mat.m[3][0] = 0.0f;     mat.m[3][1] = 0.0f;    mat.m[3][2] = 0.0f;     mat.m[3][3] = 1.0f;

            //[回転]
            MATRIX matRot;
            DxLib::CreateIdentityMatrix(&matRot);
            // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
            matRot = MMult(matRot, MGetRotZ(zRotate));
            matRot = MMult(matRot, MGetRotX(xRotate));
            matRot = MMult(matRot, MGetRotY(yRotate));
            mat = MMult(mat, matRot); //スケール × 回転

            //[移動]
            MATRIX matTrans = MGetTranslate(VGet(position.x, position.y, position.z)); // 位置positionをここで設定
            mat = MMult(mat, matTrans); //スケール×回転×移動

            DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4341

            DxLib::DrawPolygon32bitIndexed3D(pPlyData->Vertex.data(), pPlyData->Vertex.size(), pPlyData->Index.data(), pPlyData->numIndex / 3, (int)*pTexImage, FALSE); // 三角形ポリゴンをたくさん描画
        }
        DxLib::SetTransformToWorld(&matWorldDefault); // デフォルトのワールド行列に戻しておく

        DxLib::MV1DrawModel(devil3DModel); // 3Dモデル(.mv1形式)を描く

        DxLib::DrawFormatString(0, 100, GetColor(255, 255, 0), "x:%f y:%f Angle:%f %f %f", position.x, position.y, keyControlAngle.x, keyControlAngle.y, keyControlAngle.z);

        // ファイルのパス + ":" + アニメ名でアクセス
        std::string animPathName = (animeButton == 0) ? "Image/devil@kick.mv1:mixamo.com" : "Image/devil@jump.mv1:mixamo.com";
        assert(pDevilData->count(animPathName) > 0 && "指定されたアニメ名のアニメはMV1に付属していませんでした");
        animTime += animStepTime * animSpeed; // 1.0×2倍 なら2倍速でアニメ時刻が進む
        if (animTime > (*pDevilData)[animPathName].endTime)
            animTime = 0.0f; // 0秒目へとアニメをループさせる

        // 画面奥へ向かう光線レイを求める
        Vector3 rayStart = DxLib::ConvScreenPosToWorldPos(VGet(Input::MouseX, Input::MouseY, 0.0f));
        Vector3 rayEnd = DxLib::ConvScreenPosToWorldPos(VGet(Input::MouseX, Input::MouseY, 1.0f));
        DxLib::DrawLine3D(rayStart, rayEnd, GetColor(255, 255, 0));
       
        // 球0
        Vector3 sphere0Center{ 10,10,10 };
        float sphere0Radius = 10;
        Vector3 collision0;
        bool isCollision0 = isCollisionSphere_Line(sphere0Center, sphere0Radius, rayStart, rayEnd, &collision0); // 球と光線(レイ)の当たり判定
        DxLib::DrawSphere3D(sphere0Center, sphere0Radius, 16, (isCollision0) ? GetColor(255, 0, 0) : GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);
        if (isCollision0) DxLib::DrawSphere3D(collision0, 0.5, 16, GetColor(255, 0, 0), GetColor(255, 255, 255), TRUE); // レイと当たった部分に赤い球を表示

        // 球1
        Vector3 sphere1Center{ 10,-10,10 };
        float sphere1Radius = 10;
        Vector3 collision1;
        bool isCollision1 = isCollisionSphere_Ray(sphere1Center, sphere1Radius, rayStart, rayEnd - rayStart, &collision1); // 球と光線(レイ)の当たり判定
        DxLib::DrawSphere3D(sphere1Center, sphere1Radius, 16, (isCollision1) ? GetColor(0, 255, 0) : GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);
        if (isCollision1) DxLib::DrawSphere3D(collision1, 0.5, 16, GetColor(0, 255, 0), GetColor(255, 255, 255), TRUE); // レイと当たった部分に緑の球を表示


       
        // 再生時間をセットする
        DxLib::MV1SetAttachAnimTime((int)*pDevilData, (*pDevilData)[animPathName].index, animTime);
      
        subscreen1->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen1->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // サブの別のスクリーンの(0,0)位置を起点に文字列を描く
            DxLib::DrawString(0, 0, "サブのスクリーンを使ってWindowsみたいな自作ウインドウを描画してみる", GetColor(255, 255, 255));
        }
        subscreen1->DrawEnd();

        subscreen3->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen3->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // 半透明のスクリーンも描ける
            DxLib::DrawBox(0, 0, subscreen3->Width, subscreen3->Height, GetColor(0, 255, 0), TRUE);
            DxLib::DrawString(0, 0, "アルファを設定すれば\n半透明スクリーンも可能", GetColor(255, 255, 255));
        }
        subscreen3->DrawEnd();

        subscreen4->ClearDrawScreen(); // 描く前に一旦キャンバスをきれいにまっさらに
        subscreen4->DrawStart(); // このDrawStartとDrawEndのあいだにDraw~されたらサブのスクリーンに描かれるようになる
        {   // サブの別のスクリーンの(0,0)位置を起点に文字列を描く
            DxLib::DrawString(0, 0, "枠の無いスクリーンを使えば2Pの分割画面などにも応用できる", GetColor(255, 255, 255));
        }
        subscreen4->DrawEnd();


        SubScreen::DrawScreens(); // サブのスクリーンを全部描く


        editor.Draw(); // エディタのDraw処理

        ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    WaitKey();
    // DXライブラリの後始末
    DxLib_End();
    // ソフトの終了
    return 0;
}



main.cppのコードにドラッグできるコントロール点をもつカプセルや球体やボックスやポリゴン平面を追加します。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include "Input.h"
#include "Screen.h"
#include "Ply.h"
#include "DataMV1.h"
#include "DataPly.h"
#include "Collision3D.h"
#include "MyDraw.h" // 自作の描画処理

#include "Editor.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズWidth×Heightのカラービット数32ビットで起動
    SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    SetDrawScreen(DX_SCREEN_BACK);
  
    // ゲームのwhileループを開始する前の初期化処理
    float animTime = 0.0f; // アニメの現在時刻
    float animSpeed = 2.0f; // MV1のアニメの再生速度設定 2.0fなら2倍速
    float animStepTime = 1.0f; // MV1のアニメの毎時間の進む時間の設定
    std::shared_ptr<DataMV1> pDevilData = Resource::MakeShared<DataMV1>("Image/devil@kick.mv1");
    pDevilData->Load(); // 3Dモデルをmv1形式で読込み
    std::shared_ptr<DataMV1> pDevilAttackAnimData = Resource::MakeShared<DataMV1>("Image/devil@jump.mv1");
    pDevilAttackAnimData->Load(); // 3Dの追加アニメデータをmv1形式で読込み
    pDevilData->AttachAnim(*pDevilAttackAnimData); // アニメを追加でflyのほうをベースとしたモデルにアタッチ
    int animeButton = 0; // ボタンでアニメを切り替えられるようにする

    std::shared_ptr<Texture> pTexImage = Resource::MakeShared<Texture>("Image/dice.png");
    pTexImage->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
    assert((int)*pTexImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる

    std::shared_ptr<DataPly> pPlyData = Resource::MakeShared<DataPly>("Image/dice.ply");
    pPlyData->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
   


    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 0.0f; keyControlXYZ.y = 0.0f; keyControlXYZ.z = 0;
    VECTOR keyControlAngle = VGet(0.0f,0.0f,0.0f); // 回転

        // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen1 = SubScreen::Create()->Set(600, 450, 600, 100, 255, 0, "サブスクリーン1")->SetIsBarFrameShow(true,true);
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen2 = SubScreen::Create()->Set(200, 100, 150, 100, 255, 1, "サブスクリーン2\nバーを30で太めに")->SetFrame(1,GetColor(255,255,255),true,30);
    // サブのスクリーンを生成する 透明度を128にして半透明にしてみる
    std::shared_ptr<SubScreen> subscreen3 = SubScreen::Create()->Set(150, 450, 300, 100, 128, 1, "サブスクリーン3")->SetIsBarFrameShow(true)->SetBarColor(GetColor(0, 0, 255));
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen4 = SubScreen::Create()->Set(300, 100, 600, 100, 255, 1, "サブスクリーン4");

    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    Editor& editor = Editor::GetInstance(); // エディタのシングルトン参照を得る

    float radius_lineStart0 = 0.2f, radius_lineEnd1 = 0.2f; // カプセルと球体の半径の初期設定
    // カプセル点(0)
    std::shared_ptr<DragCapsule> lineStart0 = std::make_shared<DragCapsule>(-3, 0, -4, radius_lineStart0, 1.0f, Axis3D::Y, GetColor(255, 165, 165));
    // 球体の点(1)
    std::shared_ptr<DragSphere> lineEnd1 = std::make_shared<DragSphere>(-2, -3, -3.5f, radius_lineEnd1 * 2, GetColor(255, 165, 0));

    // 三角ポリゴン(2)
    std::vector<std::shared_ptr<DragPoint>> poly2_Pos = { std::make_shared<DragPoint>(-3.0f,-2.0f,-4.0f),
                                                                         std::make_shared<DragPoint>(-2.0f,-2.0f,-3.0f),
                                                                         std::make_shared<DragPoint>(-2.0f,-2.0f,-4.0f) }; // ポリゴンの3点

    // 四角ボックスの点(4)
    Vector3 boxSize{ 1.0f,2.0f,3.0f };
    std::shared_ptr<DragBox> boxPoint4 = std::make_shared<DragBox>(7, -3, 4, boxSize, GetColor(255, 255, 0));

    std::list<std::weak_ptr<DragPoint>> dragPoints; // ドラッグ対象の点の共有ポインタのリスト

    dragPoints.emplace_back(lineStart0); // リストにカプセル点0を追加
    dragPoints.emplace_back(lineEnd1); // リストに球体の点1を追加
    dragPoints.emplace_back(boxPoint4); // リストに四角ボックスの点4を追加

    dragPoints.emplace_back(poly2_Pos[0]); // リストにポリゴン2の3点を追加
    dragPoints.emplace_back(poly2_Pos[1]);
    dragPoints.emplace_back(poly2_Pos[2]);


    //奥行0.1~5000までをカメラの描画範囲とする
    DxLib::SetCameraNearFar(0.1f, 5000.0f);

    ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Input::Update(); // マウスのドラッグなどを更新

        editor.UpdateKeyInput(); // エディタのキーのインプットを更新

        editor.UpdateDragPoints(&dragPoints); // ドラッグ点のリストを更新

        if (!editor.isDraggingPoints) editor.UpdateMouseInput(); // エディタのマウスのインプットを更新
        editor.UpdateCamera(); // エディタのカメラを更新
       
        // キーボードの 0 1 キーで3Dアニメを切り替えられるようにする
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_0))
            animeButton = 0;
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_1))
            animeButton = 1;

        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        VECTOR position = VGet(keyControlXYZ.x, keyControlXYZ.y, keyControlXYZ.z); // 3Dモデルの位置

        float width =  pTexImage->m_XSize; float height =  pTexImage->m_YSize; // テクスチャ画像のサイズ
        VECTOR scale = VGet(1.0f, 1.0f, 1.0f); // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)とcキー(C)でx,y,z軸まわりに画像を回転
        if (PadInput & PAD_INPUT_A) keyControlAngle.x += speed;
        if (PadInput & PAD_INPUT_B) keyControlAngle.y += speed;
        if (PadInput & PAD_INPUT_C) keyControlAngle.z += speed;

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数
        const float xRotate = keyControlAngle.x * Deg2Rad;  const float yRotate = keyControlAngle.y * Deg2Rad; const float zRotate = keyControlAngle.z * Deg2Rad;


        MATRIX  matWorldDefault; // デフォルトのワールド行列
        DxLib::GetTransformToWorldMatrix(&matWorldDefault); // デフォルトのワールド行列を記憶しておく
        {
            //[ワールド行列] https://ny-program.hatenablog.com/entry/2019/09/15/114245
            MATRIX mat; // モデルのスケール×回転×移動を行列計算で設定する

            //[スケール]拡大縮小のスケール行列を得る
            mat = MGetScale(scale); // まずは拡大縮小のスケール行列からはじめる
            // [勉強]↓スケール行列は 斜めに x , y , zのスケールの倍率がならんでいる行列
            //mat.m[0][0] = scale.x;  mat.m[0][1] = 0.0f;    mat.m[0][2] = 0.0f;     mat.m[0][3] = 0.0f;
            //mat.m[1][0] = 0.0f;     mat.m[1][1] = scale.y; mat.m[1][2] = 0.0f;     mat.m[1][3] = 0.0f;
            //mat.m[2][0] = 0.0f;     mat.m[2][1] = 0.0f;    mat.m[2][2] = scale.z;  mat.m[2][3] = 0.0f;
            //mat.m[3][0] = 0.0f;     mat.m[3][1] = 0.0f;    mat.m[3][2] = 0.0f;     mat.m[3][3] = 1.0f;

            //[回転]
            MATRIX matRot;
            DxLib::CreateIdentityMatrix(&matRot);
            // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
            matRot = MMult(matRot, MGetRotZ(zRotate));
            matRot = MMult(matRot, MGetRotX(xRotate));
            matRot = MMult(matRot, MGetRotY(yRotate));
            mat = MMult(mat, matRot); //スケール × 回転

            //[移動]
            MATRIX matTrans = MGetTranslate(VGet(position.x, position.y, position.z)); // 位置positionをここで設定
            mat = MMult(mat, matTrans); //スケール×回転×移動

            DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4341

            DxLib::DrawPolygon32bitIndexed3D(pPlyData->Vertex.data(), pPlyData->Vertex.size(), pPlyData->Index.data(), pPlyData->numIndex / 3, (int)*pTexImage, FALSE); // 三角形ポリゴンをたくさん描画
        }
        DxLib::SetTransformToWorld(&matWorldDefault); // デフォルトのワールド行列に戻しておく

        DxLib::MV1DrawModel(devil3DModel); // 3Dモデル(.mv1形式)を描く

        DxLib::DrawFormatString(0, 100, GetColor(255, 255, 0), "x:%f y:%f Angle:%f %f %f", position.x, position.y, keyControlAngle.x, keyControlAngle.y, keyControlAngle.z);

        // ファイルのパス + ":" + アニメ名でアクセス
        std::string animPathName = (animeButton == 0) ? "Image/devil@kick.mv1:mixamo.com" : "Image/devil@jump.mv1:mixamo.com";
        assert(pDevilData->count(animPathName) > 0 && "指定されたアニメ名のアニメはMV1に付属していませんでした");
        animTime += animStepTime * animSpeed; // 1.0×2倍 なら2倍速でアニメ時刻が進む
        if (animTime > (*pDevilData)[animPathName].endTime)
            animTime = 0.0f; // 0秒目へとアニメをループさせる
       
        // 再生時間をセットする
        DxLib::MV1SetAttachAnimTime((int)*pDevilData, (*pDevilData)[animPathName].index, animTime);

        // 三角ポリゴン(2)を含む平面
        Plane4 plane_poly2{ *poly2_Pos[0],*poly2_Pos[1],*poly2_Pos[2] }; // 3点を含む平面を求める

        Vector3 collision2; // ポリゴン2と線(lineStart0→lineEnd1)の交点
        Vector3 polygon[3] = { *poly2_Pos[0],*poly2_Pos[1],*poly2_Pos[2] };
        // 三角ポリゴン(2) と 線(lineStart0→lineEnd1) の当たり判定 をして 交点collision2 も求める
        bool isCollision2 = isCollisionPoly_Line(polygon, 3, plane_poly2, *lineStart0, *lineEnd1, &collision2);

        // 三角ポリゴン(2)を描く
        DxLib::DrawTriangle3D(*poly2_Pos[0], *poly2_Pos[1], *poly2_Pos[2], GetColor(255, 255, 255), FALSE);
        // lineStart0から平面の中心へ向かう線 を描く
        DxLib::DrawLine3D(*lineStart0, -plane_poly2.n * plane_poly2.dist, GetColor(255, 165, 165));
        // 平面の中心 から 交点collision2 へ向かう(水色) を描く
        DxLib::DrawLine3D(-plane_poly2.n * plane_poly2.dist, collision2, GetColor(0, 255, 255));
        // 平面の中心 に水色の球を描く
        DxLib::DrawSphere3D(-plane_poly2.n * plane_poly2.dist, 0.05, 16, GetColor(0, 255, 255), GetColor(255, 255, 255), TRUE);
        // 平面の中心 から 原点 に向かう線(水色)を描く
        DxLib::DrawLine3D(-plane_poly2.n * plane_poly2.dist, Vector3{0,0,0}, GetColor(0, 255, 255));

        // ポリゴンを横切る線lineStart0→lineEnd1を結ぶ線(オレンジ)を描く
        DxLib::DrawLine3D(*lineStart0, *lineEnd1, GetColor(255, 165, 0));
 
        Vector3 collisionRay2; // 三角ポリゴン(2)とマウスカーソルの光線(rayPos)の交点
        bool isCollisionRay2 = isCollisionPoly_Line(polygon, 3, plane_poly2, rayStart, rayEnd - rayStart, &collisionRay2);
        // カメラの始点から飛ばしたレイと三角ポリゴン(2)の交点に目印の球を描画
        if (isCollisionRay2) DxLib::DrawSphere3D(collisionRay2, 0.01, 16, GetColor(255, 165, 0), GetColor(255, 255, 255), FALSE);

        // 三角ポリゴン(2)の存在する平面を描く
        MyDraw::DrawPlane(plane_poly2, *poly2_Pos[0], 8,GetColor(0,255,255));
        // 三角ポリゴン(2)の存在する平面の単位法線方向の矢印(緑色)を描く
        MyDraw::DrawArrow3D(-plane_poly2.n * plane_poly2.dist, -plane_poly2.n * plane_poly2.dist + plane_poly2.n,
                                     MyDraw::ArrowType::None, MyDraw::ArrowType::Cone, 0.1f, 0.2f, GetColor(0, 255, 0));

        // 平面と軸並行ボックス(4)AABBの当たり判定とその押し戻しゴーストを描く
        Vector3 boxMin, boxMax, boxCenter;
        boxPoint4->GetParams(boxCenter, boxMin, boxMax);
        float distAABB; // 押し戻し距離(当たってないときは平面までの距離)
        bool isCollisionPlaneAABB = isCollisionPlane_AABB(plane_poly2, boxMin, boxMax, &distAABB);
        if (isCollisionPlaneAABB) // 平面とボックスが衝突したら↓法線×押し戻し距離 ぶんずらしてゴーストを描く
            DxLib::DrawCube3D(boxMin + plane_poly2.n * distAABB,
                              boxMax + plane_poly2.n * distAABB, GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);
     
        // 平面とカプセル(0)の当たり判定とその押し戻しゴーストを描く
        Vector3 centerCapsule, startCapsule, endCapsule;
        float radiusCapsule;
        lineStart0->GetParams(centerCapsule, startCapsule, endCapsule, radiusCapsule); // 線の始点lineStart0からカプセル(0)のパラメータを得る
        float distCapsule; // 平面からカプセルをどれだけ押しもどせば当たらなくなるかの距離
        bool isCollisionPlaneCapsule = isCollisionPlane_Capsule(plane_poly2, startCapsule, endCapsule, radiusCapsule, &distCapsule);
        if (isCollisionPlaneCapsule) // 平面とカプセルが衝突していたら、平面の法線n方向に押し戻したゴーストを描く
            DxLib::DrawCapsule3D(startCapsule + plane_poly2.n * distCapsule,
                endCapsule + plane_poly2.n * distCapsule, radiusCapsule, 8, GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);

        // 平面と球体(1)の当たり判定とその押し戻しゴーストを描く
        float sphereRadius;
        Vector3 sphereCenter;
        lineEnd1->GetParams(sphereCenter, sphereRadius); // 線の終点lineEnd1から球体(1)のパラメータを得る
        float distSphere; // 平面から球をどれだけ押し戻せば当たらなくなるかの距離
        bool isCollisionPlaneSphere = isCollisionPlane_Sphere(plane_poly2, sphereCenter, sphereRadius, &distSphere);
        if (isCollisionPlaneSphere) // 平面と球が衝突していたら、平面の法線n方向に押し戻したゴーストを描く
            DxLib::DrawSphere3D(*lineEnd1 + plane_poly2.n * distSphere, sphereRadius, 8, GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);

        if (isCollision2)
        {   // 三角ポリゴン(2)と線(lineStart0→lineEnd1)の交わった交点に橙の球を表示
            DxLib::DrawSphere3D(collision2, 0.05, 16, GetColor(255, 165, 0), GetColor(255, 255, 255), TRUE);
            // 球の中心とポリゴンの3点を結ぶ線の角度の合計がほぼ360度(2π)になるかがポリゴン内部かの判定条件となる
            DxLib::DrawLine3D(collision2, *poly2_Pos[0], GetColor(255, 165, 165));
            DxLib::DrawLine3D(collision2, *poly2_Pos[1], GetColor(255, 165, 165));
            DxLib::DrawLine3D(collision2, *poly2_Pos[2], GetColor(255, 165, 165));
        }

        // ドラッグ点とその当たり判定のオブジェクト(ボックスや球やカプセルなど)を描く
        for (auto dragPoint : dragPoints)
            dragPoint.lock()->Draw(); //ドラッグ点とそのまわりに広がる当たり判定範囲のカプセルやボックスなどをまとめて描く


        editor.Draw(); // エディタのDraw処理

        ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    WaitKey();
    // DXライブラリの後始末
    DxLib_End();
    // ソフトの終了
    return 0;
}

いかがでしょう?四角い点をドラッグすることで当たり判定の中心位置をコントロールすることができるようになったでしょうか?
ポリゴンの3点を移動させると、球やカプセルの押し戻し位置のゴーストも変わることも試して、平面との当たり判定を確かめてみましょう。


3Dモデルをクオータニオンで安定して回転できるようにする

Quaternion.hを新規作成してジンバルロックの心配のない回転の定義を追加しましょう。

#ifndef QUATERNION_H_
#define QUATERNION_H_

#include "DxLib.h"
#include <cmath>
#include <vector>
#include "Vector3.h"

// クオータニオン http://noa1105.seesaa.net/article/239449116.html
struct Quaternion
{
    double x, y, z, w;
    VECTOR eular(); // オイラー角を得る

    inline Quaternion(double x = 0.0, double y = 0.0, double z = 0.0, double w = 1.0) : x{ x }, y{ y }, z{ z }, w{ w } {}

    ~Quaternion() {}

    //[コンストラクタ] 回転クォータニオン Axis軸(x軸,y軸,z軸)周りにradianの角度回転したクオータニオンを生成
    inline Quaternion(double radian, const Vector3& axis) : w{ 1.0 }, x{ 0.0 }, y{ 0.0 }, z{ 0.0 }
    {
        setRotate(radian, axis.x, axis.y, axis.z);
    }

    // axis軸周り に 回転角度radian 回転したクオータニオンを設定する
    inline Quaternion& setRotate(double radian, double axisX, double axisY, double axisZ)
    {
        double norm, cos_rad2, sin_rad2;
        norm = axisX * axisX + axisY * axisY + axisZ * axisZ;
        if (norm <= 0.0f) return *this; // normの2乗 が 0 なら そのままreturn

        norm = 1.0 / std::sqrt(norm);
        axisX *= norm; axisY *= norm; axisZ *= norm;

        cos_rad2 = std::cos(radian / 2.0);
        sin_rad2 = std::sin(radian / 2.0);

        w = cos_rad2;
        x = sin_rad2 * axisX;
        y = sin_rad2 * axisY;
        z = sin_rad2 * axisZ;

        return *this;
    }

    // axis軸周り に 回転角度radian 回転したクオータニオンを設定する
    inline Quaternion& setRotate(double radian, const Vector3& axis)
    {
        return setRotate(radian, axis.x, axis.y, axis.z);
    }

    // ヨー ピッチ ロールで指定する形でクオータニオンの回転を設定する
    inline Quaternion& setYawPitchRoll(double yaw, double pitch, double roll)
    {
        //[ヨー ピッチ ロールについて] http://fantom1x.blog130.fc2.com/blog-entry-255.html
        double sinYaw   = std::sin(yaw / 2.0),   cosYaw   = std::cos(yaw / 2.0);
        double sinPitch = std::sin(pitch / 2.0), cosPitch = std::cos(pitch / 2.0);
        double sinRoll  = std::sin(roll / 2.0),  cosRoll  = std::cos(roll / 2.0);

        x = sinRoll * cosPitch * cosYaw - cosRoll * sinPitch * sinYaw;
        y = cosRoll * sinPitch * cosYaw + sinRoll * cosPitch * sinYaw;
        z = cosRoll * cosPitch * sinYaw - sinRoll * sinPitch * cosYaw;
        w = cosRoll * cosPitch * cosYaw + sinRoll * sinPitch * sinYaw;
       
        return *this;
    }


    // 単位クオータニオンを返す
    static Quaternion identity()
    {
        return Quaternion{ 0.0, 0.0, 0.0, 1.0 };
    };

    // クオータニオンの大きさ
    inline double magnitude() const
    {
        return std::sqrt(x * x + y * y + z * z + w * w); // √(x*x + y*y + z*z + w*w)
    }

    // クオータニオンの大きさの2乗
    inline double sqrMagnitude() const
    {
        return x * x + y * y + z * z + w * w;
    }

    // 正規化したクオータニオンを返す
    inline Quaternion normalized() const
    {
        double mag = magnitude();
        if (mag < 0.00001) // ほぼ0か?
            return Quaternion{ 0.0, 0.0, 0.0, 0.0 };
        else
            return Quaternion{ x / mag, y / mag, z / mag, w / mag }; // x / |x|, y / |y|, z / |z|, w / |w|
    }

    // 自身(this)を正規化する
    inline Quaternion& normalize()
    {
        *this = this->normalized(); // 自身を正規化したクオータニオンに置き換える
        return *this;
    }


    // 逆クオータニオンを返す(メンバ関数版)
    inline Quaternion inverse() const
    {
        double mag = magnitude();
        if (mag < 0.00001) // ほぼ0か?
            return Quaternion{ 0.0, 0.0, 0.0, 0.0 };
        else
            return Quaternion{ -x / mag, -y / mag, -z / mag, w / mag }; // -x / |x|, -y / |y|, -z / |z|, w / |w|
    }

    // クオータニオンからx,y,z軸Axis軸周りの回転角度 result_radian と 軸axisをreturnする
    inline Vector3 getAxisAngle(double* result_radian = nullptr) const
    {
        double angle = std::acos(w);
        double sin_angle = std::sin(angle);
        if (result_radian != nullptr) *result_radian = angle * 2.0; // result_radianに計算した角度を返す
        return Vector3{ (float)(x / sin_angle), (float)(y / sin_angle), (float)(z / sin_angle) };
    }

    // 回転角度だけを求める
    inline double getAngle() const { return std::acos(w) * 2.0; }

    // ヨーを求める(単位:ラジアン)
    inline double getYaw() const { return std::asin(-2.0 * (x * z - w * y)); }

    // ピッチを求める(単位:ラジアン)
    inline double getPitch() const { return std::atan2(w * w - x * x - y * y + z * z, 2.0 * (y * z + w * x)); }

    // ロールを求める(単位:ラジアン)
    inline double getRoll() const { return std::atan2(w * w + x * x - y * y - z * z, 2.0 * (x * y + w * z)); }

    // 演算子operatorのオーバーロード

    // マイナスをつけたクオータニオン
    inline Quaternion operator -() const
    {
        return Quaternion{ -x, -y, -z, -w };
    }

    // プラスをつけたクオータニオン
    inline Quaternion operator + () const { return *this; }

    // +=加算演算子のオーバーロード
    inline Quaternion operator += (const Quaternion& right)
    {
        x = x + right.x;
        y = y + right.y;
        z = z + right.z;
        w = w + right.w;
        return *this;
    }

    // -=減算演算子のオーバーロード
    inline Quaternion operator -= (const Quaternion& right)
    {
        x = x - right.x;
        y = y - right.y;
        z = z - right.z;
        w = w - right.w;
        return *this;
    }

    // *= クオータニオン同士の掛け算演算子のオーバーロード
    inline Quaternion operator *= (const Quaternion& right)
    {
        Quaternion tmp{x,y,z,w};
        w = tmp.w * right.w - (tmp.x * right.x + tmp.y * right.y + tmp.z * right.z);
        x = tmp.w * right.x + right.w * tmp.x + (tmp.y * right.z - tmp.z * right.y);
        y = tmp.w * right.y + right.w * tmp.y + (tmp.z * right.x - tmp.x * right.z);
        z = tmp.w * right.z + right.w * tmp.z + (tmp.x * right.y - tmp.y * right.x);
        return *this;
    }

    // *= クオータニオンの数値倍の掛け算演算子のオーバーロード
    inline Quaternion operator *= (double right)
    {
        w = w * right;
        x = x * right;
        y = y * right;
        z = z * right;
        return *this;
    }

    // /= クオータニオンを数値で割る演算子のオーバーロード
    inline Quaternion operator /= (double right)
    {
        w = w / right;
        x = x / right;
        y = y / right;
        z = z / right;
        return *this;
    }

    void SetRotation(VECTOR eularAngle); // 0~360度のオイラー角でクオータニオンを再設定する
private:
    VECTOR _eular = VGet(0, 0, 0); // 0~360度のオイラー角
};

// クォータニオンどうしを足し算
inline Quaternion operator + (const Quaternion& left, const Quaternion& right)
{
    Quaternion result;
    result.x = left.x + right.x;
    result.y = left.y + right.y;
    result.z = left.z + right.z;
    result.w = left.w + right.w;
    return result;
}

// クォータニオンどうしで引き算
inline Quaternion operator - (const Quaternion& left, const Quaternion& right)
{
    Quaternion result;
    result.x = left.x - right.x;
    result.y = left.y - right.y;
    result.z = left.z - right.z;
    result.w = left.w - right.w;
    return result;
}

// クォータニオンどうしのかけ算を計算
inline Quaternion operator * (const Quaternion& q1, const Quaternion& q2)
{    // w,x,y,zパラメータの計算
    Quaternion result;
    result.w = q1.w * q2.w - (q1.x * q2.x + q1.y * q2.y + q1.z * q2.z);
    result.x = q1.w * q2.x + q2.w * q1.x + (q1.y * q2.z - q1.z * q2.y);
    result.y = q1.w * q2.y + q2.w * q1.y + (q1.z * q2.x - q1.x * q2.z);
    result.z = q1.w * q2.z + q2.w * q1.z + (q1.x * q2.y - q1.y * q2.x);
    return result;
}

// クォータニオンをスカラー倍する乗算(右側にfloat)
inline Quaternion operator * (const Quaternion& left, float right)
{
    Quaternion result;
    result.x = left.x * right;
    result.y = left.y * right;
    result.z = left.z * right;
    result.w = left.w * right;
    return result;
}

// クォータニオンをスカラー倍する乗算(左側にfloat)
inline Quaternion operator * (double left, const Quaternion& right)
{
    return right * left; // 上の定義(左:クオータニオン)を再利用する
}

// ベクトルにクォータニオンをかけ算して座標変換
inline Vector3 operator * (const Quaternion& left, const Vector3& right)
{
    Vector3 result;
    double _2x = left.x * 2.0, _2y = left.y * 2.0, _2z = left.z * 2.0;
    double x2x = left.x * _2x,  y2y = left.y * _2y,  z2z = left.z * _2z;
    double x2y = left.x * _2y,  x2z = left.x * _2z,  y2z = left.y * _2z;
    double w2x = left.w * _2x,  w2y = left.w * _2y,  w2z = left.w * _2z;

    result.x = (1.0 - (y2y + z2z)) * right.x + (x2y - w2z) * right.y + (x2z + w2y) * right.z;
    result.y = (x2y + w2z) * right.x + (1.0 - (x2x + z2z)) * right.y + (y2z - w2x) * right.z;
    result.z = (x2z - w2y) * right.x + (y2z + w2x) * right.y + (1.0 - (x2x + y2y)) * right.z;

    return result;
}

// 一致演算子のオーバーロード
inline bool operator == (const Quaternion& q1, const Quaternion& q2)
{
    return (q1.x == q2.x && q1.y == q2.y && q1.z == q2.z && q1.w == q2.w);
}

// 不一致演算子のオーバーロード
inline bool operator != (const Quaternion& q1, const Quaternion& q2)
{
    return !(q1 == q2);
}


// クオータニオンの内積
inline double dot(const Quaternion& q1, const Quaternion& q2)
{
    return    q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w;
}

// クオータニオンの線形補間 q1→q2へ(t:補間値パラメータ)
inline Quaternion lerp(const Quaternion& q1, const Quaternion& q2, double t)
{
    Quaternion result;
    result.x = q1.x * (1 - t) + q2.x * t; // t と 1-tの比率でブレンド
    result.y = q1.y * (1 - t) + q2.y * t;
    result.z = q1.z * (1 - t) + q2.z * t;
    result.w = q1.w * (1 - t) + q2.w * t;
    return result;
}

// クオータニオンの球面線形補間 q1→q2(t:補間値パラメータ)
inline Quaternion slerp(const Quaternion& q1, const Quaternion& q2, double t)
{
    Quaternion result;
    Quaternion q2_dash;
    double cos_theta = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w;
    if (cos_theta < 0.0f) // cosθが0以下のときは
    {
        cos_theta = -cos_theta; // cosθに-をつけて+にする
        q2_dash = -q2; // q2も-をつけて+にしておく
    }
    else
        q2_dash = q2; // cosθ >= 0のときはそのままq2を使う

    double _1_t = 1.0 - t;

    if (1.0 - cos_theta > 0.001) // cosθが1.0ではないときは
    {    // 球面線形補間では t と 1-tの代わりに 角度θについてのtと1-t での sinθで補間
        double theta = std::acos(cos_theta);
        _1_t = std::sin(theta * _1_t) / std::sin(theta);
        t = std::sin(theta * t) / std::sin(theta);
    }

    // t と _1_t を使って球面線形補間を計算する
    result.x = q1.x * _1_t + q2_dash.x * t;
    result.y = q1.y * _1_t + q2_dash.y * t;
    result.z = q1.z * _1_t + q2_dash.z * t;
    result.w = q1.w * _1_t + q2_dash.w * t;

    return result.normalized(); // 正規化normalizeしてから返す
}

// 回転クォータニオン Axis軸(x軸,y軸,z軸)周りにradianの角度回転したクオータニオンを生成
inline Quaternion CreateRotationQuaternion(double radian, VECTOR axis)
{
    Quaternion result;
    double norm, cos_rad2, sin_rad2;
    result.w = result.x = result.y = result.z = 0.0;

    norm = axis.x * axis.x + axis.y * axis.y + axis.z * axis.z;
    if (norm <= 0.0) return result;

    norm = 1.0 / std::sqrt(norm);
    axis.x *= norm;
    axis.y *= norm;
    axis.z *= norm;

    cos_rad2 = std::cos(radian / 2.0);
    sin_rad2 = std::sin(radian / 2.0);

    result.w = cos_rad2;
    result.x = sin_rad2 * axis.x;
    result.y = sin_rad2 * axis.y;
    result.z = sin_rad2 * axis.z;

#if 0 // [比較]DXライブラリの回転を表すクォータニオンを返す関数
    // 回転を表すクォータニオンを返す
    extern FLOAT4 QTRot(VECTOR Axis, float Angle)
    {
        FLOAT4 Result;
        float Cos;
        float Sin;
        float Square;
        float Size;
        Square = Axis.x * Axis.x + Axis.y * Axis.y + Axis.z * Axis.z;
        if (Square < 0.0000001f)
        {
            return F4Get(-1.0f, -1.0f, -1.0f, -1.0f);
        }
        Size = _SQRT(Square);
        _SINCOS(0.5f * Angle, &Sin, &Cos);
        Result.w = Cos;
        Result.x = Axis.x / Size * Sin;
        Result.y = Axis.y / Size * Sin;
        Result.z = Axis.z / Size * Sin;
        return Result;
    }

#endif

    return result;
}

//クオータニオンを基礎となるbaseMatとオイラー角(pitchX,yawY,rollZ)から得る
inline Quaternion GetQuaternion(MATRIX& baseMat, VECTOR eularAngle)
{
    // [DXでクオータニオン] http://noa1105.seesaa.net/article/239449116.html
    Quaternion quaternion;  //クォータニオン
    float pitchX = eularAngle.x, yawY = eularAngle.y, rollZ = eularAngle.z;
    //float yaw = m_rotation.x, pitch = m_rotation.y, roll = m_rotation.z;
    // [z→x→yの順に回転] https://qiita.com/fullmated/items/ac62ebd1206a6487232d
    //ロール
    VECTOR zAxis = VGet(baseMat.m[2][0], baseMat.m[2][1], baseMat.m[2][2]);
    quaternion = quaternion * CreateRotationQuaternion(rollZ, zAxis);
    //ピッチ
    VECTOR xAxis = VGet(baseMat.m[0][0], baseMat.m[0][1], baseMat.m[0][2]);
    quaternion = quaternion * CreateRotationQuaternion(pitchX, xAxis);
    //ヨー
    VECTOR yAxis = VGet(baseMat.m[1][0], baseMat.m[1][1], baseMat.m[1][2]);
    quaternion = quaternion * CreateRotationQuaternion(yawY, yAxis);

    return quaternion;
}

//位置クォータニオン
inline Quaternion CreateXYZToQuaternion(double PosX, double PosY, double PosZ)
{
    Quaternion ans;
    ans.w = 0.0;
    ans.x = PosX;
    ans.y = PosY;
    ans.z = PosZ;
    return ans;
}

//クォータニオンから回転行列へ
inline MATRIX QuaternionToMatrix(Quaternion q)
{
    MATRIX mat = MGetIdent();
    //X軸
    mat.m[0][0] = 1.0 - 2.0 * q.y * q.y - 2.0 * q.z * q.z;
    mat.m[0][1] = 2.0 * q.x * q.y + 2.0 * q.w * q.z;
    mat.m[0][2] = 2.0 * q.x * q.z - 2.0 * q.w * q.y;

    //Y軸
    mat.m[1][0] = 2.0 * q.x * q.y - 2.0 * q.w * q.z;
    mat.m[1][1] = 1.0 - 2.0 * q.x * q.x - 2.0 * q.z * q.z;
    mat.m[1][2] = 2.0 * q.y * q.z + 2.0 * q.w * q.x;

    //Z軸
    mat.m[2][0] = 2.0 * q.x * q.z + 2.0 * q.w * q.y;
    mat.m[2][1] = 2.0 * q.y * q.z - 2.0 * q.w * q.x;
    mat.m[2][2] = 1.0 - 2.0 * q.x * q.x - 2.0 * q.y * q.y;

    return mat;
}

// クオータニオンを角度(単位:ラジアン0~2π)へ
inline VECTOR ToAngle(double x, Quaternion q)
{
    double cosX = std::cos(x);

    double sinY = (2 * q.x * q.z + 2 * q.y * q.w) / cosX;
    double cosY = (2 * std::pow(q.w, 2) + 2 * std::pow(q.z, 2) - 1) / cosX;
    double y = std::atan2(sinY, cosY);

    double sinZ = (2 * q.x * q.y + 2 * q.z * q.w) / cosX;
    double cosZ = (2 * std::pow(q.w, 2) + 2 * std::pow(q.y, 2) - 1) / cosX;
    double z = std::atan2(sinZ, cosZ);

    VECTOR angles;
    angles.x = std::fmod((x > 0.0) ? x : 2.0 * DX_PI + x, 2.0 * DX_PI); //2π=360 [xを0.0~2πの範囲に] https://mathwords.net/razian
    angles.y = std::fmod((y > 0.0) ? y : 2.0 * DX_PI + y, 2.0 * DX_PI);
    angles.z = std::fmod((z > 0.0) ? z : 2.0 * DX_PI + z, 2.0 * DX_PI);
    return angles;
}

// クオータニオンを角度へ(単位:ラジアン0~2π) [ジンバルロック状態のとき]https://qiita.com/yaegaki/items/7b6b1f487553def8e647
inline VECTOR ToAngleZimbalLock(double x, double z, Quaternion q)
{
    double y;
    if (x > 0)
    {
        double yMinusZ = std::atan2(2 * q.x * q.y - 2 * q.z * q.w, 2 * std::pow(q.w, 2) + 2 * std::pow(q.x, 2) - 1);
        y = yMinusZ + z;
    }
    else
    {
        double yPlusZ = std::atan2(-(2 * q.x * q.y - 2 * q.z * q.w), 2 * std::pow(q.w, 2) + 2 * std::pow(q.x, 2) - 1);
        y = yPlusZ - z;
    }

    VECTOR angles;
    angles.x = std::fmod((x > 0.0) ? x : 2.0 * DX_PI + x, 2.0 * DX_PI); //2π=360 [xを0.0~2πの範囲に] https://mathwords.net/razian
    angles.y = std::fmod((y > 0.0) ? y : 2.0 * DX_PI + y, 2.0 * DX_PI);
    angles.z = std::fmod((z > 0.0) ? z : 2.0 * DX_PI + z, 2.0 * DX_PI);
    return angles;
}

// 同じ回転を表す角度を取得する https://qiita.com/yaegaki/items/7b6b1f487553def8e647
inline VECTOR GetSameRotationAngle(VECTOR angle)
{
    VECTOR sameRot;
    // x = 180度 - x, y = 180度 + y, z = 180度 + z つまり π - x, π + y, π + z が同じ回転の角度の候補
    sameRot.x = DX_PI_F - angle.x;
    sameRot.y = DX_PI_F + angle.y;
    sameRot.z = DX_PI_F + angle.z;
    return sameRot;
}

// クオータニオンを角度(単位:ラジアン)に戻す: return オイラー角度(x,y,z)それぞれの軸の回転ラジアン
inline std::vector<VECTOR> QuaternionToAngle(Quaternion q)
{
    double sinX = 2 * q.y * q.z - 2 * q.x * q.w;
    double absSinX = std::abs(sinX); // 角度の絶対値(単位:ラジアン)
    const double threshold = 0.001;
    // X軸の回転が0度付近の場合、0になるか360で差が大きいので0に丸める
    if (absSinX < threshold)
        sinX = 0.0;

    //[オイラー角とジンバルロック(±90度のとき無数に角度がありうる)]https://qiita.com/yaegaki/items/7b6b1f487553def8e647
    double x = std::asin(-sinX);
    // X軸の回転が90度付近の場合はジンバルロック状態になっている
    if (std::isnan(x) || std::abs((std::abs(x) - DX_PI / 2)) < threshold)
    {
        double sign = (sinX < 0) ? -1.0 : 1.0;
        x = sign * (DX_PI / 2);
        VECTOR result = ToAngleZimbalLock(x, 0.0, q); // // Z = 0.0の時の角度を求める
        return { result, GetSameRotationAngle(result) }; //それと同じ回転を表す角度も候補として返す
    }
    else
    {    // x 度と π-x 度の2種類の角度とそれらと同じ回転を表す角度も候補として返す
        VECTOR result1 = ToAngle(x, q);
        VECTOR result2 = ToAngle(DX_PI - x, q);
        return { result1, result2, GetSameRotationAngle(result1),GetSameRotationAngle(result2) };
    }

#if 0
    //[回転行列から角度に戻す]https://qiita.com/q_tarou/items/46e5045068742dfb2fa6
    VECTOR resultAngle; // オイラー角度(x,y,z)それぞれの軸の回転ラジアン
    MATRIX matRot = QuaternionToMatrix(q); // クオータニオンから回転行列を得る
    const double threshold = 0.001;
    if (std::abs(matRot.m[2][1] - 1.0) < threshold) // matRot.m[2][1] = sin(x) = 1の時
    {
        resultAngle.x = DX_PI_F / 2;
        resultAngle.y = 0;
        resultAngle.z = std::atan2(matRot.m[1][0], matRot.m[0][0]);
    }
    else if (std::abs(matRot.m[2][1] + 1.0) < threshold) // matRot.m[2][1] = sin(x) = -1の時
    {
        resultAngle.x = -DX_PI_F / 2;
        resultAngle.y = 0;
        resultAngle.z = std::atan2(matRot.m[1][0], matRot.m[0][0]);
    }
    else
    {
        resultAngle.x = std::asin(matRot.m[2][1]);
        resultAngle.y = std::atan2(-matRot.m[2][0], matRot.m[2][2]);
        resultAngle.z = std::atan2(-matRot.m[0][1], matRot.m[1][1]);
    }
    return resultAngle;
#endif
}

inline VECTOR Quaternion::eular() // オイラー角を得る
{
    std::vector<VECTOR> angles = QuaternionToAngle(*this); // クオータニオンを角度(単位:ラジアン)に戻す
    float xDifMin = angles[0].x - _eular.x; // 現在の角度との差をとる
    xDifMin = (xDifMin < 0) ? -xDifMin : xDifMin; // 絶対値に直す
    for (size_t i = 1, iSize = angles.size(); i < iSize; ++i) // 角度の候補が2つ以上あるので一番現在の角度との差が小さい角度を採用する
    {
        float xDif = angles[1].x - _eular.x; // 現在の角度との差をとる
        xDif = (xDif < 0) ? -xDif : xDif; // 絶対値に直す
        angles[0].x = (xDifMin < xDif) ? angles[0].x : angles[i].x; // 差が小さい方の角度を採用する
        angles[0].y = (xDifMin < xDif) ? angles[0].y : angles[i].y;
        angles[0].z = (xDifMin < xDif) ? angles[0].z : angles[i].z;
    }
    _eular.x = angles[0].x * 180.0f / DX_PI_F;
    _eular.y = angles[0].y * 180.0f / DX_PI_F;
    _eular.z = angles[0].z * 180.0f / DX_PI_F;
    return _eular;
}

// 0~360度のオイラー角でクオータニオンを再設定する
inline void Quaternion::SetRotation(VECTOR eularAngle)
{
    this->_eular = eularAngle;
    VECTOR radAngle = VGet(eularAngle.x / 180.0f * DX_PI_F, eularAngle.y / 180.0f * DX_PI_F, eularAngle.z / 180.0f * DX_PI_F);
    MATRIX mat;
    DxLib::CreateIdentityMatrix(&mat); // 単位行列を作成
    Quaternion q = GetQuaternion(mat, radAngle); // x,y,z軸に対してrotationで指定された角度回転
    this->w = q.w; this->x = q.x; this->y = q.y; this->z = q.z;
}

#if 0 //[比較] DXのクオータニオン関数

// 3次元空間上の点を任意の軸の周りに任意の角度だけ回転させる関数
extern VECTOR VRotQ(VECTOR P, VECTOR Axis, float Angle)
{
    VECTOR Result;
    FLOAT4 Temp;
    FLOAT4 RotQ;
    float Cos;
    float Sin;
    float Square;
    float Size;
    Square = Axis.x * Axis.x + Axis.y * Axis.y + Axis.z * Axis.z;
    if (Square < 0.0000001f)
    {
        return VGet(-1.0f, -1.0f, -1.0f);
    }
    Size = _SQRT(Square);
    _SINCOS(0.5f * -Angle, &Sin, &Cos);
    RotQ.w = Cos;
    RotQ.x = Axis.x / Size * Sin;
    RotQ.y = Axis.y / Size * Sin;
    RotQ.z = Axis.z / Size * Sin;
    Temp.w = P.x * RotQ.x + RotQ.y * P.y + RotQ.z * P.z;
    Temp.x = P.x * RotQ.w - RotQ.y * P.z + RotQ.z * P.y;
    Temp.y = P.y * RotQ.w - RotQ.z * P.x + RotQ.x * P.z;
    Temp.z = P.z * RotQ.w - RotQ.x * P.y + RotQ.y * P.x;
    Result.x = RotQ.x * Temp.w + Temp.x * RotQ.w + (Temp.y * RotQ.z - Temp.z * RotQ.y);
    Result.y = RotQ.y * Temp.w + Temp.y * RotQ.w + (Temp.z * RotQ.x - Temp.x * RotQ.z);
    Result.z = RotQ.z * Temp.w + Temp.z * RotQ.w + (Temp.x * RotQ.y - Temp.y * RotQ.x);
    return Result;
}

#endif

#endif



クオータニオンについての数学はムズイので、とにかく重要なとこは下記スライドの赤枠の部分「クオータニオンを回転行列に変換しているRへの式」に着目



これで、クオータニオンを普通のX軸Y軸Z軸のオイラー角に変換できます。下記の抜き出したコードの数式と上の赤枠を比較してみよう。

抜き出したクオータニオンから、X軸、Y軸、Z軸のオイラー角への変換式のぶぶん。
//クォータニオンから回転行列へ
inline MATRIX QuaternionToMatrix(Quaternion q)
{
    MATRIX mat = MGetIdent();
    //X軸
    mat.m[0][0] = 1.0f - 2.0f * q.y * q.y - 2.0f * q.z * q.z;
    mat.m[0][1] = 2.0f * q.x * q.y + 2.0f * q.w * q.z;
    mat.m[0][2] = 2.0f * q.x * q.z - 2.0f * q.w * q.y;

    //Y軸
    mat.m[1][0] = 2.0f * q.x * q.y - 2.0f * q.w * q.z;
    mat.m[1][1] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.z * q.z;
    mat.m[1][2] = 2.0f * q.y * q.z + 2.0f * q.w * q.x;

    //Z軸
    mat.m[2][0] = 2.0f * q.x * q.z + 2.0f * q.w * q.y;
    mat.m[2][1] = 2.0f * q.y * q.z - 2.0f * q.w * q.x;
    mat.m[2][2] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.y * q.y;

    return mat;
}

数学的には、qとqの逆クオータニオン-1(単位1のときは共役)とではさみこまれているのでR1 R2 R3..と順に掛け算していっても大丈夫なことも頭の片隅に。
くわしい内容はネットでいろいろ検索してわかる範囲で数式をおいかけてみるがよろし。

UnityでもGameObjectの内部では、オイラー角ではなくて、クオータニオンが使われています。UI上ではX,Y,Zの角度が見えるけどあれは見せかけです。

main.cppのコードをいったんまっさらきれいにします。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include "Input.h"
#include "Screen.h"
#include "Ply.h"
#include "DataMV1.h"
#include "DataPly.h"
#include "Collision3D.h"
#include "MyDraw.h" // 自作の描画処理

#include "Editor.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズWidth×Heightのカラービット数32ビットで起動
    SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    SetDrawScreen(DX_SCREEN_BACK);
  
    // ゲームのwhileループを開始する前の初期化処理
    float animTime = 0.0f; // アニメの現在時刻
    float animSpeed = 2.0f; // MV1のアニメの再生速度設定 2.0fなら2倍速
    float animStepTime = 1.0f; // MV1のアニメの毎時間の進む時間の設定
    std::shared_ptr<DataMV1> pDevilData = Resource::MakeShared<DataMV1>("Image/devil@kick.mv1");
    pDevilData->Load(); // 3Dモデルをmv1形式で読込み
    std::shared_ptr<DataMV1> pDevilAttackAnimData = Resource::MakeShared<DataMV1>("Image/devil@jump.mv1");
    pDevilAttackAnimData->Load(); // 3Dの追加アニメデータをmv1形式で読込み
    pDevilData->AttachAnim(*pDevilAttackAnimData); // アニメを追加でflyのほうをベースとしたモデルにアタッチ
    int animeButton = 0; // ボタンでアニメを切り替えられるようにする

    std::shared_ptr<Texture> pTexImage = Resource::MakeShared<Texture>("Image/dice.png");
    pTexImage->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
    assert((int)*pTexImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる

    std::shared_ptr<DataPly> pPlyData = Resource::MakeShared<DataPly>("Image/dice.ply");
    pPlyData->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
   


    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 0.0f; keyControlXYZ.y = 0.0f; keyControlXYZ.z = 0;
    VECTOR keyControlAngle = VGet(0.0f,0.0f,0.0f); // 回転

        // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen1 = SubScreen::Create()->Set(600, 450, 600, 100, 255, 0, "サブスクリーン1")->SetIsBarFrameShow(true,true);
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen2 = SubScreen::Create()->Set(200, 100, 150, 100, 255, 1, "サブスクリーン2\nバーを30で太めに")->SetFrame(1,GetColor(255,255,255),true,30);
    // サブのスクリーンを生成する 透明度を128にして半透明にしてみる
    std::shared_ptr<SubScreen> subscreen3 = SubScreen::Create()->Set(150, 450, 300, 100, 128, 1, "サブスクリーン3")->SetIsBarFrameShow(true)->SetBarColor(GetColor(0, 0, 255));
    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen4 = SubScreen::Create()->Set(300, 100, 600, 100, 255, 1, "サブスクリーン4");


    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    Editor& editor = Editor::GetInstance(); // エディタのシングルトン参照を得る

    float radius_lineStart0 = 0.2f, radius_lineEnd1 = 0.2f; // カプセルと球体の半径の初期設定
    // カプセル点(0)
    std::shared_ptr<DragCapsule> lineStart0 = std::make_shared<DragCapsule>(-3, 0, -4, radius_lineStart0, 1.0f, Axis3D::Y, GetColor(255, 165, 165));
    // 球体の点(1)
    std::shared_ptr<DragSphere> lineEnd1 = std::make_shared<DragSphere>(-2, -3, -3.5f, radius_lineEnd1 * 2, GetColor(255, 165, 0));

    // 三角ポリゴン(2)
    std::vector<std::shared_ptr<DragPoint>> poly2_Pos = { std::make_shared<DragPoint>(-3.0f,-2.0f,-4.0f),
                                                                         std::make_shared<DragPoint>(-2.0f,-2.0f,-3.0f),
                                                                         std::make_shared<DragPoint>(-2.0f,-2.0f,-4.0f) }; // ポリゴンの3点

    // 四角ボックスの点(4)
    Vector3 boxSize{ 1.0f,2.0f,3.0f };
    std::shared_ptr<DragBox> boxPoint4 = std::make_shared<DragBox>(7, -3, 4, boxSize, GetColor(255, 255, 0));


    std::list<std::weak_ptr<DragPoint>> dragPoints; // ドラッグ対象の点の共有ポインタのリスト

    dragPoints.emplace_back(lineStart0); // リストにカプセル点0を追加
    dragPoints.emplace_back(lineEnd1); // リストに球体の点1を追加
    dragPoints.emplace_back(boxPoint4); // リストに四角ボックスの点4を追加

    dragPoints.emplace_back(poly2_Pos[0]); // リストにポリゴン2の3点を追加
    dragPoints.emplace_back(poly2_Pos[1]);
    dragPoints.emplace_back(poly2_Pos[2]);


    //奥行0.1~5000までをカメラの描画範囲とする
    DxLib::SetCameraNearFar(0.1f, 5000.0f);

    ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Input::Update(); // マウスのドラッグなどを更新

        editor.UpdateKeyInput(); // エディタのキーのインプットを更新

        editor.UpdateDragPoints(&dragPoints); // ドラッグ点のリストを更新

        if (!editor.isDraggingPoints) editor.UpdateMouseInput(); // エディタのマウスのインプットを更新
        editor.UpdateCamera(); // エディタのカメラを更新
       
        // キーボードの 0 1 キーで3Dアニメを切り替えられるようにする
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_0))
            animeButton = 0;
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_1))
            animeButton = 1;

        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        VECTOR position = VGet(keyControlXYZ.x, keyControlXYZ.y, keyControlXYZ.z); // 3Dモデルの位置

        float width =  pTexImage->m_XSize; float height =  pTexImage->m_YSize; // テクスチャ画像のサイズ
        VECTOR scale = VGet(1.0f, 1.0f, 1.0f); // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)とcキー(C)でx,y,z軸まわりに画像を回転
        if (PadInput & PAD_INPUT_A) keyControlAngle.x += speed;
        if (PadInput & PAD_INPUT_B) keyControlAngle.y += speed;
        if (PadInput & PAD_INPUT_C) keyControlAngle.z += speed;

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数
        const float xRotate = keyControlAngle.x * Deg2Rad;  const float yRotate = keyControlAngle.y * Deg2Rad; const float zRotate = keyControlAngle.z * Deg2Rad;


        MATRIX  matWorldDefault; // デフォルトのワールド行列
        DxLib::GetTransformToWorldMatrix(&matWorldDefault); // デフォルトのワールド行列を記憶しておく
        {
            //[ワールド行列] https://ny-program.hatenablog.com/entry/2019/09/15/114245
            MATRIX mat; // モデルのスケール×回転×移動を行列計算で設定する

            //[スケール]拡大縮小のスケール行列を得る
            mat = MGetScale(scale); // まずは拡大縮小のスケール行列からはじめる
            // [勉強]↓スケール行列は 斜めに x , y , zのスケールの倍率がならんでいる行列
            //mat.m[0][0] = scale.x;  mat.m[0][1] = 0.0f;    mat.m[0][2] = 0.0f;     mat.m[0][3] = 0.0f;
            //mat.m[1][0] = 0.0f;     mat.m[1][1] = scale.y; mat.m[1][2] = 0.0f;     mat.m[1][3] = 0.0f;
            //mat.m[2][0] = 0.0f;     mat.m[2][1] = 0.0f;    mat.m[2][2] = scale.z;  mat.m[2][3] = 0.0f;
            //mat.m[3][0] = 0.0f;     mat.m[3][1] = 0.0f;    mat.m[3][2] = 0.0f;     mat.m[3][3] = 1.0f;

            //[回転]
            MATRIX matRot;
            DxLib::CreateIdentityMatrix(&matRot);
            // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
            matRot = MMult(matRot, MGetRotZ(zRotate));
            matRot = MMult(matRot, MGetRotX(xRotate));
            matRot = MMult(matRot, MGetRotY(yRotate));
            mat = MMult(mat, matRot); //スケール × 回転

            //[移動]
            MATRIX matTrans = MGetTranslate(VGet(position.x, position.y, position.z)); // 位置positionをここで設定
            mat = MMult(mat, matTrans); //スケール×回転×移動

            DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4341

            DxLib::DrawPolygon32bitIndexed3D(pPlyData->Vertex.data(), pPlyData->Vertex.size(), pPlyData->Index.data(), pPlyData->numIndex / 3, (int)*pTexImage, FALSE); // 三角形ポリゴンをたくさん描画
        }
        DxLib::SetTransformToWorld(&matWorldDefault); // デフォルトのワールド行列に戻しておく

        DxLib::MV1DrawModel(devil3DModel); // 3Dモデル(.mv1形式)を描く

        DxLib::DrawFormatString(0, 100, GetColor(255, 255, 0), "x:%f y:%f Angle:%f %f %f", position.x, position.y, keyControlAngle.x, keyControlAngle.y, keyControlAngle.z);

        // ファイルのパス + ":" + アニメ名でアクセス
        std::string animPathName = (animeButton == 0) ? "Image/devil@kick.mv1:mixamo.com" : "Image/devil@jump.mv1:mixamo.com";
        assert(pDevilData->count(animPathName) > 0 && "指定されたアニメ名のアニメはMV1に付属していませんでした");
        animTime += animStepTime * animSpeed; // 1.0×2倍 なら2倍速でアニメ時刻が進む
        if (animTime > (*pDevilData)[animPathName].endTime)
            animTime = 0.0f; // 0秒目へとアニメをループさせる
       
        // 再生時間をセットする
        DxLib::MV1SetAttachAnimTime((int)*pDevilData, (*pDevilData)[animPathName].index, animTime);

        // 三角ポリゴン(2)を含む平面
        Plane4 plane_poly2{ *poly2_Pos[0],*poly2_Pos[1],*poly2_Pos[2] }; // 3点を含む平面を求める

        Vector3 collision2; // ポリゴン2と線(lineStart0→lineEnd1)の交点
        Vector3 polygon[3] = { *poly2_Pos[0],*poly2_Pos[1],*poly2_Pos[2] };
        // 三角ポリゴン(2) と 線(lineStart0→lineEnd1) の当たり判定 をして 交点collision2 も求める
        bool isCollision2 = isCollisionPoly_Line(polygon, 3, plane_poly2, *lineStart0, *lineEnd1, &collision2);

        // 三角ポリゴン(2)を描く
        DxLib::DrawTriangle3D(*poly2_Pos[0], *poly2_Pos[1], *poly2_Pos[2], GetColor(255, 255, 255), FALSE);
        // lineStart0から平面の中心へ向かう線 を描く
        DxLib::DrawLine3D(*lineStart0, -plane_poly2.n * plane_poly2.dist, GetColor(255, 165, 165));
        // 平面の中心 から 交点collision2 へ向かう(水色) を描く
        DxLib::DrawLine3D(-plane_poly2.n * plane_poly2.dist, collision2, GetColor(0, 255, 255));
        // 平面の中心 に水色の球を描く
        DxLib::DrawSphere3D(-plane_poly2.n * plane_poly2.dist, 0.05, 16, GetColor(0, 255, 255), GetColor(255, 255, 255), TRUE);
        // 平面の中心 から 原点 に向かう線(水色)を描く
        DxLib::DrawLine3D(-plane_poly2.n * plane_poly2.dist, Vector3{0,0,0}, GetColor(0, 255, 255));

        // ポリゴンを横切る線lineStart0→lineEnd1を結ぶ線(オレンジ)を描く
        DxLib::DrawLine3D(*lineStart0, *lineEnd1, GetColor(255, 165, 0));
 
        Vector3 collisionRay2; // 三角ポリゴン(2)とマウスカーソルの光線(rayPos)の交点
        bool isCollisionRay2 = isCollisionPoly_Line(polygon, 3, plane_poly2, rayStart, rayEnd - rayStart, &collisionRay2);
        // カメラの始点から飛ばしたレイと三角ポリゴン(2)の交点に目印の球を描画
        if (isCollisionRay2) DxLib::DrawSphere3D(collisionRay2, 0.01, 16, GetColor(255, 165, 0), GetColor(255, 255, 255), FALSE);

        // 三角ポリゴン(2)の存在する平面を描く
        MyDraw::DrawPlane(plane_poly2, *poly2_Pos[0], 8,GetColor(0,255,255));
        // 三角ポリゴン(2)の存在する平面の単位法線方向の矢印(緑色)を描く
        MyDraw::DrawArrow3D(-plane_poly2.n * plane_poly2.dist, -plane_poly2.n * plane_poly2.dist + plane_poly2.n,
                                     MyDraw::ArrowType::None, MyDraw::ArrowType::Cone, 0.1f, 0.2f, GetColor(0, 255, 0));

        // 平面と軸並行ボックス(4)AABBの当たり判定とその押し戻しゴーストを描く
        Vector3 boxMin, boxMax, boxCenter;
        boxPoint4->GetParams(boxCenter, boxMin, boxMax);
        float distAABB; // 押し戻し距離(当たってないときは平面までの距離)
        bool isCollisionPlaneAABB = isCollisionPlane_AABB(plane_poly2, boxMin, boxMax, &distAABB);
        if (isCollisionPlaneAABB) // 平面とボックスが衝突したら↓法線×押し戻し距離 ぶんずらしてゴーストを描く
            DxLib::DrawCube3D(boxMin + plane_poly2.n * distAABB,
                              boxMax + plane_poly2.n * distAABB, GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);
     
        // 平面とカプセル(0)の当たり判定とその押し戻しゴーストを描く
        Vector3 centerCapsule, startCapsule, endCapsule;
        float radiusCapsule;
        lineStart0->GetParams(centerCapsule, startCapsule, endCapsule, radiusCapsule); // 線の始点lineStart0からカプセル(0)のパラメータを得る
        float distCapsule; // 平面からカプセルをどれだけ押しもどせば当たらなくなるかの距離
        bool isCollisionPlaneCapsule = isCollisionPlane_Capsule(plane_poly2, startCapsule, endCapsule, radiusCapsule, &distCapsule);
        if (isCollisionPlaneCapsule) // 平面とカプセルが衝突していたら、平面の法線n方向に押し戻したゴーストを描く
            DxLib::DrawCapsule3D(startCapsule + plane_poly2.n * distCapsule,
                endCapsule + plane_poly2.n * distCapsule, radiusCapsule, 8, GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);

        // 平面と球体(1)の当たり判定とその押し戻しゴーストを描く
        float sphereRadius;
        Vector3 sphereCenter;
        lineEnd1->GetParams(sphereCenter, sphereRadius); // 線の終点lineEnd1から球体(1)のパラメータを得る
        float distSphere; // 平面から球をどれだけ押し戻せば当たらなくなるかの距離
        bool isCollisionPlaneSphere = isCollisionPlane_Sphere(plane_poly2, sphereCenter, sphereRadius, &distSphere);
        if (isCollisionPlaneSphere) // 平面と球が衝突していたら、平面の法線n方向に押し戻したゴーストを描く
            DxLib::DrawSphere3D(*lineEnd1 + plane_poly2.n * distSphere, sphereRadius, 8, GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);

        if (isCollision2)
        {   // 三角ポリゴン(2)と線(lineStart0→lineEnd1)の交わった交点に橙の球を表示
            DxLib::DrawSphere3D(collision2, 0.05, 16, GetColor(255, 165, 0), GetColor(255, 255, 255), TRUE);
            // 球の中心とポリゴンの3点を結ぶ線の角度の合計がほぼ360度(2π)になるかがポリゴン内部かの判定条件となる
            DxLib::DrawLine3D(collision2, *poly2_Pos[0], GetColor(255, 165, 165));
            DxLib::DrawLine3D(collision2, *poly2_Pos[1], GetColor(255, 165, 165));
            DxLib::DrawLine3D(collision2, *poly2_Pos[2], GetColor(255, 165, 165));
        }

        // ドラッグ点とその当たり判定のオブジェクト(ボックスや球やカプセルなど)を描く
        for (auto dragPoint : dragPoints)
            dragPoint.lock()->Draw(); //ドラッグ点とそのまわりに広がる当たり判定範囲のカプセルやボックスなどをまとめて描く


        editor.Draw(); // エディタのDraw処理

        ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    WaitKey();
    // DXライブラリの後始末
    DxLib_End();
    // ソフトの終了
    return 0;
}



main.cppのコードを変更してダイスをクオータニオンで回転させるようにします。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include "Input.h"
#include "Screen.h"
#include "Ply.h"
#include "DataMV1.h"
#include "DataPly.h"
#include "Collision3D.h"
#include "MyDraw.h" // 自作の描画処理
#include "Quaternion.h"

#include "Editor.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズWidth×Heightのカラービット数32ビットで起動
    SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    SetDrawScreen(DX_SCREEN_BACK);
  
    // ゲームのwhileループを開始する前の初期化処理
    float animTime = 0.0f; // アニメの現在時刻
    float animSpeed = 2.0f; // MV1のアニメの再生速度設定 2.0fなら2倍速
    float animStepTime = 1.0f; // MV1のアニメの毎時間の進む時間の設定
    std::shared_ptr<DataMV1> pDevilData = Resource::MakeShared<DataMV1>("Image/devil@kick.mv1");
    pDevilData->Load(); // 3Dモデルをmv1形式で読込み
    std::shared_ptr<DataMV1> pDevilAttackAnimData = Resource::MakeShared<DataMV1>("Image/devil@jump.mv1");
    pDevilAttackAnimData->Load(); // 3Dの追加アニメデータをmv1形式で読込み
    pDevilData->AttachAnim(*pDevilAttackAnimData); // アニメを追加でflyのほうをベースとしたモデルにアタッチ
    int animeButton = 0; // ボタンでアニメを切り替えられるようにする

    std::shared_ptr<Texture> pTexImage = Resource::MakeShared<Texture>("Image/dice.png");
    pTexImage->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
    assert((int)*pTexImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる

    std::shared_ptr<DataPly> pPlyData = Resource::MakeShared<DataPly>("Image/dice.ply");
    pPlyData->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
   


    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 0.0f; keyControlXYZ.y = 0.0f; keyControlXYZ.z = 0;
    VECTOR keyControlAngle = VGet(0.0f,0.0f,0.0f); // 回転
    Quaternion quaternion; // 回転クオータニオン

        // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen1 = SubScreen::Create()->Set(600, 450, 600, 100, 255, 0, "サブスクリーン1")->SetIsBarFrameShow(true,true);
   

    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    Editor& editor = Editor::GetInstance(); // エディタのシングルトン参照を得る

   

    std::list<std::weak_ptr<DragPoint>> dragPoints; // ドラッグ対象の点の共有ポインタのリスト

   

    //奥行0.1~5000までをカメラの描画範囲とする
    DxLib::SetCameraNearFar(0.1f, 5000.0f);

    ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Input::Update(); // マウスのドラッグなどを更新

        editor.UpdateKeyInput(); // エディタのキーのインプットを更新

        editor.UpdateDragPoints(&dragPoints); // ドラッグ点のリストを更新

        if (!editor.isDraggingPoints) editor.UpdateMouseInput(); // エディタのマウスのインプットを更新
        editor.UpdateCamera(); // エディタのカメラを更新
       
        // キーボードの 0 1 キーで3Dアニメを切り替えられるようにする
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_0))
            animeButton = 0;
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_1))
            animeButton = 1;

        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        VECTOR position = VGet(keyControlXYZ.x, keyControlXYZ.y, keyControlXYZ.z); // 3Dモデルの位置

        float width =  pTexImage->m_XSize; float height =  pTexImage->m_YSize; // テクスチャ画像のサイズ
        VECTOR scale = VGet(1.0f, 1.0f, 1.0f); // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)とcキー(C)でx,y,z軸まわりに画像を回転
        if (PadInput & PAD_INPUT_A) quaternion *= CreateRotationQuaternion(speed * DX_PI / 180, VGet(1, 0, 0)); // X軸(1,0,0)を軸にspeed度ぶんだけ回転
        if (PadInput & PAD_INPUT_B) quaternion *= CreateRotationQuaternion(speed * DX_PI / 180, VGet(0, 1, 0)); // Y軸(0,1,0)を軸にspeed度ぶんだけ回転
        if (PadInput & PAD_INPUT_C) quaternion *= CreateRotationQuaternion(speed * DX_PI / 180, VGet(0, 0, 1)); // Z軸(0,0,1)を軸にspeed度ぶんだけ回転

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数
        const float xRotate = keyControlAngle.x * Deg2Rad;  const float yRotate = keyControlAngle.y * Deg2Rad; const float zRotate = keyControlAngle.z * Deg2Rad;


        MATRIX  matWorldDefault; // デフォルトのワールド行列
        DxLib::GetTransformToWorldMatrix(&matWorldDefault); // デフォルトのワールド行列を記憶しておく
        {
            //[ワールド行列] https://ny-program.hatenablog.com/entry/2019/09/15/114245
            MATRIX mat; // モデルのスケール×回転×移動を行列計算で設定する

            //[スケール]拡大縮小のスケール行列を得る
            mat = MGetScale(scale); // まずは拡大縮小のスケール行列からはじめる
            // [勉強]↓スケール行列は 斜めに x , y , zのスケールの倍率がならんでいる行列
            //mat.m[0][0] = scale.x;  mat.m[0][1] = 0.0f;    mat.m[0][2] = 0.0f;     mat.m[0][3] = 0.0f;
            //mat.m[1][0] = 0.0f;     mat.m[1][1] = scale.y; mat.m[1][2] = 0.0f;     mat.m[1][3] = 0.0f;
            //mat.m[2][0] = 0.0f;     mat.m[2][1] = 0.0f;    mat.m[2][2] = scale.z;  mat.m[2][3] = 0.0f;
            //mat.m[3][0] = 0.0f;     mat.m[3][1] = 0.0f;    mat.m[3][2] = 0.0f;     mat.m[3][3] = 1.0f;

            //[回転]
            MATRIX matRot;
            DxLib::CreateIdentityMatrix(&matRot);
            // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
            matRot = MMult(matRot, MGetRotZ(zRotate));
            matRot = MMult(matRot, MGetRotX(xRotate));
            matRot = MMult(matRot, MGetRotY(yRotate));
            mat = MMult(mat, matRot); //スケール × 回転

            mat = MMult(mat, QuaternionToMatrix(quaternion)); // スケール × 回転

            //[移動]
            MATRIX matTrans = MGetTranslate(VGet(position.x, position.y, position.z)); // 位置positionをここで設定
            mat = MMult(mat, matTrans); //スケール×回転×移動

            DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4341

            DxLib::DrawPolygon32bitIndexed3D(pPlyData->Vertex.data(), pPlyData->Vertex.size(), pPlyData->Index.data(), pPlyData->numIndex / 3, (int)*pTexImage, FALSE); // 三角形ポリゴンをたくさん描画
        }
        DxLib::SetTransformToWorld(&matWorldDefault); // デフォルトのワールド行列に戻しておく

        DxLib::MV1DrawModel(devil3DModel); // 3Dモデル(.mv1形式)を描く

        auto xyzAngle = QuaternionToAngle(quaternion); // クオータニオンからそれに対応するオイラー角度の候補を得る(候補は最大3種ありうるし、ある軸が傾いていて0度じゃない場合他の軸も連動)
        DxLib::DrawFormatString(0, 100, GetColor(255, 255, 0), "x:%f y:%f Angle:%d %d %d", position.x, position.y, (int)(xyzAngle[0].x * 180 / DX_PI), (int)(xyzAngle[0].y * 180 / DX_PI), (int)(xyzAngle[0].z * 180 / DX_PI));

        // ファイルのパス + ":" + アニメ名でアクセス
        std::string animPathName = (animeButton == 0) ? "Image/devil@kick.mv1:mixamo.com" : "Image/devil@jump.mv1:mixamo.com";
        assert(pDevilData->count(animPathName) > 0 && "指定されたアニメ名のアニメはMV1に付属していませんでした");
        animTime += animStepTime * animSpeed; // 1.0×2倍 なら2倍速でアニメ時刻が進む
        if (animTime > (*pDevilData)[animPathName].endTime)
            animTime = 0.0f; // 0秒目へとアニメをループさせる
       
        // 再生時間をセットする
        DxLib::MV1SetAttachAnimTime((int)*pDevilData, (*pDevilData)[animPathName].index, animTime);

       

        editor.Draw(); // エディタのDraw処理

        ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    WaitKey();
    // DXライブラリの後始末
    DxLib_End();
    // ソフトの終了
    return 0;
}


いかがでしょう?キーボードのzボタン(X軸回転)、xボタン(Y軸回転)、cボタン(Z軸回転)でダイスが回転したでしょうか?
もとのx,y,zをオイラー角で2つ以上のx,y,zを同時に回転させるとぶるぶる不安定だった回転が、明確に一つの軸を中心に回転するようになったのが実感できますでしょうか?
このようにクオータニオンなら、x,y,zを同時に回転させたり、X,Y,Z軸じゃない「斜め方向の軸」などを中心に回転させたりすることができて、
より自由に安定した形で回転をさせることができるので、3Dの回転にはオイラー角でなく、クオータニオンを利用するのです。
オイラー角でのx,y,zの同時押しぶるぶる回転
クオータニオンでの安定した回転、斜め軸回転

自作したシェーダー言語をコンパイルしてGPU上で動くプログラムを実感する

まず最初に、シェーダーはC++とはまったく別の言語です。(文法は多少似てるけど)
そして、シェーダー言語はコンパイルで機械語へビルドして、事前にGPUへ送りつけておかねば動かない。
GPUは送られたシェーダー実行ファイルをもとにした処理をCPUとは独立して実行します。
つまり、いままで勉強してきたC++だけでは、あくまでCPUプログラマどまり、であること。
シェーダー言語でGPUに腕を突っ込んではじめて、GPUプログラマとして、CPUとGPUを両腕にかかえてPCの支配者への入り口に立てるのだ!!

シェーダ言語を定めている国際的な勢力は大きく分けて二つある。
HLSL(マイクロソフトDirectX系、NVIDIA協力)とGLSL(その他OpenGL:Vulkan系)の2大勢力だ。

DXライブラリはDirectXをベースにしているので、HLSLの仕様をまずは気にしておくとよい。
シェーダー言語(拡張子.hlsl)をビルドするためのコンパイラはfxc.exeファイルであるが、
DXライブラリにはそのうちのエフェクト(拡張子.fx)をビルドするためのコンパイラが「DXライブラリをインストールしたフォルダ/Tool/ShaderCompiler/」というフォルダにおいてある。
まずは下記を参考にDXライブラリのツールフォルダにあるShaderCompiler.exeを探し出そう。(最新Ver.のDirectXのシェーダーをビルドするにはこれ以外にDirectXの最新版をインストールしてfxc.exeを探す必要もあるかも)


シェーダー言語の拡張子は.hlslとか.fxになるが、
とりあえず手っ取り早くはWindowsのメモ帳で新規に空のテキストファイルを作成して、[名前を付けて保存]で拡張子を.fxファイルにして保存すればコードのファイルを作成できる。


シェーダー言語と3D描画についてのDXライブラリの公式サイトのコードはここにあるが、
今回は入門として、ただ単に、3Dを全部赤色に塗りつぶすシェーダーを作るところから始めてみよう。
まず、上記で作成した TestVS.fx のVSはVertex Shader(Vertex:頂点 Shader:シェーダ)の略だ。
3Dのキャラクターとかの3Dモデルは大量の三角形ポリゴンの面の集まりで描かれている。
そして、その三角形は3点の頂点の集まりだ。頂点シェーダーはその大量の各点で行う計算について書いたプログラムだ。

入門者は各点で具体的に何を計算するの?とイメージできないと思うが、
例えば、点の明るさを計算することを考えてみよう。
ライト(光源)から1つの頂点へのベクトル と その頂点からカメラ へ向かうベクトル との反射を計算して、
すべてのライト(光源)について 反射する光の強さの数値を蓄積させれば、その1つの頂点の明るさが計算できる。
1万個 点があっても、GPUの中に沢山あるユニットで並列で手分けして1万個の点の明るさを 頂点シェーダー のプログラムに沿って計算させてしまえば、
画面に描くキャラクタの表面の点のひとつひとつの明るさが計算できるわけだ。
三角ポリゴンの3点の明るさが求まれば、あとはその三角形の内側の点の明るさは3点の明るさのグラデーションで計算すればいいから3Dキャラが描けるわけだ。

これがシェード(陰影)を計算するシェーダーのからくりのおおまかな説明だ。
3D空間に大量に点が増えても、GPUの中で働く大量のユニット(アルバイト人員的な計算要員)の「人海戦術GPUパワー」で強引に計算しているイメージだ。
そして、そのシェーダー言語のプログラミングを自分でカスタマイズできれば、色んな3Dの描き方 や 色んな平行計算(AIとかも) ができるというわけです。

今回はまずはライトとは関係なしに、蓄積する点の明るさのうち、拡散光(Diffuse)を強引に赤色[RGB = (1.0f,0,0f,0,0f)]にして描く実験をしてみよう。

TestVS.fxをShaderフォルダに新規作成して下記コードで VSOutputの拡散光(Diffuse) を 赤色[RGB = (1.0f,0,0f,0,0f)] にしてreturnさせます。

// 頂点シェーダーの入力 DrawPolygonIndexed3DToShader_UseVertexBuffer 用の頂点データの定義
// https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=5437
struct VS_INPUT
{   // [:POSITION0や:TEXCOORD0などシェーダーの変数定義の後ろにあるのはセマンティクス] https://qiita.com/sune2/items/fa5d50d9ea9bd48761b2
    float3 Position        : POSITION0 ;    // 座標( ローカル空間 )
    float4 SubPosition     : POSITION1 ;    // 予備座標
    float3 Normal          : NORMAL0 ;    // 法線( ローカル空間 )
    float3 Tangent         : TANGENT0 ;    // 接線( ローカル空間 )
    float3 Binormal        : BINORMAL0 ;    // 従法線( ローカル空間 )
    float4 Diffuse         : COLOR0 ;    // ディフューズカラー
    float4 Specular        : COLOR1 ;    // スペキュラカラー
    float2 TexCoords0      : TEXCOORD0 ;    // テクスチャ座標
    float2 TexCoords1      : TEXCOORD1 ;    // サブテクスチャ座標
} ;


// 頂点シェーダーの出力
struct VS_OUTPUT
{
    float4 Diffuse         : COLOR0 ;
    float4 Specular        : COLOR1 ;
    float2 TexCoords0      : TEXCOORD0 ;
    float4 Position        : SV_POSITION ;    // 座標( プロジェクション空間 )
} ;


// [DirectX11版]マテリアルパラメータ
struct DX_D3D11_CONST_MATERIAL
{
    float4        Diffuse ;        // ディフューズカラー
    float4        Specular ;        // スペキュラカラー
    float4        Ambient_Emissive ;    // マテリアルエミッシブカラー + マテリアルアンビエントカラー * グローバルアンビエントカラー

    float        Power ;            // スペキュラの強さ
    float        TypeParam0 ;        // マテリアルタイプパラメータ0
    float        TypeParam1 ;        // マテリアルタイプパラメータ1
    float        TypeParam2 ;        // マテリアルタイプパラメータ2
} ;

// [DirectX11版]フォグパラメータ
struct DX_D3D11_VS_CONST_FOG
{
    float        LinearAdd ;    // フォグ用パラメータ end / ( end - start )
    float        LinearDiv ;    // フォグ用パラメータ -1  / ( end - start )
    float        Density ;    // フォグ用パラメータ density
    float        E ;        // フォグ用パラメータ 自然対数の低

    float4        Color ;        // カラー
} ;

// [DirectX11版]ライトパラメータ
struct DX_D3D11_CONST_LIGHT
{
    int        Type ;        // ライトタイプ( DX_LIGHTTYPE_POINT など )
    int3        Padding1 ;    // パディング1↑int型4バイト+ 12バイト(int3 4バイト×3) = 16バイト飛びでGPUがデータを読み出せるようにパディングでstruct内のデータ構造を調整

    float3        Position ;    // 座標( ビュー空間 )
    float        RangePow2 ;    // 有効距離の2乗

    float3        Direction ;    // 方向( ビュー空間 )
    float        FallOff ;    // スポットライト用FallOff

    float3        Diffuse ;    // ディフューズカラー
    float        SpotParam0 ;    // スポットライト用パラメータ0( cos( Phi / 2.0f ) ) https://learn.microsoft.com/ja-jp/windows/uwp/graphics-concepts/light-types#spotlight

    float3        Specular ;    // スペキュラカラー
    float        SpotParam1 ;    // スポットライト用パラメータ1( 1.0f / ( cos( Theta / 2.0f ) - cos( Phi / 2.0f ) ) ) 上記↑リンク参考

    float4        Ambient ;    // アンビエントカラーとマテリアルのアンビエントカラーを乗算したもの

    float        Attenuation0 ;    // 距離による減衰処理用パラメータ0
    float        Attenuation1 ;    // 距離による減衰処理用パラメータ1
    float        Attenuation2 ;    // 距離による減衰処理用パラメータ2
    float        Padding2 ;    // パディング2 ↑float型4バイト×3 + float型のPadding2が4バイト = 合計16バイトのかたまりになるようにstructのデータ構造を調整
} ;

// [DirectX11版]ピクセルシェーダー・頂点シェーダー共通パラメータ
struct DX_D3D11_CONST_BUFFER_COMMON
{
    DX_D3D11_CONST_LIGHT        Light[ 6 ] ;    // ライトパラメータ [ 6 ]なので最大6つのライトまでしか使えない
    DX_D3D11_CONST_MATERIAL        Material ;    // マテリアルパラメータ
    DX_D3D11_VS_CONST_FOG        Fog ;        // フォグパラメータ
} ;

// [DirectX11版]基本パラメータ
struct DX_D3D11_VS_CONST_BUFFER_BASE
{
    float4        AntiViewportMatrix[ 4 ] ;    // アンチビューポート行列
    float4        ProjectionMatrix[ 4 ] ;        // ビュー → プロジェクション行列
    float4        ViewMatrix[ 3 ] ;        // ワールド → ビュー行列
    float4        LocalWorldMatrix[ 3 ] ;        // ローカル → ワールド行列

    float4        ToonOutLineSize ;        // トゥーンの輪郭線の大きさ
    float        DiffuseSource ;            // ディフューズカラー( 0.0f:マテリアル  1.0f:頂点 )
    float        SpecularSource ;        // スペキュラカラー(   0.0f:マテリアル  1.0f:頂点 )
    float        MulSpecularColor ;        // スペキュラカラー値に乗算する値( スペキュラ無効処理で使用 )
    float        Padding ; // ↑float型4バイト×3つが12バイト + Paddingがfloat型4バイト = 合計16バイトになるようにstructのデータ構造のバイト数を調整
} ;

// [DirectX11版]その他の行列
struct DX_D3D11_VS_CONST_BUFFER_OTHERMATRIX
{
    float4        ShadowMapLightViewProjectionMatrix[ 3 ][ 4 ] ;    // シャドウマップ用のライトビュー行列とライト射影行列を乗算したもの
    float4        TextureMatrix[ 3 ][ 2 ] ;            // テクスチャ座標操作用行列
} ;

// [DirectX11版]頂点シェーダー・ピクセルシェーダー共通パラメータ
cbuffer cbD3D11_CONST_BUFFER_COMMON : register( b0 ) // bの0番のレジスタがCPU側から定数を受け取る定数バッファ(cbuffer)に設定されている
{
    DX_D3D11_CONST_BUFFER_COMMON    g_Common ;
} ;

// [DirectX11版]基本パラメータ
cbuffer cbD3D11_CONST_BUFFER_VS_BASE    : register( b1 )
{
    DX_D3D11_VS_CONST_BUFFER_BASE    g_Base ;
} ;

// [DirectX11版]その他の行列
cbuffer cbD3D11_CONST_BUFFER_VS_OTHERMATRIX : register( b2 )
{
    DX_D3D11_VS_CONST_BUFFER_OTHERMATRIX    g_OtherMatrix ;
} ;


// main関数
VS_OUTPUT main( VS_INPUT VSInput )
{
    VS_OUTPUT VSOutput ;
    float4 lLocalPosition; // ローカル座標
    float4 lWorldPosition; // ワールド座標
    float4 lViewPosition; // 視線ビュー座標
   
    // 頂点座標変換 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 開始 )
    // ローカル座標のセット
    lLocalPosition.xyz = VSInput.Position;
    lLocalPosition.w = 1.0f;
    // ローカル座標をワールド座標に変換 [3D座標がスクリーンに投影されるための行列変換の過程] https://light11.hatenadiary.com/entry/2019/01/27/160541
    lWorldPosition.x = dot(lLocalPosition, g_Base.LocalWorldMatrix[0]);
    lWorldPosition.y = dot(lLocalPosition, g_Base.LocalWorldMatrix[1]);
    lWorldPosition.z = dot(lLocalPosition, g_Base.LocalWorldMatrix[2]);
    lWorldPosition.w = 1.0f;
    // ワールド座標をビュー座標に変換 [ワールド座標のカメラの位置が原点(0,0,0)になる行列変換] https://light11.hatenadiary.com/entry/2019/01/27/160541
    lViewPosition.x = dot(lWorldPosition, g_Base.ViewMatrix[0]);
    lViewPosition.y = dot(lWorldPosition, g_Base.ViewMatrix[1]);
    lViewPosition.z = dot(lWorldPosition, g_Base.ViewMatrix[2]);
    lViewPosition.w = 1.0f;
    // 視線ビュー座標をプロジェクション射影座標に変換 https://light11.hatenadiary.com/entry/2019/01/27/160541
    VSOutput.Position.x = dot(lViewPosition, g_Base.ProjectionMatrix[0]);
    VSOutput.Position.y = dot(lViewPosition, g_Base.ProjectionMatrix[1]);
    VSOutput.Position.z = dot(lViewPosition, g_Base.ProjectionMatrix[2]);
    VSOutput.Position.w = dot(lViewPosition, g_Base.ProjectionMatrix[3]);
    // 頂点座標変換 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 終了 )

    VSOutput.TexCoords0 = VSInput.TexCoords0; // テクスチャの座標をそのままOutputする
   
    // [実験]頂点カラーの拡散光を赤色にして返す
    VSOutput.Diffuse.r = 1.0f; // R(赤の拡散値)
    VSOutput.Diffuse.g = 0.0f; // G(緑の拡散値)
    VSOutput.Diffuse.b = 0.0f; // B(青の拡散値)

    // 出力パラメータを返す
    return VSOutput ;
}


TestVS.fxをDXのプロジェクトのShaderフォルダに保存したら、
①Windowsのバーからコマンドプロンプトを検索して起動して、
②cdコマンドを実行して、黒画面のコマンドプロンプト上でShaderフォルダに移動する(ウインドウズがない大昔のPC初期:MS-DOSは文字コマンドだけでフォルダ移動などをしていた時代だった)
③コマンドプロンプトでShaderCompiler.exe /Tvs5_0 TestVS.fxを実行して TestVS.fx をビルドして TestVS.vso を生成する。


同じ要領で今度はTestPS.fxを作成して、ピクセルシェーダー(Pixel:画面のドットの各ピクセルの Shader:シェーダ)の略 も ビルドしましょう。
[重要]ピクセルシェーダは別環境ではフラグメントシェーダと呼ばれます。(OpenGLのGLSL言語とか、Unityのシェーダ言語ではfragって言葉を見かけたらピクセルシェーダの処理だな、と心で読み替えましょう)

TestPS.fxをShaderフォルダに新規作成して下記コードで 頂点シェーダなどを経由してやってきたPSInputの拡散光(Diffuse) を そのままPSOutPutのColor0に設定してreturnさせましょう。

// ピクセルシェーダーの入力
struct PS_INPUT
{
    float4 Diffuse         : COLOR0 ;
    float4 Specular        : COLOR1 ;
    float2 TexCoords0      : TEXCOORD0 ;
} ;

// ピクセルシェーダーの出力
struct PS_OUTPUT
{
    float4 Color0          : SV_TARGET0 ;    // 色
} ;


// [DirectX11版]定数バッファピクセルシェーダー基本パラメータ
struct DX_D3D11_PS_CONST_BUFFER_BASE
{
    float4        FactorColor ;        // アルファ値等

    float        MulAlphaColor ;        // カラーにアルファ値を乗算するかどうか( 0.0f:乗算しない  1.0f:乗算する )
    float        AlphaTestRef ;        // アルファテストで使用する比較値
    float2        Padding1 ;

    int        AlphaTestCmpMode ;    // アルファテスト比較モード( DX_CMP_NEVER など )
    int3        Padding2 ;

    float4        IgnoreTextureColor ;    // テクスチャカラー無視処理用カラー
} ;

// [DirectX11版]基本パラメータ https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4904
cbuffer cbD3D11_CONST_BUFFER_PS_BASE    : register( b1 )
{
    DX_D3D11_PS_CONST_BUFFER_BASE    g_Base ;
} ;


SamplerState g_DiffuseMapSampler    : register( s0 ) ;    // ディフューズマップテクスチャのサンプラー
Texture2D    g_DiffuseMapTexture    : register( t0 ) ;    // ディフューズマップテクスチャ テクスチャはbではなくtレジスタ



// main関数
PS_OUTPUT main( PS_INPUT PSInput )
{
    PS_OUTPUT PSOutput ;

    // 頂点シェーダなどを経由してやってきたDiffuse拡散光の色をそのまま画面スクリーンのピクセル色としてOutputする
    PSOutput.Color0.r = PSInput.Diffuse.r ; // R:赤     画面スクリーン上のピクセル色
    PSOutput.Color0.g = PSInput.Diffuse.g ; // G:緑     画面スクリーン上のピクセル色
    PSOutput.Color0.b = PSInput.Diffuse.b ; // B:青     画面スクリーン上のピクセル色
    PSOutput.Color0.a = PSInput.Diffuse.a ; // A:透明度 画面スクリーン上のピクセル色

    // 出力パラメータを返す
    return PSOutput ;
}


TestPS.fxをDXのプロジェクトのShaderフォルダに保存したら、
①Windowsのバーからコマンドプロンプトを検索して起動して、
②cdコマンドを実行して、黒画面のコマンドプロンプト上でShaderフォルダに移動
③コマンドプロンプトでShaderCompiler.exe /Tps5_0 TestPS.fxを実行して TestPS.fx をビルドして TestPS.pso を生成する。



頂点シェーダから出た頂点はラスタライザで内側にあるピクセルがもとめられ、ピクセルシェーダはその内側のピクセルの数ぶんだけ実行されます。
頂点の3点以外の拡散光の強さDiffuse値は3点の間にあるピクセルでは適度にグラデーションで補われてやってくるので、
厳密には TestVSの出力VSOutPutのDiffuse値 ≒ TestPSの入力PSOutPutのDiffuse値 ではありますが、
VSのOutputの影響を受けたPSInputのDiffuse値が渡ってくるというイメージでいいと思います。


DataPly.hを変更して頂点Vertexのタイプ型をVERTEX3DからVERTEX3DSHADERに変えて今回作成したシェーダーの描画に対応できるようにしましょう。

#ifndef DATAPLY_H_
#define DATAPLY_H_

#include "Dxlib.h"

#include <memory>
#include <vector>
#include <string>

#include <assert.h> // 読込み失敗表示用

#include "Ply.h"

#include "Resource.h"

// .ply形式の3Dモデルデータを読み込む構造体
struct DataPly : public Resource
{
protected: // ファクトリーパターンでコンストラクタ経由の生成経路をprotected隠蔽(ResourceクラスのMakeShared関数経由でしか新規生成は できない)
    DataPly(std::string filePath = "", bool isGPUBufferRendering = false)
        : Resource(filePath, Type::Ply), isGPUBufferRendering{ isGPUBufferRendering } {}
public:
    size_t numIndex = 0;
    int VertexBufHandle{ -1 }; // GPU側の頂点バッファへのハンドル番号
    int IndexBufHandle{ -1 }; // GPU側のインデックスバッファへのハンドル番号
    bool isSendDataToGPU = false; // GPUへデータを転送済みか
    bool isGPUBufferRendering; // GPUバッファにVertexとIndexデータを送ってCPU側にはデータをもたないようにするか?
    std::vector<uint32_t> Index; // 3Dモデルのポリゴンのインデックス配列
    std::vector<VERTEX3DSHADER> Vertex; // 3Dモデルのポリゴンの頂点配列

    Ply::Data Data; //.plyのデータ

    bool isInitialized = false; //[継承2重ロード対策]ロード済みになったらtrueに falseのときにLoadするとリロード

    virtual ~DataPly()
    {// 仮想デストラクタ
        clear();// 2次元配列データのお掃除
    };

    virtual void clearCPUData() // CPU上の配列データをクリアする
    {   // [確実にメモリを空にするには] http://vivi.dyndns.org/tech/cpp/vector.html#shrink_to_fit
        std::vector<VERTEX3DSHADER>().swap(Vertex); // 空のテンポラリオブジェクトでリセット
        std::vector<unsigned int>().swap(Index); // 空のテンポラリオブジェクトでリセット
    }

    virtual void clearGPUData() // GPU上のバッファデータをクリアする
    {
        DeleteVertexBuffer(VertexBufHandle); // GPUの頂点バッファを削除する
        DeleteIndexBuffer(IndexBufHandle); // GPUのインデックスバッファを削除する
        VertexBufHandle = -1; IndexBufHandle = -1;
        isSendDataToGPU = false;
        isGPUBufferRendering = false;
    }

    // データをクリアしてメモリを節約する
    virtual void clear()
    {
        Data.clear();
        clearCPUData(); // CPU上の配列データをクリアする
        clearGPUData(); // GPUのインデックスバッファを削除する
        isInitialized = false; //ロード済みフラグをOFF
    }

    // ファイルの読み込み
    virtual int Load(const std::string& filePath = "") override
    {
        if (filePath == "" && m_FilePath == "") return -1; //ファイル名がない
        else if (files.count(filePath) > 0 && isInitialized) return 0; // すでに辞書にロード済みファイルがあった
        else if (filePath != "" && m_FilePath == "") m_FilePath = filePath; // 読み出しファイルパスを記録
        clear(); //データを一旦クリア

        Ply::load(m_FilePath, Data); // .plyデータをロード

        auto vertex = Data["vertex"]; // 頂点へのアクセス
        assert(vertex->size() != 0 && "Plyデータの頂点サイズが0です。ファイルのパスは大丈夫ですか?");
        Vertex.resize(vertex->size()); // 配列の数の事前確保

        // [std::shared_ptrへthis↓ポインタをつかうためには] https://www.kabuku.co.jp/developers/cpp_enable_shared_from_this
        files.emplace(m_FilePath, shared_from_this()); // リソースの辞書へ登録 https://cpprefjp.github.io/reference/memory/enable_shared_from_this.html

        // [.plyの頂点のプロパティ情報]https://jp.mathworks.com/help/vision/ug/the-ply- format.html
        const float* p_x = vertex->properties["x"]->ptr<float>();
        const float* p_y = vertex->properties["y"]->ptr<float>();
        const float* p_z = vertex->properties["z"]->ptr<float>();
        const float* p_nx = (vertex->properties.has_key("nx")) ? vertex->properties["nx"]->ptr<float>() : nullptr;
        const float* p_ny = (vertex->properties.has_key("ny")) ? vertex->properties["ny"]->ptr<float>() : nullptr;
        const float* p_nz = (vertex->properties.has_key("nz")) ? vertex->properties["nz"]->ptr<float>() : nullptr;
        bool has_norm = !(p_nx == nullptr || p_ny == nullptr || p_nz == nullptr);
        const float* p_s = (vertex->properties.has_key("s")) ? vertex->properties["s"]->ptr<float>() : nullptr; // Blender書き出しでは(u,v)ではなく(s,t)として書き出されている
        const float* p_t = (vertex->properties.has_key("t")) ? vertex->properties["t"]->ptr<float>() : nullptr;

        const unsigned char* p_R = (vertex->properties.has_key("red")) ? vertex->properties["red"]->ptr<unsigned char>()
            : (vertex->properties.has_key("r")) ? vertex->properties["r"]->ptr<unsigned char>() : nullptr;
        const unsigned char* p_G = (vertex->properties.has_key("green")) ? vertex->properties["green"]->ptr<unsigned char>()
            : (vertex->properties.has_key("g")) ? vertex->properties["g"]->ptr<unsigned char>() : nullptr;
        const unsigned char* p_B = (vertex->properties.has_key("blue")) ? vertex->properties["blue"]->ptr<unsigned char>()
            : (vertex->properties.has_key("b")) ? vertex->properties["b"]->ptr<unsigned char>() : nullptr;
        const unsigned char* p_A = (vertex->properties.has_key("alpha")) ? vertex->properties["alpha"]->ptr<unsigned char>()
            : (vertex->properties.has_key("a")) ? vertex->properties["a"]->ptr<unsigned char>() : nullptr;
        bool has_color = !(p_R == nullptr || p_G == nullptr || p_B == nullptr || p_A == nullptr);

        for (size_t i = 0, iEnd = Vertex.size(); i < iEnd; i += 1)
        {
            // 頂点のデータをセット 各ツールの系一覧 https://zenn.dev/it_ks/articles/cbe27860548ea1
            //Vertex[i].pos = VGet(p_x[i], p_y[i], p_z[i]); // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
            //Vertex[i].pos = VGet(p_x[i], -p_y[i], p_z[i]); // 右手Y↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
            //Vertex[i].norm = (has_norm) ? VNorm(VGet(p_nx[i], -p_ny[i], p_nz[i])) : VGet(0.0f, 0.0f, 0.0f);
           
            // 右手Z↑→左手Y↑系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
            Vertex[i].pos = VGet(-(p_y[i]), p_z[i], p_x[i]);
            Vertex[i].norm = (has_norm) ? VNorm(VGet(-p_ny[i], p_nz[i], p_nx[i])) : VGet(0.0f, 0.0f, 0.0f);
            Vertex[i].dif = (has_color) ? GetColorU8(p_R[i], p_G[i], p_B[i], p_A[i]) : GetColorU8(255, 255, 255, 255);
            Vertex[i].spc = GetColorU8(255, 255, 255, 255);
            Vertex[i].u = (p_s == nullptr) ? 0.0f : p_s[i];
            Vertex[i].v = (p_t == nullptr) ? 0.0f : 1 - p_t[i];
            Vertex[i].su = -1.0f;
            Vertex[i].sv = 0.0f;
        }

        const auto& face_index = Data["face"]->properties["vertex_indices"];
        const float* face_uv = (Data["face"]->properties.has_key("texcoord")) ? Data["face"]->properties["texcoord"]->ptr<float>() : nullptr;
        Index.resize(face_index->size() * 3);

        for (size_t i = 0, iEnd = face_index->size(), nFace = 0; i < iEnd; i += 3)// i + face_index->listCounts[nFace], ++nFace)
        {
            //size_t count = face_index->listCounts[nFace]; // ポリゴンならインデックス数は3 四角形ならインデックス数は4 .. 5 .. 6
            //size_t nPolygon = count - 2; // 三角ポリゴンの数 = インデックスの数 - 2 (例) 3 - 2 = 1ポリゴン , 4 - 2 = 2ポリゴン ..
            //for (size_t n = 0; n < nPolygon; ++n)
            {
                //for (size_t j = 0; j < 3; ++j) // 右手系(裏面は時計回り) https://yttm-work.jp/gmpg/gmpg_0014.html
                for (size_t j = 2; j + 1 > 0; --j) // 左手系(裏面は反時計回り) http://www.ohshiro.tuis.ac.jp/~ohshiro/gamesoft/coordinate/index.html
                {
                    unsigned int index = face_index->at<unsigned int>(i + j);
                    const size_t uvIndex = 2 * (i + j); // uとv 2つ * ( i + j )
                    if (Vertex[index].su != -1.0f) // su sv の suが初期値-1でなく設定済なら頂点の被りとして新しいnewIndexとして新規Index番号で点を新規登録
                    {
                        const unsigned int newIndex = Vertex.size();
                        Vertex.emplace_back(Vertex[index]);
                        index = newIndex;
                        Index.push_back(newIndex);
                    }
                    Vertex[index].su = 0.0f;

                    //Index[i + j] = index;   // 右手系(裏面は時計回り)
                    Index[i + 2 - j] = index; // 左手系(裏面は反時計回り)
                    if (face_uv != nullptr)
                    {
                        Vertex[index].u = face_uv[uvIndex];
                        Vertex[index].v = 1.0f - face_uv[uvIndex + 1];
                    }

                    // 三角形の2辺のベクトルの外積から法線を計算(法線がないとライトの光の反射方向をシェーディングできないから)
                    if (j == 0 && !has_norm && Vertex[index].norm.x == 0.0f && Vertex[index].norm.y == 0.0f && Vertex[index].norm.z == 0.0f)
                    {
                        const unsigned int index0 = Index[i];
                        const unsigned int index1 = Index[i + 1];
                        const unsigned int index2 = Index[i + 2];

                        // 外積 a×b cross_a_b を求める https://examist.jp/mathematics/space-vector/vector-gaiseki/
                        const VECTOR a = VSub(Vertex[index1].pos, Vertex[index0].pos);
                        const VECTOR b = VSub(Vertex[index2].pos, Vertex[index0].pos);
                        // 左手系(表面は時計回り) 右手系と左手系では裏面の時計回りと反時計回りが変わる
                        const VECTOR cross1_2 = VCross(a, b);
                        // 右手系(表面は反時計回り)
                        //const VECTOR cross2_1 = VCross(b, a);

                        // 単位ベクトル(ノルム)に直す [単位ベクトル]は√(x*x + y*y + z*z) = 1となる
                        const VECTOR norm1_2 = VNorm(cross1_2);
                        //const VECTOR norm2_1 = VNorm(cross2_1);
                        Vertex[index0].norm = norm1_2; Vertex[index1].norm = norm1_2; Vertex[index2].norm = norm1_2;
                        //Vertex[index0].norm = norm2_1; Vertex[index1].norm = norm2_1; Vertex[index2].norm = norm2_1;
                    }

                    ++numIndex;
                }
            }
        }
        if (isGPUBufferRendering)
            SendDataToGPU(); // GPUのバッファへVertexとIndexデータ配列を送信

        isInitialized = true;

        return 0;
    }

    void SendDataToGPU() // GPUのバッファへVertexとIndexデータ配列を送信
    {
        int result = false;
        VertexBufHandle = DxLib::CreateVertexBuffer(Vertex.size(), DX_VERTEX_TYPE_SHADER_3D); // 頂点バッファを作成
        result = DxLib::SetVertexBufferData(0, Vertex.data(), Vertex.size(), VertexBufHandle); // 頂点バッファに頂点データを転送する
        //UpdateVertexBuffer(VertexBufHandle, UpdateStartIndex, UpdateVertexNum); // 頂点バッファハンドルの頂点バッファへの変更を適用する( GetBufferVertexBuffer で取得したバッファへの変更を反映する )
        if (result == -1) return;
       
        IndexBufHandle = DxLib::CreateIndexBuffer(Index.size(), DX_INDEX_TYPE_32BIT); // インデックスバッファを作成
        result = DxLib::SetIndexBufferData(0, Index.data(), Index.size(), IndexBufHandle); // インデックスバッファにインデックスデータを転送する
        if (result == -1) return;

        isGPUBufferRendering = true;
        isSendDataToGPU = true;
    }
};

#endif


一応、VERTEX3D型 と VERTEX3DSHADER型の定義の違いを見ておきましょう。(DxLib.hの中に定義がある)

DxLib.hの中身に定義がある
(以上、略)...
// 3D描画に使用する頂点データ型
typedef struct tagVERTEX3D
{
    VECTOR                    pos ;                            // 座標
    VECTOR                    norm ;                            // 法線
    COLOR_U8                dif ;                            // ディフューズカラー
    COLOR_U8                spc ;                            // スペキュラカラー
    float                    u, v ;                            // テクスチャ座標
    float                    su, sv ;                        // 補助テクスチャ座標
} VERTEX3D, *LPVERTEX3D ;

// 3D描画に使用する頂点データ型( DrawPrimitive3DToShader用 )
// 注意…メンバ変数に追加があるかもしれませんので、宣言時の初期化( VERTEX3DSHADER Vertex = { 0.0f, 0.0f, ... というようなもの )はしない方が良いです
typedef struct tagVERTEX3DSHADER
{
    VECTOR                    pos ;                            // 座標
    FLOAT4                    spos ;                            // 補助座標
    VECTOR                    norm ;                            // 法線
    VECTOR                    tan ;                            // 接線
    VECTOR                    binorm ;                        // 従法線
    COLOR_U8                dif ;                            // ディフューズカラー
    COLOR_U8                spc ;                            // スペキュラカラー
    float                    u, v ;                            // テクスチャ座標
    float                    su, sv ;                        // 補助テクスチャ座標
} VERTEX3DSHADER, *LPVERTEX3DSHADER ;
(以下、略)....

GPUに渡されるデータについては
DX公式サイトにも説明がある
コメントに書いてあるように、中身のメンバ変数は変わる可能性がある、
つまりゲームの3D表現ごとに描く際に必要な頂点に埋め込んでおきたいデータは変わってくることもありうるわけなので、
3D保存データの保存データ構造 と 頂点データの定義 と シェーダのプログラム と GPU上の処理 はセットで考えられる必要があるということ。
ゆえに、ゲームごとにエンジンがカスタマイズされる際に、3Dモデルのデータの書き出しスクリプトを書く人や、
データ構造自体からカスタマイズしてシェーダのプログラムをできる人..などが、必要とされる人材となるわけです。
そのためには最低限、ポリゴンからシェーダーを経由して、画面にドットが映し出されるまでの過程をイメージできるだけのセンスが重要です。
今なぜわざわざシェーダやポリゴンデータを直接いじっているかというと、3Dの点がどういう風にシェーダのコードに渡って、画面にピクセルのドットとして描かれるかを体感して入門するためです。

では、シェーダをmain.cppでロードして、画面に赤いサイコロを描いてみましょう。

main.cppのコードを変更して自作シェーダーをGPU上で走らせて、サイコロを赤色で塗りつぶして描いてみましょう。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include "Input.h"
#include "Screen.h"
#include "Ply.h"
#include "DataMV1.h"
#include "DataPly.h"
#include "Collision3D.h"
#include "MyDraw.h" // 自作の描画処理
#include "Quaternion.h"

#include "Editor.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズWidth×Heightのカラービット数32ビットで起動
    SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    SetUseDirect3DVersion(DX_DIRECT3D_11); // 最新のDXライブラリはデフォルトでDirectX11版のシェーダー言語に対応しているが、念のため11に事前設定

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    SetDrawScreen(DX_SCREEN_BACK);
  
    // ゲームのwhileループを開始する前の初期化処理
    float animTime = 0.0f; // アニメの現在時刻
    float animSpeed = 2.0f; // MV1のアニメの再生速度設定 2.0fなら2倍速
    float animStepTime = 1.0f; // MV1のアニメの毎時間の進む時間の設定
    std::shared_ptr<DataMV1> pDevilData = Resource::MakeShared<DataMV1>("Image/devil@kick.mv1");
    pDevilData->Load(); // 3Dモデルをmv1形式で読込み
    std::shared_ptr<DataMV1> pDevilAttackAnimData = Resource::MakeShared<DataMV1>("Image/devil@jump.mv1");
    pDevilAttackAnimData->Load(); // 3Dの追加アニメデータをmv1形式で読込み
    pDevilData->AttachAnim(*pDevilAttackAnimData); // アニメを追加でflyのほうをベースとしたモデルにアタッチ
    int animeButton = 0; // ボタンでアニメを切り替えられるようにする

    std::shared_ptr<Texture> pTexImage = Resource::MakeShared<Texture>("Image/dice.png");
    pTexImage->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
    assert((int)*pTexImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる

    std::shared_ptr<DataPly> pPlyData = Resource::MakeShared<DataPly>("Image/dice.ply");
    pPlyData->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
   
    int VertexShaderHandle = DxLib::LoadVertexShader("Shader/TestVS.vso"); // 頂点シェーダーを読み込む
    int PixelShaderHandle  = DxLib::LoadPixelShader("Shader/TestPS.pso");  // ピクセルシェーダーを読み込む

   
    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 0.0f; keyControlXYZ.y = 0.0f; keyControlXYZ.z = 0;
    Quaternion quaternion; // 回転クオータニオン

    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen1 = SubScreen::Create()->Set(600, 450, 600, 100, 255, 0, "サブスクリーン1")->SetIsBarFrameShow(true,true);
   

    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    Editor& editor = Editor::GetInstance(); // エディタのシングルトン参照を得る

    std::list<std::weak_ptr<DragPoint>> dragPoints; // ドラッグ対象の点の共有ポインタのリスト

    //奥行0.1~5000までをカメラの描画範囲とする
    DxLib::SetCameraNearFar(0.1f, 5000.0f);

    ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Input::Update(); // マウスのドラッグなどを更新

        editor.UpdateKeyInput(); // エディタのキーのインプットを更新

        editor.UpdateDragPoints(&dragPoints); // ドラッグ点のリストを更新

        if (!editor.isDraggingPoints) editor.UpdateMouseInput(); // エディタのマウスのインプットを更新
        editor.UpdateCamera(); // エディタのカメラを更新
       
        // キーボードの 0 1 キーで3Dアニメを切り替えられるようにする
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_0))
            animeButton = 0;
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_1))
            animeButton = 1;

        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        VECTOR position = VGet(keyControlXYZ.x, keyControlXYZ.y, keyControlXYZ.z); // 3Dモデルの位置

        float width =  pTexImage->m_XSize; float height =  pTexImage->m_YSize; // テクスチャ画像のサイズ
        VECTOR scale = VGet(1.0f, 1.0f, 1.0f); // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)とcキー(C)でx,y,z軸まわりに画像を回転
        if (PadInput & PAD_INPUT_A) quaternion *= CreateRotationQuaternion(speed * DX_PI / 180, VGet(1, 0, 0)); // X軸(1,0,0)を軸にspeed度ぶんだけ回転
        if (PadInput & PAD_INPUT_B) quaternion *= CreateRotationQuaternion(speed * DX_PI / 180, VGet(0, 1, 0)); // Y軸(0,1,0)を軸にspeed度ぶんだけ回転
        if (PadInput & PAD_INPUT_C) quaternion *= CreateRotationQuaternion(speed * DX_PI / 180, VGet(0, 0, 1)); // Z軸(0,0,1)を軸にspeed度ぶんだけ回転

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数

        DxLib::SetUseVertexShader(VertexShaderHandle); // 使用する頂点シェーダーをセット
        DxLib::SetUsePixelShader(PixelShaderHandle); // 使用するピクセルシェーダーをセット
        DxLib::SetUseTextureToShader(0, *pTexImage); // 使用するテクスチャを0番にバインドする[ register( t0 ) ]


        MATRIX  matWorldDefault; // デフォルトのワールド行列
        DxLib::GetTransformToWorldMatrix(&matWorldDefault); // デフォルトのワールド行列を記憶しておく
        {
            //[ワールド行列] https://ny-program.hatenablog.com/entry/2019/09/15/114245
            MATRIX mat; // モデルのスケール×回転×移動を行列計算で設定する

            //[スケール]拡大縮小のスケール行列を得る
            mat = MGetScale(scale); // まずは拡大縮小のスケール行列からはじめる
            // [勉強]↓スケール行列は 斜めに x , y , zのスケールの倍率がならんでいる行列
            //mat.m[0][0] = scale.x;  mat.m[0][1] = 0.0f;    mat.m[0][2] = 0.0f;     mat.m[0][3] = 0.0f;
            //mat.m[1][0] = 0.0f;     mat.m[1][1] = scale.y; mat.m[1][2] = 0.0f;     mat.m[1][3] = 0.0f;
            //mat.m[2][0] = 0.0f;     mat.m[2][1] = 0.0f;    mat.m[2][2] = scale.z;  mat.m[2][3] = 0.0f;
            //mat.m[3][0] = 0.0f;     mat.m[3][1] = 0.0f;    mat.m[3][2] = 0.0f;     mat.m[3][3] = 1.0f;

            //[回転]
            MATRIX matRot;
            DxLib::CreateIdentityMatrix(&matRot);
            // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
            mat = MMult(mat, QuaternionToMatrix(quaternion)); // スケール × 回転

            //[移動]
            MATRIX matTrans = MGetTranslate(VGet(position.x, position.y, position.z)); // 位置positionをここで設定
            mat = MMult(mat, matTrans); //スケール×回転×移動

            DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4341

            DxLib::DrawPolygon32bitIndexed3DToShader(pPlyData->Vertex.data(), pPlyData->Vertex.size(), pPlyData->Index.data(), pPlyData->numIndex / 3, (int)*pTexImage, FALSE); // 三角形ポリゴンをたくさん描画
        }
        DxLib::SetTransformToWorld(&matWorldDefault); // デフォルトのワールド行列に戻しておく

        DxLib::SetUseVertexShader(-1); // -1で毎度デフォルトのシェーダに戻しておけば他の3Dモデルの描画には影響を与えない
        DxLib::SetUsePixelShader(-1); // -1で毎度デフォルトのシェーダに戻しておけば他の3Dモデルの描画には影響を与えない

       
        DxLib::MV1DrawModel(devil3DModel); // 3Dモデル(.mv1形式)を描く

        auto xyzAngle = QuaternionToAngle(quaternion); // クオータニオンからそれに対応するオイラー角度の候補を得る(候補は最大3種ありうるし、ある軸が傾いていて0度じゃない場合他の軸も連動)
        DxLib::DrawFormatString(0, 100, GetColor(255, 255, 0), "x:%f y:%f Angle:%d %d %d", position.x, position.y, (int)(xyzAngle[0].x * 180 / DX_PI), (int)(xyzAngle[0].y * 180 / DX_PI), (int)(xyzAngle[0].z * 180 / DX_PI));

        // ファイルのパス + ":" + アニメ名でアクセス
        std::string animPathName = (animeButton == 0) ? "Image/devil@kick.mv1:mixamo.com" : "Image/devil@jump.mv1:mixamo.com";
        assert(pDevilData->count(animPathName) > 0 && "指定されたアニメ名のアニメはMV1に付属していませんでした");
        animTime += animStepTime * animSpeed; // 1.0×2倍 なら2倍速でアニメ時刻が進む
        if (animTime > (*pDevilData)[animPathName].endTime)
            animTime = 0.0f; // 0秒目へとアニメをループさせる
       
        // 再生時間をセットする
        DxLib::MV1SetAttachAnimTime((int)*pDevilData, (*pDevilData)[animPathName].index, animTime);

       

        editor.Draw(); // エディタのDraw処理

        ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    WaitKey();
    // DXライブラリの後始末
    DxLib_End();
    // ソフトの終了
    return 0;
}


いかがでしょうか?サイコロが赤で塗りつぶして描かれたでしょうか?
黒かったり白かったりする場合は、シェーダー言語のビルドに失敗しているか、シェーダーのフォルダが間違っている可能性が高いかと思うので見直してみてください。



次に実験として、頂点シェーダで頂点のx位置が 0.0f以上か以下か で赤か青かに描き分ける実験をしてみよう。

TestVS.fxを変更して頂点のx位置が0.0f以上か以下かで 頂点カラーを赤か青かに描き分けます。




(前略).........

// main関数
VS_OUTPUT main( VS_INPUT VSInput )
{
    VS_OUTPUT VSOutput ;
    float4 lLocalPosition; // ローカル座標
    float4 lWorldPosition; // ワールド座標
    float4 lViewPosition; // 視線ビュー座標
   
    // 頂点座標変換 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 開始 )
    // ローカル座標のセット
    lLocalPosition.xyz = VSInput.Position;
    lLocalPosition.w = 1.0f;
    // ローカル座標をワールド座標に変換 [3D座標がスクリーンに投影されるための行列変換の過程] https://light11.hatenadiary.com/entry/2019/01/27/160541
    lWorldPosition.x = dot(lLocalPosition, g_Base.LocalWorldMatrix[0]);
    lWorldPosition.y = dot(lLocalPosition, g_Base.LocalWorldMatrix[1]);
    lWorldPosition.z = dot(lLocalPosition, g_Base.LocalWorldMatrix[2]);
    lWorldPosition.w = 1.0f;
    // ワールド座標をビュー座標に変換 [ワールド座標のカメラの位置が原点(0,0,0)になる行列変換] https://light11.hatenadiary.com/entry/2019/01/27/160541
    lViewPosition.x = dot(lWorldPosition, g_Base.ViewMatrix[0]);
    lViewPosition.y = dot(lWorldPosition, g_Base.ViewMatrix[1]);
    lViewPosition.z = dot(lWorldPosition, g_Base.ViewMatrix[2]);
    lViewPosition.w = 1.0f;
    // 視線ビュー座標をプロジェクション射影座標に変換 https://light11.hatenadiary.com/entry/2019/01/27/160541
    VSOutput.Position.x = dot(lViewPosition, g_Base.ProjectionMatrix[0]);
    VSOutput.Position.y = dot(lViewPosition, g_Base.ProjectionMatrix[1]);
    VSOutput.Position.z = dot(lViewPosition, g_Base.ProjectionMatrix[2]);
    VSOutput.Position.w = dot(lViewPosition, g_Base.ProjectionMatrix[3]);
    // 頂点座標変換 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 終了 )

    VSOutput.TexCoords0 = VSInput.TexCoords0; // テクスチャの座標をそのままOutputする
   
    // [実験]頂点カラーの拡散光をxの位置が0.0f以上か以下かで青か赤かに描き分ける処理を書いてみる
    VSOutput.Diffuse.r = (VSOutput.Position.x > 0.0f) ? 1.0f : 0.0f; // R(赤の拡散値)
    VSOutput.Diffuse.g = 0.0f; // G(緑の拡散値)
    VSOutput.Diffuse.b = (VSOutput.Position.x > 0.0f) ? 0.0f : 1.0f; // B(青の拡散値)

    // 出力パラメータを返す
    return VSOutput ;
}


TestVS.fxを変更したら保存して、コマンドプロンプトから再びShaderCompiler.exeのコマンドでビルドしなおします。(キーボードの上キーを押すと過去に実行したコマンドが出てきて楽)
いかがでしょう?実行したら、サイコロが真ん中より右方向(x+方向:赤)か左方向(x-方向:青)かで描き分けられているでしょうか?
このように頂点シェーダを書き換えれば、3Dの点が1万個あったとしても、頂点すべてに対してfor文を1万回まわすことなくGPUで処理を行わせることができます。
3Dを描く際に、GPUにどのように計算の処理をさせているのかの実感が少し湧いたでしょうか?

うーん、いまいちゲームに使う実感が湧かない?例えば、xなど位置に対応して色を描き分けられるということは、平面に模様をGPUで描かせることへ発展できるということです(←x,yから セルラーノイズ=x,yに対応した乱数 で海中の模様を描けている)。


では、次の実験として、今度はTestPS.fxつまりピクセルシェーダーの方を変更して、サイコロのテクスチャの色を今作った頂点シェーダーからの色に掛け算してみましょう。

TestPS.fxを変更して下記コードで 頂点シェーダからやってくるのPSInputの拡散光(Diffuse) に テクスチャの模様の色を掛け算して合成してみましょう。

// ピクセルシェーダーの入力
struct PS_INPUT
{
    float4 Diffuse         : COLOR0 ;
    float4 Specular        : COLOR1 ;
    float2 TexCoords0      : TEXCOORD0 ;
} ;

// ピクセルシェーダーの出力
struct PS_OUTPUT
{
    float4 Color0          : SV_TARGET0 ;    // 色
} ;


// [DirectX11版]定数バッファピクセルシェーダー基本パラメータ
struct DX_D3D11_PS_CONST_BUFFER_BASE
{
    float4        FactorColor ;        // アルファ値等

    float        MulAlphaColor ;        // カラーにアルファ値を乗算するかどうか( 0.0f:乗算しない  1.0f:乗算する )
    float        AlphaTestRef ;        // アルファテストで使用する比較値
    float2        Padding1 ;

    int        AlphaTestCmpMode ;    // アルファテスト比較モード( DX_CMP_NEVER など )
    int3        Padding2 ;

    float4        IgnoreTextureColor ;    // テクスチャカラー無視処理用カラー
} ;

// [DirectX11版]基本パラメータ https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4904
cbuffer cbD3D11_CONST_BUFFER_PS_BASE    : register( b1 )
{
    DX_D3D11_PS_CONST_BUFFER_BASE    g_Base ;
} ;


SamplerState g_DiffuseMapSampler    : register( s0 ) ;    // ディフューズマップテクスチャ
Texture2D    g_DiffuseMapTexture    : register( t0 ) ;    // ディフューズマップテクスチャ




// main関数
PS_OUTPUT main( PS_INPUT PSInput )
{
    PS_OUTPUT PSOutput ;
    float4 TextureDiffuseColor ;

    // テクスチャ画像の色をサンプラーで[サンプリング採集 して取り出す]
    TextureDiffuseColor = g_DiffuseMapTexture.Sample(g_DiffuseMapSampler, PSInput.TexCoords0);


    // 頂点シェーダなどを経由してやってきたDiffuse拡散光の色をそのまま画面スクリーンのピクセル色としてOutputする
    PSOutput.Color0.r = PSInput.Diffuse.r ; // R:赤     画面スクリーン上のピクセル色
    PSOutput.Color0.g = PSInput.Diffuse.g ; // G:緑     画面スクリーン上のピクセル色
    PSOutput.Color0.b = PSInput.Diffuse.b ; // B:青     画面スクリーン上のピクセル色
    PSOutput.Color0.a = PSInput.Diffuse.a ; // A:透明度 画面スクリーン上のピクセル色


    // [実験]出力する色はディフューズカラーをテクスチャの色にかけ算したもの
    PSOutput.Color0 = PSInput.Diffuse * TextureDiffuseColor;


    // 出力パラメータを返す
    return PSOutput ;
}


いかがでしょう?サイコロの模様と先ほどの頂点シェーダーで描き分けた赤と青が合成されて表示されたでしょうか?
このように、かけ算によって 色の数値 と テクスチャの模様 を GPUで各ドット(ピクセル)で、かけ算することもできるわけです。
こういった視点をもった上で、普段プレイしているゲームをプレイすると、もしかしたらこの床の模様、このアイテムの点滅や光りかた、シェーダでやってるのでは?という視点の入り口に立つことができます。


ディレクショナルライト、ポイントライト、スポットライトを生成してシェーダと連携させる

シェーダの色付けにはそれを映し出す源となる光源の存在が重要なので、次は光源となるディレクショナルライト、ポイントライト、スポットライトを生成してみましょう。

main.cppのコードを変更して光源を生成して、キーボードのLボタンでコントロールするライトを切り替えて、上下左右PageUp PageDownキーで動かせるようにしてみましょう。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include "Input.h"
#include "Screen.h"
#include "Ply.h"
#include "DataMV1.h"
#include "DataPly.h"
#include "Collision3D.h"
#include "MyDraw.h" // 自作の描画処理
#include "Quaternion.h"

#include "Editor.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズWidth×Heightのカラービット数32ビットで起動
    SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    SetUseDirect3DVersion(DX_DIRECT3D_11); // 最新のDXライブラリはデフォルトでDirectX11版のシェーダー言語に対応しているが、念のため11に事前設定

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    SetDrawScreen(DX_SCREEN_BACK);
  
    // ゲームのwhileループを開始する前の初期化処理
    float animTime = 0.0f; // アニメの現在時刻
    float animSpeed = 2.0f; // MV1のアニメの再生速度設定 2.0fなら2倍速
    float animStepTime = 1.0f; // MV1のアニメの毎時間の進む時間の設定
    std::shared_ptr<DataMV1> pDevilData = Resource::MakeShared<DataMV1>("Image/devil@kick.mv1");
    pDevilData->Load(); // 3Dモデルをmv1形式で読込み
    std::shared_ptr<DataMV1> pDevilAttackAnimData = Resource::MakeShared<DataMV1>("Image/devil@jump.mv1");
    pDevilAttackAnimData->Load(); // 3Dの追加アニメデータをmv1形式で読込み
    pDevilData->AttachAnim(*pDevilAttackAnimData); // アニメを追加でflyのほうをベースとしたモデルにアタッチ
    int animeButton = 0; // ボタンでアニメを切り替えられるようにする

    std::shared_ptr<Texture> pTexImage = Resource::MakeShared<Texture>("Image/dice.png");
    pTexImage->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
    assert((int)*pTexImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる

    std::shared_ptr<DataPly> pPlyData = Resource::MakeShared<DataPly>("Image/dice.ply");
    pPlyData->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
   
    int VertexShaderHandle = DxLib::LoadVertexShader("Shader/TestVS.vso"); // 頂点シェーダーを読み込む
    int PixelShaderHandle  = DxLib::LoadPixelShader("Shader/TestPS.pso");  // ピクセルシェーダーを読み込む
   
    int DirLightHandle; // ディレクショナルライトのハンドル番号
    int SpotLightHandle; // スポットライトのハンドル番号
    int PointLightHandle; // ポイントライトのハンドル番号
    int controlLight = -1; // ユーザーのコントロール中のライト
    DxLib::SetUseLighting(TRUE); // 光の明暗計算を有効に 同時に有効にできるのは標準ライトを含めて3~6つ(シェーダーのVerやLight[]配列の数の定義による)
    DxLib::SetLightEnable(FALSE); // デフォルトで3D空間を照らしていた標準ライトは無効にする(無効にしないとg_Common.Light[ 0 ]に標準のデフォルトライトが割り込んじゃう)

    // ※※ライトの初期化順※※※
    // ★[重要] CreateLight系関数を呼んだ順にシェーダー側のg_Common.Light[ 0 ] g_Common.Light[ 1 ] g_Common.Light[ 2 ]にライトが対応するので
    // シェーダーのコードとの対応関係を考えた上でディレクショナルライト、スポットライト、ポイントライトの初期化の順番を合わせないといけない!!!
    //[DXでは必ずディレクショナルライト→スポットライト→ポイントライトの順に初期化しないといけない] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=5605


    // [標準ライトのデフォルトで照らしていたライトはディレクショナルライト]
   
    // [ディレクショナルライト] https://dxlib.xsrv.jp/function/dxfunc_3d_light.html#R13N1
    // 標準ディレクショナルライト:ディレクショナルライトとは位置を持たず方向だけを設定するライト
    // 太陽の光などの光源が遠すぎて何処に居ても同じ方向から照らされるライト効果などに使用
    // DXライブラリの初期設定では標準ライトはディレクショナルライトとなる 計算負荷も最も軽い

    // ★g_Common.Light[ 0 ]
    // ディレクショナルライトを作成する
    DirLightHandle = DxLib::CreateDirLightHandle(VGet(0.0f, 0.0f, 1.0f));
    // ディレクショナルライトのアンビエントカラーを抑える
    //DxLib::SetLightAmbColorHandle(DirLightHandle, GetColorF(0.0f, 0.1f, 0.0f, 0.0f));
    // ディレクショナルライトのディフューズカラーを緑にする
    DxLib::SetLightDifColorHandle(DirLightHandle, GetColorF(0.0f, 1.0f, 0.0f, 0.0f));


    // [スポットライト] https://dxlib.xsrv.jp/function/dxfunc_3d_light.html#R13N23
    // 位置、方向、角度範囲、距離減衰パラメータを持つスポットライト 一番計算負荷が高く また頂点単位のライティングではポリゴンを細かく分割しておかないと思ったような結果が出ないなど扱いが難しいタイプのライト

    // ★g_Common.Light[ 1 ]
    // スポットライトを作成する
    SpotLightHandle = DxLib::CreateSpotLightHandle(VGet(0.0f, 0.0f, 0.0f), VGet(0.0f, -1.0f, 0.0f), 0.7f, 0.6f, 1000.0f, 0.391586f, 0.001662f, 0.0f);
    // スポットライトのアンビエントカラーを無効にする
    //DxLib::SetLightAmbColorHandle(SpotLightHandle, GetColorF(0.0f, 0.0f, 0.0f, 0.0f));
    // スポットライトのディフューズカラーを水色にする
    DxLib::SetLightDifColorHandle(SpotLightHandle, GetColorF(0.0f, 1.0f, 1.0f, 0.0f));


    // [ポイントライト] https://dxlib.xsrv.jp/function/dxfunc_3d_light.html#R13N2
    // 指定した位置から全方向に光を放つライト 位置と距離減衰パラメータを持つ

    // ★g_Common.Light[ 2 ]
    // ポイントライトを作成する
    PointLightHandle = DxLib::CreatePointLightHandle(VGet(0.0f, 0.0f, 0.0f), 7000.0f, 1.016523f, 0.000100f, 0.000010f);
    // ポイントライトのアンビエントカラーを無効にする
    //DxLib::SetLightAmbColorHandle(PointLightHandle, GetColorF(0.0f, 0.0f, 0.0f, 0.0f));
    // ポイントライトのディフューズカラーをピンク色にする
    DxLib::SetLightDifColorHandle(PointLightHandle, GetColorF(1.0f, 0.0f, 1.0f, 0.0f));

    controlLight = DirLightHandle; // デフォルトでコントロールするのはディレクショナルライトにする


    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 0.0f; keyControlXYZ.y = 0.0f; keyControlXYZ.z = 0;
    Quaternion quaternion; // 回転クオータニオン

    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen1 = SubScreen::Create()->Set(600, 450, 600, 100, 255, 0, "サブスクリーン1")->SetIsBarFrameShow(true,true);
   

    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    Editor& editor = Editor::GetInstance(); // エディタのシングルトン参照を得る

    std::list<std::weak_ptr<DragPoint>> dragPoints; // ドラッグ対象の点の共有ポインタのリスト

    //奥行0.1~5000までをカメラの描画範囲とする
    DxLib::SetCameraNearFar(0.1f, 5000.0f);

    ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Input::Update(); // マウスのドラッグなどを更新

        editor.UpdateKeyInput(); // エディタのキーのインプットを更新

        editor.UpdateDragPoints(&dragPoints); // ドラッグ点のリストを更新

        if (!editor.isDraggingPoints) editor.UpdateMouseInput(); // エディタのマウスのインプットを更新
        editor.UpdateCamera(); // エディタのカメラを更新
       
        // キーボードの 0 1 キーで3Dアニメを切り替えられるようにする
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_0))
            animeButton = 0;
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_1))
            animeButton = 1;

        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        VECTOR position = VGet(keyControlXYZ.x, keyControlXYZ.y, keyControlXYZ.z); // 3Dモデルの位置

        if (Input::GetButtonDown(Pad::Keyboard, KEY_INPUT_L)) // キーボードのLボタンが押されたら
        {
            if (controlLight == DirLightHandle) // 現在コントロール中のライトがディレクショナルライトのときは
                controlLight = PointLightHandle; // コントロールするライトをポイントライトにスイッチする
            else if (controlLight == PointLightHandle) // 現在コントロール中のライトがポイントライトのときは
                controlLight = SpotLightHandle; // コントロールするライトをスポットライトにスイッチする
            else // それ以外のとき、すなわちデフォルトのライトか、現在コントロール中のライトがスポットライトのときは
                controlLight = DirLightHandle; // コントロールするライトをディレクショナルライトにスイッチする
        }
        // コントロール中のライトの現在の位置
        VECTOR lightPos = (controlLight == -1) ? DxLib::GetLightPosition() : DxLib::GetLightPositionHandle(controlLight);
        VECTOR nextPos = VGet(lightPos.x, lightPos.y, lightPos.z); // コントロール中のライトの次の位置
        VECTOR nextDir = VGet(0, 0, 0); // コントロール中のライトの向いている方向
        bool isLightMove = false; // ライトが動いているかのフラグ
       
        // ライトをキーボードの上下左右PageUp PageDownで動かす
        if (PadInput & PAD_INPUT_LEFT) // 左矢印キーが押されたら
        {
            nextPos.x += -1; // x方向に-1動かす
            nextDir = VGet(-1, 0, 0); // ライトの向いている方向を-1の方向に
            isLightMove = true;
        }
        else if (PadInput & PAD_INPUT_RIGHT)
        {
            nextPos.x += 1;
            nextDir = VGet(1, 0, 0);
            isLightMove = true;
        }
        if (PadInput & PAD_INPUT_UP)
        {
            nextPos.z += 1;
            nextDir = VGet(0, 0, 1);
            isLightMove = true;
        }
        else if (PadInput & PAD_INPUT_DOWN)
        {
            nextPos.z += -1;
            nextDir = VGet(0, 0, -1);
            isLightMove = true;
        }
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGUP)) // PageUpボタン
        {
            nextPos.y += 1;
            nextDir = VGet(0, 1, 0);
            isLightMove = true;
        }
        else if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGDN)) // PageDownボタン
        {
            nextPos.y += -1;
            nextDir = VGet(0, -1, 0);
            isLightMove = true;
        }

        // ライトの位置の更新
        if (isLightMove)
        {
            if (controlLight == -1)
            {
                DxLib::SetLightPosition(nextPos); // デフォルトライトの位置を更新
                DxLib::SetLightDirection(nextDir); // デフォルトライトの向きを更新
            }
            else
            {
                DxLib::SetLightPositionHandle(controlLight, nextPos); // コントロール中のライトの位置を更新
                DxLib::SetLightDirectionHandle(controlLight, nextDir); // コントロール中のライトの向きを更新
            }
        }


        float width =  pTexImage->m_XSize; float height =  pTexImage->m_YSize; // テクスチャ画像のサイズ
        VECTOR scale = VGet(1.0f, 1.0f, 1.0f); // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)とcキー(C)でx,y,z軸まわりに画像を回転
        if (PadInput & PAD_INPUT_A) quaternion *= CreateRotationQuaternion(speed * DX_PI / 180, VGet(1, 0, 0)); // X軸(1,0,0)を軸にspeed度ぶんだけ回転
        if (PadInput & PAD_INPUT_B) quaternion *= CreateRotationQuaternion(speed * DX_PI / 180, VGet(0, 1, 0)); // Y軸(0,1,0)を軸にspeed度ぶんだけ回転
        if (PadInput & PAD_INPUT_C) quaternion *= CreateRotationQuaternion(speed * DX_PI / 180, VGet(0, 0, 1)); // Z軸(0,0,1)を軸にspeed度ぶんだけ回転

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数

        DxLib::SetUseVertexShader(VertexShaderHandle); // 使用する頂点シェーダーをセット
        DxLib::SetUsePixelShader(PixelShaderHandle); // 使用するピクセルシェーダーをセット
        DxLib::SetUseTextureToShader(0, *pTexImage); // 使用するテクスチャを0番にバインドする[ register( t0 ) ]

        MATRIX  matWorldDefault; // デフォルトのワールド行列
        DxLib::GetTransformToWorldMatrix(&matWorldDefault); // デフォルトのワールド行列を記憶しておく
        {
            //[ワールド行列] https://ny-program.hatenablog.com/entry/2019/09/15/114245
            MATRIX mat; // モデルのスケール×回転×移動を行列計算で設定する

            //[スケール]拡大縮小のスケール行列を得る
            mat = MGetScale(scale); // まずは拡大縮小のスケール行列からはじめる
            // [勉強]↓スケール行列は 斜めに x , y , zのスケールの倍率がならんでいる行列
            //mat.m[0][0] = scale.x;  mat.m[0][1] = 0.0f;    mat.m[0][2] = 0.0f;     mat.m[0][3] = 0.0f;
            //mat.m[1][0] = 0.0f;     mat.m[1][1] = scale.y; mat.m[1][2] = 0.0f;     mat.m[1][3] = 0.0f;
            //mat.m[2][0] = 0.0f;     mat.m[2][1] = 0.0f;    mat.m[2][2] = scale.z;  mat.m[2][3] = 0.0f;
            //mat.m[3][0] = 0.0f;     mat.m[3][1] = 0.0f;    mat.m[3][2] = 0.0f;     mat.m[3][3] = 1.0f;

            //[回転]
            MATRIX matRot;
            DxLib::CreateIdentityMatrix(&matRot);
            // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
            mat = MMult(mat, QuaternionToMatrix(quaternion)); // スケール × 回転

            //[移動]
            MATRIX matTrans = MGetTranslate(VGet(position.x, position.y, position.z)); // 位置positionをここで設定
            mat = MMult(mat, matTrans); //スケール×回転×移動

            DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4341

            DxLib::DrawPolygon32bitIndexed3DToShader(pPlyData->Vertex.data(), pPlyData->Vertex.size(), pPlyData->Index.data(), pPlyData->numIndex / 3); // 三角形ポリゴンをたくさん描画
        }
        DxLib::SetTransformToWorld(&matWorldDefault); // デフォルトのワールド行列に戻しておく

        DxLib::SetUseVertexShader(-1); // -1で毎度デフォルトのシェーダに戻しておけば他の3Dモデルの描画には影響を与えない
        DxLib::SetUsePixelShader(-1); // -1で毎度デフォルトのシェーダに戻しておけば他の3Dモデルの描画には影響を与えない
       
        DxLib::MV1DrawModel(devil3DModel); // 3Dモデル(.mv1形式)を描く

        auto xyzAngle = QuaternionToAngle(quaternion); // クオータニオンからそれに対応するオイラー角度の候補を得る(候補は最大3種ありうるし、ある軸が傾いていて0度じゃない場合他の軸も連動)
        DxLib::DrawFormatString(0, 100, GetColor(255, 255, 0), "x:%f y:%f Angle:%d %d %d", position.x, position.y, (int)(xyzAngle[0].x * 180 / DX_PI), (int)(xyzAngle[0].y * 180 / DX_PI), (int)(xyzAngle[0].z * 180 / DX_PI));

        // ファイルのパス + ":" + アニメ名でアクセス
        std::string animPathName = (animeButton == 0) ? "Image/devil@kick.mv1:mixamo.com" : "Image/devil@jump.mv1:mixamo.com";
        assert(pDevilData->count(animPathName) > 0 && "指定されたアニメ名のアニメはMV1に付属していませんでした");
        animTime += animStepTime * animSpeed; // 1.0×2倍 なら2倍速でアニメ時刻が進む
        if (animTime > (*pDevilData)[animPathName].endTime)
            animTime = 0.0f; // 0秒目へとアニメをループさせる
       
        // 再生時間をセットする
        DxLib::MV1SetAttachAnimTime((int)*pDevilData, (*pDevilData)[animPathName].index, animTime);

        int lightType = (controlLight == -1) ? DxLib::GetLightType() : DxLib::GetLightTypeHandle(controlLight);
        // 画面にライトのタイプを描画
        std::string TypeName;
        switch (lightType)
        {
        case DX_LIGHTTYPE_DIRECTIONAL: TypeName = "Directional"; break;
        case DX_LIGHTTYPE_POINT:       TypeName = "Point";       break;
        case DX_LIGHTTYPE_SPOT:        TypeName = "Spot";        break;
        }
        DxLib::DrawFormatString(0, 200, GetColor(255, 255, 255), "LightType    %s", TypeName.c_str());


        // [ディレクショナルライト表示]
        {   // ライトの円錐を描画 [ディレクショナルライト] https://dxlib.xsrv.jp/function/dxfunc_3d_light.html#R13N1
            float coneScale = 30.0f; //         lightPos + lightDir * coneScale
            VECTOR lightPos0 = VGet(0.0f, 0.0f, 0.0f);
            VECTOR lightDir0 = (controlLight == -1) ? DxLib::GetLightDirection() : DxLib::GetLightDirectionHandle(DirLightHandle);
            DxLib::DrawCone3D(lightPos0, VAdd(lightPos0, VScale(lightDir0, coneScale)), 10 * 2 * DX_PI_F, 8, GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE); //[自作円錐もあるが]https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4962
            //DrawCone3D(lightPos, VAdd(lightPos, VScale(lightDir, coneScale)), OutAngle * 2 * DX_PI_F, 8, GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);

            if (lightType == DX_LIGHTTYPE_DIRECTIONAL) // [ディレクショナルライト情報]
                DxLib::DrawFormatString(0, 200 + 60, GetColor(255, 255, 255), "Direction    %f %f %f", lightDir0.x, lightDir0.y, lightDir0.z);
        }

        // [スポットライト表示]
        {
            // ライトの照射角度パラメータ 標準ライト:GetLightAngle() 別のライト:GetLightAngleHandle
            float OutAngle; // スポットライトの影響角度(有効な値は 0.0f ~ DX_PI_F) スポットライトの向きに対してこの引数で指定する角度以上の頂点にはライトの影響はありません。
            float InAngle; // スポットライトの影響が減衰を始める角度(有効な値は 0.0f ~ OutAngle) スポットライトが OutAngle の角度まで100%の影響を与えて、 そこから急に影響が無い状態になりますと不自然に見えるかもしれないので、そんなときはこの引数でスポットライトの影響が弱まり始める角度を指定 スポットライトの向きに対してこの引数で指定する角度以上で且つ OutAngle 以下の場合はライトの影響が100%ではなくなります
            if (controlLight == -1)
                DxLib::GetLightAngle(&OutAngle, &InAngle);
            else
                DxLib::GetLightAngleHandle(SpotLightHandle, &OutAngle, &InAngle);

            float Range; // スポットライトの影響最大距離:距離以上の座標にある頂点は、 例え距離減衰計算の結果が0ではなくてもライトの影響は無くなる
            //ライトの影響力(%) = 100.0f / ( Atten0 + Atten1 * d + Atten2 * d * d ) [d = ライトから頂点への距離]
            // Attenは率を除算する値ですので、 非常に小さな値でも物凄くライトの影響範囲が狭まる
            float Atten0; // Atten0 はライトと頂点の距離に関係なく減衰する率を指定する引数
            float Atten1; // Atten1 はライトの距離に比例して減衰する率
            float Atten2; // Atten2 はライトの距離の二乗に比例して減衰する率
            if (controlLight == -1)
                DxLib::GetLightRangeAtten(&Range, &Atten0, &Atten1, &Atten2);
            else
                DxLib::GetLightRangeAttenHandle(SpotLightHandle, &Range, &Atten0, &Atten1, &Atten2);

            // ライトの位置に円錐を描画 [スポットライト] https://dxlib.xsrv.jp/function/dxfunc_3d_light.html#R13N3
            float coneScale = 15.0f; //         lightPos + lightDir * coneScale
            VECTOR lightPos1 = DxLib::GetLightPositionHandle(SpotLightHandle);
            VECTOR lightDir1 = DxLib::GetLightDirectionHandle(SpotLightHandle);
            DxLib::DrawCone3D(lightPos1, VAdd(lightPos1, VScale(lightDir1, coneScale)), InAngle * 2 * DX_PI_F, 8, GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE); //[自作円錐もあるが]https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4962
            DxLib::DrawCone3D(lightPos1, VAdd(lightPos1, VScale(lightDir1, coneScale)), OutAngle * 2 * DX_PI_F, 8, GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);

            if (lightType == DX_LIGHTTYPE_SPOT) // [スポットライト情報]
            {
                DxLib::DrawFormatString(0, 200 + 30,  GetColor(255, 255, 255), "Position     %f %f %f", lightPos1.x, lightPos1.y, lightPos1.z);
                DxLib::DrawFormatString(0, 200 + 60,  GetColor(255, 255, 255), "Direction    %f %f %f", lightDir1.x, lightDir1.y, lightDir1.z);
                DxLib::DrawFormatString(0, 200 + 90,  GetColor(255, 255, 255), "InOutAngle   %f %f", InAngle, OutAngle);
                DxLib::DrawFormatString(0, 200 + 120, GetColor(255, 255, 255), "Range        %f", Range);
                DxLib::DrawFormatString(0, 200 + 150, GetColor(255, 255, 255), "Attenuation  %f %f %f", Atten0, Atten1, Atten2);
            }
        }

        // [ポイントライト表示]
        {
            // [ポイントライト] 有効距離と距離減衰パラメータ https://dxlib.xsrv.jp/function/dxfunc_3d_light.html#R13N2
            float Range; // ポイントライトの影響最大距離:距離以上の座標にある頂点は、 例え距離減衰計算の結果が0ではなくてもライトの影響は無くなる
            //ライトの影響力(%) = 100.0f / ( Atten0 + Atten1 * d + Atten2 * d * d ) [d = ライトから頂点への距離]
            // Attenは率を除算する値ですので、 非常に小さな値でも物凄くライトの影響範囲が狭まる
            float Atten0; // Atten0 はライトと頂点の距離に関係なく減衰する率を指定する引数
            float Atten1; // Atten1 はライトの距離に比例して減衰する率
            float Atten2; // Atten2 はライトの距離の二乗に比例して減衰する率
            if (controlLight == -1)
                DxLib::GetLightRangeAtten(&Range, &Atten0, &Atten1, &Atten2);
            else
                DxLib::GetLightRangeAttenHandle(controlLight, &Range, &Atten0, &Atten1, &Atten2);

            // ライトの位置に球を描画 [ポイントライト]
            float scale = 10.0f;
            VECTOR lightPos2 = DxLib::GetLightPositionHandle(PointLightHandle);
            DxLib::DrawSphere3D(lightPos2, scale, 4, GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);

            if (lightType == DX_LIGHTTYPE_POINT) // [ポイントライト情報]
            {
                DxLib::DrawFormatString(0, 200 + 30,  GetColor(255, 255, 255), "Position     %f %f %f", lightPos2.x, lightPos2.y, lightPos2.z);
                DxLib::DrawFormatString(0, 200 + 120, GetColor(255, 255, 255), "Range        %f", Range);
                DxLib::DrawFormatString(0, 200 + 150, GetColor(255, 255, 255), "Attenuation  %f %f %f", Atten0, Atten1, Atten2);
            }
        }


        editor.Draw(); // エディタのDraw処理

        ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    WaitKey();
    // DXライブラリの後始末
    DxLib_End();
    // ソフトの終了
    return 0;
}


いかがでしょう?キーボードのLボタンを押すと、コントロールするライトが切り替わり、上下左右の矢印キーとPageUpやPageDownキーを押すと、ライトを奥手前:z 左右:x 上下:yに動かすことができましたか?


ここで、体感し基礎固めしておきたいのは、ディレクショナルライト、スポットライト、ポイントライト、の違いです。
Lキーを押すと、各ライトのパラメータが画面に表示されますが、
読み解いておきたいことは、
ディレクショナルライト:方向(ディレクション)しかない→つまり、位置がなくて、光の方向だけがあるライト
具体的に太陽とかは朝はフィールド全体を東から西方向に、昼は全体を上から下方向に、夕方は西から東に、照らす光のようにフィールドすべてのものが右上の部分だけ明るくなるみたいな照らされ方をする光です。
位置がないので、計算的するときに厳密な光の位置から反射点への角度のベクトルの計算が必要なくなるぶん、GPUの計算処理が軽くなる特徴があります。

ポイントライト:今度は位置はあるが、方向はない→つまり、光がある1点から上下左右360度全方向に照らされるライト
豆電球などをイメージするとよい。明かりがある点から全体を照らす感じになります。ディレクショナルライトよりは計算処理は重くなる。

スポットライト:今度は位置も方向も両方ある→逆にいうと光の出ない方向があるライト
電球の上に傘がついている光源のイメージ。光の広がり方のパラメータもある。
ポイントライトとスポットライト、どちらの計算負荷が重いか?ポイントライトは全方向に光を飛ばす、当然ながらたどらなきゃいけない光の経路はスポットライトより多い。
なので、[重要]ポイントライトよりはスポットライトを使った方がGPUの計算の負荷は軽い。ということは光の出る方向の計算と絡めて知っておく必要がある。
例えば、たいまつを洞窟に置くときも、たいまつは全方向に光が出るからポイントライトかな?と思うかもしれないが、たいまつの付け根の部分は木とつながってて光がでない。
だから、スポットライトの広がる角度を180度以上にすれば、スポットライトの傘が台風で逆向きになる感じになって、付け根付近までその広がりを広げればスポットライトでも表現できる。
光を出さない方向があればあるほど、計算の負荷はさがるという節約志向で、照明をうまく使う必要がある。そのためにはこういったデータの定義と計算の基礎考察をイメージできる確かな基礎教養が重要です。


さて、現在のままでは、ダイスには生成したディレクショナルライトなどの光の照りこみは映っていないはずです。(ダイスは赤や青のまま、ディレクショナルライトは緑色)
なぜなら、自作したシェーダーのコードにはまだディレクショナルライトなどの光の蓄積する計算式をまだ書いてないからです。
つぎは、自作の頂点シェーダーにディレクショナルライトの光の計算式を書き込んで、ダイスが緑色の光を拡散反射できるようにしてみましょう。

TestVS.fxを変更してディレクショナルライト(g_Common.Light[0])の光の蓄積値を計算して結果をVS_OUTで出力しましょう。



(前略).........

// main関数
VS_OUTPUT main( VS_INPUT VSInput )
{
    VS_OUTPUT VSOutput ;
    float4 lLocalPosition; // ローカル座標
    float4 lWorldPosition; // ワールド座標
    float4 lViewPosition; // 視線ビュー座標
   
    float3 lWorldNrm; // ワールド法線ベクトル
    float3 lViewNrm; // 視線ビュー法線ベクトル
   
    // ライティングの計算に必要な定義
    float3 lLightHalfVec; // ハーフベクトルの計算 norm( ( norm( 頂点位置からビュー視点へのベクトル ) + ライトの方向 ) )
    float4 lLightLitParam; // ライトのパラメータ 反射角度などの内積の値をもとめておく
    float4 lLightLitDest; // lit関数に上記のライトの内積パラメータを入力して光の減衰率の係数をもとめる
    float3 lLightDir; // 光源の向いている方向のベクトル(ディレクショナルライトやスポットライトの方向ベクトルがここに来る)
   
    // ディフューズ:拡散反射光 と スペキュラ:鏡面反射光の合計トータル蓄積値
    float3 lTotalDiffuse; // 拡散反射光のトータル蓄積値
    float3 lTotalSpecular; // 鏡面反射光のトータル蓄積値

   
    // 頂点座標変換 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 開始 )
    // ローカル座標のセット
    lLocalPosition.xyz = VSInput.Position;
    lLocalPosition.w = 1.0f;
    // ローカル座標をワールド座標に変換 [3D座標がスクリーンに投影されるための行列変換の過程] https://light11.hatenadiary.com/entry/2019/01/27/160541
    lWorldPosition.x = dot(lLocalPosition, g_Base.LocalWorldMatrix[0]);
    lWorldPosition.y = dot(lLocalPosition, g_Base.LocalWorldMatrix[1]);
    lWorldPosition.z = dot(lLocalPosition, g_Base.LocalWorldMatrix[2]);
    lWorldPosition.w = 1.0f;
    // ワールド座標をビュー座標に変換 [ワールド座標のカメラの位置が原点(0,0,0)になる行列変換] https://light11.hatenadiary.com/entry/2019/01/27/160541
    lViewPosition.x = dot(lWorldPosition, g_Base.ViewMatrix[0]);
    lViewPosition.y = dot(lWorldPosition, g_Base.ViewMatrix[1]);
    lViewPosition.z = dot(lWorldPosition, g_Base.ViewMatrix[2]);
    lViewPosition.w = 1.0f;
    // 視線ビュー座標をプロジェクション射影座標に変換 https://light11.hatenadiary.com/entry/2019/01/27/160541
    VSOutput.Position.x = dot(lViewPosition, g_Base.ProjectionMatrix[0]);
    VSOutput.Position.y = dot(lViewPosition, g_Base.ProjectionMatrix[1]);
    VSOutput.Position.z = dot(lViewPosition, g_Base.ProjectionMatrix[2]);
    VSOutput.Position.w = dot(lViewPosition, g_Base.ProjectionMatrix[3]);
    // 頂点座標変換 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 終了 )

   
    // 法線をビュー空間の角度に変換 =====================================================( 開始 )
    // ローカル法線ベクトル:VSInput.Normal を ワールド法線ベクトル:lWorldNrm に変換
    lWorldNrm.x = dot(VSInput.Normal, g_Base.LocalWorldMatrix[0].xyz);
    lWorldNrm.y = dot(VSInput.Normal, g_Base.LocalWorldMatrix[1].xyz);
    lWorldNrm.z = dot(VSInput.Normal, g_Base.LocalWorldMatrix[2].xyz);
    // ワールド法線ベクトル:lWorldNrm を ビュー法線ベクトル:lViewNrm に変換
    lViewNrm.x = dot(lWorldNrm, g_Base.ViewMatrix[0].xyz);
    lViewNrm.y = dot(lWorldNrm, g_Base.ViewMatrix[1].xyz);
    lViewNrm.z = dot(lWorldNrm, g_Base.ViewMatrix[2].xyz);
    // 法線をビュー空間の角度に変換 =====================================================( 終了 )

   
    //[※注意!ディレクショナルライト→スポットライト→ポイントライトの順に勝手にg_Common.Light[0],[1],[2]は並べ変わる]
    // https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=5605
   
    // ディレクショナルライトの処理 g_Common.Light[0]を使う******************************( 開始 )
    // ライトの方向セット
    lLightDir = g_Common.Light[0].Direction;
    // ライトディフューズカラーとライトスペキュラカラーの角度減衰計算 =======( 開始 )
    // 法線とライトの逆方向ベクトルとの内積を lLightLitParam.x にセット
    lLightLitParam.x = dot(lViewNrm, -lLightDir);
    // ハーフベクトルの計算 norm( ( norm( 頂点位置から視点へのベクトル ) + ライトの方向 ) )
    lLightHalfVec = normalize(normalize(-lViewPosition.xyz) - lLightDir);
    // 法線とハーフベクトルの内積を lLightLitParam.y にセット [法線とライトベクトルの内積で平面がカメラに向いている割合計算] https://someiyoshino.info/entry/20171231/1514692823
    lLightLitParam.y = dot(lLightHalfVec, lViewNrm);
    // スペキュラ反射率を lLightLitParam.w にセット
    lLightLitParam.w = 20.0f;
    // ライト計算 [HLSL言語のlit関数] https://learn.microsoft.com/ja-jp/windows/win32/direct3dhlsl/dx-graphics-hlsl-lit
    lLightLitDest = lit(lLightLitParam.x, lLightLitParam.y, lLightLitParam.w);
    // ライトディフューズカラーとライトスペキュラカラーの角度減衰計算 =======( 終了 )
    // カラー計算 ===========================================================( 開始 )
    // ディフューズライト蓄積値 += ディフューズ角度減衰計算結果 * ライトのディフューズカラー + ライトのアンビエントカラー
    lTotalDiffuse += lLightLitDest.y * g_Common.Light[0].Diffuse + g_Common.Light[0].Ambient.xyz;
    // スペキュラライト蓄積値 += スペキュラ角度減衰計算結果 * ライトのスペキュラカラー
    lTotalSpecular += lLightLitDest.z * g_Common.Light[0].Specular;
    // カラー計算 ===========================================================( 終了 )
    // ディレクショナルライトの処理 *****************************************************( 終了 )

    // ディフューズカラー = ディフューズライト蓄積値
    VSOutput.Diffuse.xyz = lTotalDiffuse;
    // スペキュラカラー = スペキュラライト蓄積値
    VSOutput.Specular.xyz = lTotalSpecular;
   

   
    VSOutput.TexCoords0 = VSInput.TexCoords0; // テクスチャの座標をそのままOutputする
   
    // テクスチャ座標変換行列による変換を行った結果のテクスチャ座標をセット
    VSOutput.TexCoords0.x = dot(VSInput.TexCoords0, g_OtherMatrix.TextureMatrix[0][0]);
    VSOutput.TexCoords0.y = dot(VSInput.TexCoords0, g_OtherMatrix.TextureMatrix[0][1]);

    // [実験]頂点カラーの拡散光をxの位置が0.0f以上か以下かで青か赤かに描き分ける処理を書いてみる
    VSOutput.Diffuse.r = (VSOutput.Position.x > 0.0f) ? 1.0f : 0.0f; // R(赤の拡散値)
    VSOutput.Diffuse.g = 0.0f; // G(緑の拡散値)
    VSOutput.Diffuse.b = (VSOutput.Position.x > 0.0f) ? 0.0f : 1.0f; // B(青の拡散値)


    // 出力パラメータを返す
    return VSOutput ;
}


TestVS.fxを変更したら保存して、コマンドプロンプトから再びShaderCompiler.exeのコマンドでビルドしなおします。(キーボードの上キーを押すと過去に実行したコマンドが出てきて楽)

続いて、ピクセルシェーダーの計算式もすこし変更しておきましょう。

TestPS.fxを変更して下記コードで 頂点シェーダからやってくるのPSInputの鏡面反射光(Specular) も PSOutput.Color0に足し算して合成して出力しましょう。

// ピクセルシェーダーの入力
struct PS_INPUT
{
    float4 Diffuse         : COLOR0 ;
    float4 Specular        : COLOR1 ;
    float2 TexCoords0      : TEXCOORD0 ;
} ;

// ピクセルシェーダーの出力
struct PS_OUTPUT
{
    float4 Color0          : SV_TARGET0 ;    // 色
} ;


// [DirectX11版]定数バッファピクセルシェーダー基本パラメータ
struct DX_D3D11_PS_CONST_BUFFER_BASE
{
    float4        FactorColor ;        // アルファ値等

    float        MulAlphaColor ;        // カラーにアルファ値を乗算するかどうか( 0.0f:乗算しない  1.0f:乗算する )
    float        AlphaTestRef ;        // アルファテストで使用する比較値
    float2        Padding1 ;

    int        AlphaTestCmpMode ;    // アルファテスト比較モード( DX_CMP_NEVER など )
    int3        Padding2 ;

    float4        IgnoreTextureColor ;    // テクスチャカラー無視処理用カラー
} ;

// [DirectX11版]基本パラメータ https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4904
cbuffer cbD3D11_CONST_BUFFER_PS_BASE    : register( b1 )
{
    DX_D3D11_PS_CONST_BUFFER_BASE    g_Base ;
} ;


SamplerState g_DiffuseMapSampler    : register( s0 ) ;    // ディフューズマップテクスチャ
Texture2D    g_DiffuseMapTexture    : register( t0 ) ;    // ディフューズマップテクスチャ



// main関数
PS_OUTPUT main( PS_INPUT PSInput )
{
    PS_OUTPUT PSOutput ;
    float4 TextureDiffuseColor ;

    // テクスチャ画像の色をサンプラーで[サンプリング採集 して取り出す]
    TextureDiffuseColor = g_DiffuseMapTexture.Sample(g_DiffuseMapSampler, PSInput.TexCoords0);

    // 出力カラー = ディフューズカラー * テクスチャカラー + スペキュラカラー
    PSOutput.Color0 = PSInput.Diffuse * TextureDiffuseColor + PSInput.Specular;

    // 出力アルファ = ディフューズアルファ * テクスチャアルファ * 不透明度
    PSOutput.Color0.a = PSInput.Diffuse.a * TextureDiffuseColor.a * g_Base.FactorColor.a;


    // 出力パラメータを返す
    return PSOutput ;
}

TestPS.fxを変更したら保存して、コマンドプロンプトから再びShaderCompiler.exeのコマンドでビルドしなおします。(キーボードの上キーを押すと過去に実行したコマンドが出てきて楽)

いかがでしょう?実行したら、サイコロの表面にディレクショナルライトの緑色が反映されたでしょうか?
キーボードの上下左右PageDown,PageUpボタンを押して、ディレクショナルライトの方向が変わると緑色に光る面が変わることを確認してみましょう。

シェーダーのコード内では、たくさんの頂点を、ローカル→ワールド→カメラからの視線ビューへと行列変換して、
求めたカメラ視線からのビューの法線ベクトル と 光源のライトの光の方向のベクトル を dotで内積したパラメータなどを HLSL言語のlit関数 に入れて、ライトの角度による減衰を計算しています。
このようにポリゴンの表面の光の反射具合は、シェーダー言語に処理を書いてGPUに計算させる過程を経て、画面に3D空間の光の反射した3Dモデルが描かれているわけです。
シェーダー言語を自分で書いて勉強することで、3Dモデルのデータが画面にドットの色として描かれるまでの一連の計算の流れを頭の中でイメージできることがゲームの描画プログラマとして一歩レベルアップするのに必要です。


3Dモデルの表面のマテリアルを定義してシェーダでライトの計算と連携させる

つづいて次は、3Dモデルの表面のマテリアル:質感を自分で定義して、シェーダーで先ほどのディレクショナルライトなどのライト計算と連携させてみましょう。

main.cppのコードを変更してサイコロの表面の設定を表すマテリアル構造体 MATERIALPARAM diceMaterial; を定義して数値を設定してシェーダーへサイコロの表面の情報を伝えてみましょう。

#include "DxLib.h"
#include <assert.h> // 画像読み込みの読込み失敗表示用
#include "Input.h"
#include "Screen.h"
#include "Ply.h"
#include "DataMV1.h"
#include "DataPly.h"
#include "Collision3D.h"
#include "MyDraw.h" // 自作の描画処理
#include "Quaternion.h"

#include "Editor.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMain からプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズWidth×Heightのカラービット数32ビットで起動
    SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズWidth×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    SetMainWindowText("ゲームのウィンドウ名を変えるときはここ");//この行でエラーになったら【設定】マルチバイト文字セットが間 違ってるかも

    SetUseDirect3DVersion(DX_DIRECT3D_11); // 最新のDXライブラリはデフォルトでDirectX11版のシェーダー言語に対応しているが、念のため11に事前設定

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    //表示しているスクリーンの後ろで隠れて次に描く画像を先に描くモード
    // これとペアでScreenFlip();でつぎのページと入れ替えでちらつきを防ぐ
    SetDrawScreen(DX_SCREEN_BACK);
  
    // ゲームのwhileループを開始する前の初期化処理
    float animTime = 0.0f; // アニメの現在時刻
    float animSpeed = 2.0f; // MV1のアニメの再生速度設定 2.0fなら2倍速
    float animStepTime = 1.0f; // MV1のアニメの毎時間の進む時間の設定
    std::shared_ptr<DataMV1> pDevilData = Resource::MakeShared<DataMV1>("Image/devil@kick.mv1");
    pDevilData->Load(); // 3Dモデルをmv1形式で読込み
    std::shared_ptr<DataMV1> pDevilAttackAnimData = Resource::MakeShared<DataMV1>("Image/devil@jump.mv1");
    pDevilAttackAnimData->Load(); // 3Dの追加アニメデータをmv1形式で読込み
    pDevilData->AttachAnim(*pDevilAttackAnimData); // アニメを追加でflyのほうをベースとしたモデルにアタッチ
    int animeButton = 0; // ボタンでアニメを切り替えられるようにする

    std::shared_ptr<Texture> pTexImage = Resource::MakeShared<Texture>("Image/dice.png");
    pTexImage->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
    assert((int)*pTexImage != -1); // 画像読込失敗、ファイル名かフォルダ名が間違ってる

    MATERIALPARAM diceMaterial; // サイコロの表面を設定するためのマテリアル構造体
    {   // ディフューズ色,アンビエント色,スペキュラ色,エミッシブ色,
        // ディフューズ拡散光の反射色(ポリゴンの面の法線角度が影響)
        COLOR_F    Diffuse; Diffuse.r = 1.0f; Diffuse.g = 1.0f; Diffuse.b = 1.0f; Diffuse.a = 1.0f;
        // アンビエント環境光の反射色(法線の影響なくライトが当たる範囲内なら表面が光ってみえる)
        COLOR_F    Ambient; Ambient.r = 0.3f; Ambient.g = 0.3f; Ambient.b = 0.3f; Ambient.a = 1.0f;
        // エミッシブ自己発光の色(ライトの光ではなく、3Dの物体自体が自分で発光して表面が光って見える)
        COLOR_F    Emissive; Emissive.r = 0.5f; Emissive.g = 0.0f; Emissive.b = 0.0f; Emissive.a = 1.0f; // 光があたっていなくても赤色に光るようにEmissive.r = 0.5f;にする
        // スペキュラ鏡面反射光の色(スプーンなどの表面のように面の法線と視点の角度が狭いと鋭く反射して見える)
        COLOR_F    Specular; Specular.r = 0.5f; Specular.g = 0.5f; Specular.b = 0.5f; Specular.a = 1.0f;
        float Power = 20.0f; // スペキュラ反射の角度による減衰強度、大きい数字になると反射する面積が小さくなり鋭くキラッと反射して見える
        diceMaterial.Diffuse = Diffuse;
        diceMaterial.Ambient = Ambient;
        diceMaterial.Emissive = Emissive;
        diceMaterial.Specular = Specular;
        diceMaterial.Power = Power;
    }

    MATERIALPARAM defaultMaterial; // デフォルトのマテリアル設定値
    {
        COLOR_F    Diffuse; Diffuse.r = 1.0f; Diffuse.g = 1.0f; Diffuse.b = 1.0f; Diffuse.a = 1.0f;
        COLOR_F    Ambient; Ambient.r = 0.3f; Ambient.g = 0.3f; Ambient.b = 0.3f; Ambient.a = 1.0f;
        COLOR_F    Emissive; Emissive.r = 0.0f; Emissive.g = 0.0f; Emissive.b = 0.0f; Emissive.a = 1.0f;
        COLOR_F    Specular; Specular.r = 0.5f; Specular.g = 0.5f; Specular.b = 0.5f; Specular.a = 1.0f;
        defaultMaterial.Diffuse = Diffuse;
        defaultMaterial.Ambient = Ambient;
        defaultMaterial.Emissive = Emissive;
        defaultMaterial.Specular = Specular;
        defaultMaterial.Power = 20.0f;
    }

   
    std::shared_ptr<DataPly> pPlyData = Resource::MakeShared<DataPly>("Image/dice.ply");
    pPlyData->Load(); // MakeSharedとLoadを分けた(フォルダだけ設定しておいて、あとからロードもできるように)
   
    int VertexShaderHandle = DxLib::LoadVertexShader("Shader/TestVS.vso"); // 頂点シェーダーを読み込む
    int PixelShaderHandle  = DxLib::LoadPixelShader("Shader/TestPS.pso");  // ピクセルシェーダーを読み込む
   
    int DirLightHandle; // ディレクショナルライトのハンドル番号
    int SpotLightHandle; // スポットライトのハンドル番号
    int PointLightHandle; // ポイントライトのハンドル番号
    int controlLight = -1; // ユーザーのコントロール中のライト
    DxLib::SetUseLighting(TRUE); // 光の明暗計算を有効に 同時に有効にできるのは標準ライトを含めて3~6つ(シェーダーのVerやLight[]配列の数の定義による)
    DxLib::SetLightEnable(FALSE); // デフォルトで3D空間を照らしていた標準ライトは無効にする(無効にしないとg_Common.Light[ 0 ]に標準のデフォルトライトが割り込んじゃう)

    // ※※ライトの初期化順※※※
    // ★[重要] CreateLight系関数を呼んだ順にシェーダー側のg_Common.Light[ 0 ] g_Common.Light[ 1 ] g_Common.Light[ 2 ]にライトが対応するので
    // シェーダーのコードとの対応関係を考えた上でディレクショナルライト、スポットライト、ポイントライトの初期化の順番を合わせないといけない!!!
    //[DXでは必ずディレクショナルライト→スポットライト→ポイントライトの順に初期化しないといけない] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=5605


    // [標準ライトのデフォルトで照らしていたライトはディレクショナルライト]
   
    // [ディレクショナルライト] https://dxlib.xsrv.jp/function/dxfunc_3d_light.html#R13N1
    // 標準ディレクショナルライト:ディレクショナルライトとは位置を持たず方向だけを設定するライト
    // 太陽の光などの光源が遠すぎて何処に居ても同じ方向から照らされるライト効果などに使用
    // DXライブラリの初期設定では標準ライトはディレクショナルライトとなる 計算負荷も最も軽い

    // ★g_Common.Light[ 0 ]
    // ディレクショナルライトを作成する
    DirLightHandle = DxLib::CreateDirLightHandle(VGet(0.0f, 0.0f, 1.0f));
    // ディレクショナルライトのアンビエントカラーを抑える
    //DxLib::SetLightAmbColorHandle(DirLightHandle, GetColorF(0.0f, 0.1f, 0.0f, 0.0f));
    // ディレクショナルライトのディフューズカラーを緑にする
    DxLib::SetLightDifColorHandle(DirLightHandle, GetColorF(0.0f, 1.0f, 0.0f, 0.0f));


    // [スポットライト] https://dxlib.xsrv.jp/function/dxfunc_3d_light.html#R13N23
    // 位置、方向、角度範囲、距離減衰パラメータを持つスポットライト 一番計算負荷が高く また頂点単位のライティングではポリゴンを細かく分割しておかないと思ったような結果が出ないなど扱いが難しいタイプのライト

    // ★g_Common.Light[ 1 ]
    // スポットライトを作成する
    SpotLightHandle = DxLib::CreateSpotLightHandle(VGet(0.0f, 0.0f, 0.0f), VGet(0.0f, -1.0f, 0.0f), 0.7f, 0.6f, 1000.0f, 0.391586f, 0.001662f, 0.0f);
    // スポットライトのアンビエントカラーを無効にする
    //DxLib::SetLightAmbColorHandle(SpotLightHandle, GetColorF(0.0f, 0.0f, 0.0f, 0.0f));
    // スポットライトのディフューズカラーを水色にする
    DxLib::SetLightDifColorHandle(SpotLightHandle, GetColorF(0.0f, 1.0f, 1.0f, 0.0f));


    // [ポイントライト] https://dxlib.xsrv.jp/function/dxfunc_3d_light.html#R13N2
    // 指定した位置から全方向に光を放つライト 位置と距離減衰パラメータを持つ

    // ★g_Common.Light[ 2 ]
    // ポイントライトを作成する
    PointLightHandle = DxLib::CreatePointLightHandle(VGet(0.0f, 0.0f, 0.0f), 7000.0f, 1.016523f, 0.000100f, 0.000010f);
    // ポイントライトのアンビエントカラーを無効にする
    //DxLib::SetLightAmbColorHandle(PointLightHandle, GetColorF(0.0f, 0.0f, 0.0f, 0.0f));
    // ポイントライトのディフューズカラーをピンク色にする
    DxLib::SetLightDifColorHandle(PointLightHandle, GetColorF(1.0f, 0.0f, 1.0f, 0.0f));

    controlLight = DirLightHandle; // デフォルトでコントロールするのはディレクショナルライトにする

    VECTOR keyControlXYZ; // 上下左右キーでxy位置を動かせるように
    keyControlXYZ.x = 0.0f; keyControlXYZ.y = 0.0f; keyControlXYZ.z = 0;
    Quaternion quaternion; // 回転クオータニオン

    // サブのスクリーンを生成する
    std::shared_ptr<SubScreen> subscreen1 = SubScreen::Create()->Set(600, 450, 600, 100, 255, 0, "サブスクリーン1")->SetIsBarFrameShow(true,true);
   

    // バックカリングするか?(裏面になっているポリゴンを書かない処理)
    DxLib::SetUseBackCulling(TRUE);

    Editor& editor = Editor::GetInstance(); // エディタのシングルトン参照を得る

    std::list<std::weak_ptr<DragPoint>> dragPoints; // ドラッグ対象の点の共有ポインタのリスト

    //奥行0.1~5000までをカメラの描画範囲とする
    DxLib::SetCameraNearFar(0.1f, 5000.0f);

    ScreenFlip();

    // アニメーション(パラパラ漫画)するにはWhile文
    while (ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Input::Update(); // マウスのドラッグなどを更新

        editor.UpdateKeyInput(); // エディタのキーのインプットを更新

        editor.UpdateDragPoints(&dragPoints); // ドラッグ点のリストを更新

        if (!editor.isDraggingPoints) editor.UpdateMouseInput(); // エディタのマウスのインプットを更新
        editor.UpdateCamera(); // エディタのカメラを更新
       
        // キーボードの 0 1 キーで3Dアニメを切り替えられるようにする
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_0))
            animeButton = 0;
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_1))
            animeButton = 1;

        int PadInput = GetJoypadInputState(DX_INPUT_KEY_PAD1);
        int speed = 5;
        // 上下左右キーでポリゴンを描く起点(x,y)を移動
        if (PadInput & PAD_INPUT_LEFT)       keyControlXYZ.x -= speed;
        else if (PadInput & PAD_INPUT_RIGHT) keyControlXYZ.x += speed;
        if (PadInput & PAD_INPUT_UP)        keyControlXYZ.y += speed;
        else if (PadInput & PAD_INPUT_DOWN) keyControlXYZ.y -= speed;

        VECTOR position = VGet(keyControlXYZ.x, keyControlXYZ.y, keyControlXYZ.z); // 3Dモデルの位置

        if (Input::GetButtonDown(Pad::Keyboard, KEY_INPUT_L)) // キーボードのLボタンが押されたら
        {
            if (controlLight == DirLightHandle) // 現在コントロール中のライトがディレクショナルライトのときは
                controlLight = PointLightHandle; // コントロールするライトをポイントライトにスイッチする
            else if (controlLight == PointLightHandle) // 現在コントロール中のライトがポイントライトのときは
                controlLight = SpotLightHandle; // コントロールするライトをスポットライトにスイッチする
            else // それ以外のとき、すなわちデフォルトのライトか、現在コントロール中のライトがスポットライトのときは
                controlLight = DirLightHandle; // コントロールするライトをディレクショナルライトにスイッチする
        }
        // コントロール中のライトの現在の位置
        VECTOR lightPos = (controlLight == -1) ? DxLib::GetLightPosition() : DxLib::GetLightPositionHandle(controlLight);
        VECTOR nextPos = VGet(lightPos.x, lightPos.y, lightPos.z); // コントロール中のライトの次の位置
        VECTOR nextDir = VGet(0, 0, 0); // コントロール中のライトの向いている方向
        bool isLightMove = false; // ライトが動いているかのフラグ
       
        // ライトをキーボードの上下左右PageUp PageDownで動かす
        if (PadInput & PAD_INPUT_LEFT) // 左矢印キーが押されたら
        {
            nextPos.x += -1; // x方向に-1動かす
            nextDir = VGet(-1, 0, 0); // ライトの向いている方向を-1の方向に
            isLightMove = true;
        }
        else if (PadInput & PAD_INPUT_RIGHT)
        {
            nextPos.x += 1;
            nextDir = VGet(1, 0, 0);
            isLightMove = true;
        }
        if (PadInput & PAD_INPUT_UP)
        {
            nextPos.z += 1;
            nextDir = VGet(0, 0, 1);
            isLightMove = true;
        }
        else if (PadInput & PAD_INPUT_DOWN)
        {
            nextPos.z += -1;
            nextDir = VGet(0, 0, -1);
            isLightMove = true;
        }
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGUP)) // PageUpボタン
        {
            nextPos.y += 1;
            nextDir = VGet(0, 1, 0);
            isLightMove = true;
        }
        else if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGDN)) // PageDownボタン
        {
            nextPos.y += -1;
            nextDir = VGet(0, -1, 0);
            isLightMove = true;
        }

        // ライトの位置の更新
        if (isLightMove)
        {
            if (controlLight == -1)
            {
                DxLib::SetLightPosition(nextPos); // デフォルトライトの位置を更新
                DxLib::SetLightDirection(nextDir); // デフォルトライトの向きを更新
            }
            else
            {
                DxLib::SetLightPositionHandle(controlLight, nextPos); // コントロール中のライトの位置を更新
                DxLib::SetLightDirectionHandle(controlLight, nextDir); // コントロール中のライトの向きを更新
            }
        }

        float width =  pTexImage->m_XSize; float height =  pTexImage->m_YSize; // テクスチャ画像のサイズ
        VECTOR scale = VGet(1.0f, 1.0f, 1.0f); // 拡大縮小スケール

        // キーボードのzキー(A)とxキー(B)とcキー(C)でx,y,z軸まわりに画像を回転
        if (PadInput & PAD_INPUT_A) quaternion *= CreateRotationQuaternion(speed * DX_PI / 180, VGet(1, 0, 0)); // X軸(1,0,0)を軸にspeed度ぶんだけ回転
        if (PadInput & PAD_INPUT_B) quaternion *= CreateRotationQuaternion(speed * DX_PI / 180, VGet(0, 1, 0)); // Y軸(0,1,0)を軸にspeed度ぶんだけ回転
        if (PadInput & PAD_INPUT_C) quaternion *= CreateRotationQuaternion(speed * DX_PI / 180, VGet(0, 0, 1)); // Z軸(0,0,1)を軸にspeed度ぶんだけ回転

        const float Deg2Rad = DX_PI_F / 180.0f; // 角度(単位:°度)からラジアン(単位:rad)へ変換する係数

        DxLib::SetUseVertexShader(VertexShaderHandle); // 使用する頂点シェーダーをセット
        DxLib::SetUsePixelShader(PixelShaderHandle); // 使用するピクセルシェーダーをセット
        DxLib::SetUseTextureToShader(0, *pTexImage); // 使用するテクスチャを0番にバインドする[ register( t0 ) ]
        DxLib::SetMaterialParam(diceMaterial); // 使用するマテリアルを設定する

        MATRIX  matWorldDefault; // デフォルトのワールド行列
        DxLib::GetTransformToWorldMatrix(&matWorldDefault); // デフォルトのワールド行列を記憶しておく
        {
            //[ワールド行列] https://ny-program.hatenablog.com/entry/2019/09/15/114245
            MATRIX mat; // モデルのスケール×回転×移動を行列計算で設定する

            //[スケール]拡大縮小のスケール行列を得る
            mat = MGetScale(scale); // まずは拡大縮小のスケール行列からはじめる
            // [勉強]↓スケール行列は 斜めに x , y , zのスケールの倍率がならんでいる行列
            //mat.m[0][0] = scale.x;  mat.m[0][1] = 0.0f;    mat.m[0][2] = 0.0f;     mat.m[0][3] = 0.0f;
            //mat.m[1][0] = 0.0f;     mat.m[1][1] = scale.y; mat.m[1][2] = 0.0f;     mat.m[1][3] = 0.0f;
            //mat.m[2][0] = 0.0f;     mat.m[2][1] = 0.0f;    mat.m[2][2] = scale.z;  mat.m[2][3] = 0.0f;
            //mat.m[3][0] = 0.0f;     mat.m[3][1] = 0.0f;    mat.m[3][2] = 0.0f;     mat.m[3][3] = 1.0f;

            //[回転]
            MATRIX matRot;
            DxLib::CreateIdentityMatrix(&matRot);
            // ★MMultは行列の乗算【乗算した順にz軸回転→x軸回転→y軸回転→平行移動】
            mat = MMult(mat, QuaternionToMatrix(quaternion)); // スケール × 回転

            //[移動]
            MATRIX matTrans = MGetTranslate(VGet(position.x, position.y, position.z)); // 位置positionをここで設定
            mat = MMult(mat, matTrans); //スケール×回転×移動

            DxLib::SetTransformToWorld(&mat); // [ワールド行列を指定] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4341

            DxLib::DrawPolygon32bitIndexed3DToShader(pPlyData->Vertex.data(), pPlyData->Vertex.size(), pPlyData->Index.data(), pPlyData->numIndex / 3); // 三角形ポリゴンをたくさん描画
        }
        DxLib::SetTransformToWorld(&matWorldDefault); // デフォルトのワールド行列に戻しておく

        DxLib::SetMaterialParam(defaultMaterial); // デフォルトのマテリアルに戻して他の描画に影響を与えないようにしておく
        DxLib::SetUseVertexShader(-1); // -1で毎度デフォルトのシェーダに戻しておけば他の3Dモデルの描画には影響を与えない
        DxLib::SetUsePixelShader(-1); // -1で毎度デフォルトのシェーダに戻しておけば他の3Dモデルの描画には影響を与えない
       
        DxLib::MV1DrawModel(devil3DModel); // 3Dモデル(.mv1形式)を描く

        auto xyzAngle = QuaternionToAngle(quaternion); // クオータニオンからそれに対応するオイラー角度の候補を得る(候補は最大3種ありうるし、ある軸が傾いていて0度じゃない場合他の軸も連動)
        DxLib::DrawFormatString(0, 100, GetColor(255, 255, 0), "x:%f y:%f Angle:%d %d %d", position.x, position.y, (int)(xyzAngle[0].x * 180 / DX_PI), (int)(xyzAngle[0].y * 180 / DX_PI), (int)(xyzAngle[0].z * 180 / DX_PI));

        // ファイルのパス + ":" + アニメ名でアクセス
        std::string animPathName = (animeButton == 0) ? "Image/devil@kick.mv1:mixamo.com" : "Image/devil@jump.mv1:mixamo.com";
        assert(pDevilData->count(animPathName) > 0 && "指定されたアニメ名のアニメはMV1に付属していませんでした");
        animTime += animStepTime * animSpeed; // 1.0×2倍 なら2倍速でアニメ時刻が進む
        if (animTime > (*pDevilData)[animPathName].endTime)
            animTime = 0.0f; // 0秒目へとアニメをループさせる
       
        // 再生時間をセットする
        DxLib::MV1SetAttachAnimTime((int)*pDevilData, (*pDevilData)[animPathName].index, animTime);

        int lightType = (controlLight == -1) ? DxLib::GetLightType() : DxLib::GetLightTypeHandle(controlLight);
        // 画面にライトのタイプを描画
        std::string TypeName;
        switch (lightType)
        {
        case DX_LIGHTTYPE_DIRECTIONAL: TypeName = "Directional"; break;
        case DX_LIGHTTYPE_POINT:       TypeName = "Point";       break;
        case DX_LIGHTTYPE_SPOT:        TypeName = "Spot";        break;
        }
        DxLib::DrawFormatString(0, 200, GetColor(255, 255, 255), "LightType    %s", TypeName.c_str());


        // [ディレクショナルライト表示]
        {   // ライトの円錐を描画 [ディレクショナルライト] https://dxlib.xsrv.jp/function/dxfunc_3d_light.html#R13N1
            float coneScale = 30.0f; //         lightPos + lightDir * coneScale
            VECTOR lightPos0 = VGet(0.0f, 0.0f, 0.0f);
            VECTOR lightDir0 = (controlLight == -1) ? DxLib::GetLightDirection() : DxLib::GetLightDirectionHandle(DirLightHandle);
            DxLib::DrawCone3D(lightPos0, VAdd(lightPos0, VScale(lightDir0, coneScale)), 10 * 2 * DX_PI_F, 8, GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE); //[自作円錐もあるが]https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4962
            //DrawCone3D(lightPos, VAdd(lightPos, VScale(lightDir, coneScale)), OutAngle * 2 * DX_PI_F, 8, GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);

            if (lightType == DX_LIGHTTYPE_DIRECTIONAL) // [ディレクショナルライト情報]
                DxLib::DrawFormatString(0, 200 + 60, GetColor(255, 255, 255), "Direction    %f %f %f", lightDir0.x, lightDir0.y, lightDir0.z);
        }

        // [スポットライト表示]
        {
            // ライトの照射角度パラメータ 標準ライト:GetLightAngle() 別のライト:GetLightAngleHandle
            float OutAngle; // スポットライトの影響角度(有効な値は 0.0f ~ DX_PI_F) スポットライトの向きに対してこの引数で指定する角度以上の頂点にはライトの影響はありません。
            float InAngle; // スポットライトの影響が減衰を始める角度(有効な値は 0.0f ~ OutAngle) スポットライトが OutAngle の角度まで100%の影響を与えて、 そこから急に影響が無い状態になりますと不自然に見えるかもしれないので、そんなときはこの引数でスポットライトの影響が弱まり始める角度を指定 スポットライトの向きに対してこの引数で指定する角度以上で且つ OutAngle 以下の場合はライトの影響が100%ではなくなります
            if (controlLight == -1)
                DxLib::GetLightAngle(&OutAngle, &InAngle);
            else
                DxLib::GetLightAngleHandle(SpotLightHandle, &OutAngle, &InAngle);

            float Range; // スポットライトの影響最大距離:距離以上の座標にある頂点は、 例え距離減衰計算の結果が0ではなくてもライトの影響は無くなる
            //ライトの影響力(%) = 100.0f / ( Atten0 + Atten1 * d + Atten2 * d * d ) [d = ライトから頂点への距離]
            // Attenは率を除算する値ですので、 非常に小さな値でも物凄くライトの影響範囲が狭まる
            float Atten0; // Atten0 はライトと頂点の距離に関係なく減衰する率を指定する引数
            float Atten1; // Atten1 はライトの距離に比例して減衰する率
            float Atten2; // Atten2 はライトの距離の二乗に比例して減衰する率
            if (controlLight == -1)
                DxLib::GetLightRangeAtten(&Range, &Atten0, &Atten1, &Atten2);
            else
                DxLib::GetLightRangeAttenHandle(SpotLightHandle, &Range, &Atten0, &Atten1, &Atten2);

            // ライトの位置に円錐を描画 [スポットライト] https://dxlib.xsrv.jp/function/dxfunc_3d_light.html#R13N3
            float coneScale = 15.0f; //         lightPos + lightDir * coneScale
            VECTOR lightPos1 = DxLib::GetLightPositionHandle(SpotLightHandle);
            VECTOR lightDir1 = DxLib::GetLightDirectionHandle(SpotLightHandle);
            DxLib::DrawCone3D(lightPos1, VAdd(lightPos1, VScale(lightDir1, coneScale)), InAngle * 2 * DX_PI_F, 8, GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE); //[自作円錐もあるが]https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4962
            DxLib::DrawCone3D(lightPos1, VAdd(lightPos1, VScale(lightDir1, coneScale)), OutAngle * 2 * DX_PI_F, 8, GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);

            if (lightType == DX_LIGHTTYPE_SPOT) // [スポットライト情報]
            {
                DxLib::DrawFormatString(0, 200 + 30,  GetColor(255, 255, 255), "Position     %f %f %f", lightPos1.x, lightPos1.y, lightPos1.z);
                DxLib::DrawFormatString(0, 200 + 60,  GetColor(255, 255, 255), "Direction    %f %f %f", lightDir1.x, lightDir1.y, lightDir1.z);
                DxLib::DrawFormatString(0, 200 + 90,  GetColor(255, 255, 255), "InOutAngle   %f %f", InAngle, OutAngle);
                DxLib::DrawFormatString(0, 200 + 120, GetColor(255, 255, 255), "Range        %f", Range);
                DxLib::DrawFormatString(0, 200 + 150, GetColor(255, 255, 255), "Attenuation  %f %f %f", Atten0, Atten1, Atten2);
            }
        }

        // [ポイントライト表示]
        {
            // [ポイントライト] 有効距離と距離減衰パラメータ https://dxlib.xsrv.jp/function/dxfunc_3d_light.html#R13N2
            float Range; // ポイントライトの影響最大距離:距離以上の座標にある頂点は、 例え距離減衰計算の結果が0ではなくてもライトの影響は無くなる
            //ライトの影響力(%) = 100.0f / ( Atten0 + Atten1 * d + Atten2 * d * d ) [d = ライトから頂点への距離]
            // Attenは率を除算する値ですので、 非常に小さな値でも物凄くライトの影響範囲が狭まる
            float Atten0; // Atten0 はライトと頂点の距離に関係なく減衰する率を指定する引数
            float Atten1; // Atten1 はライトの距離に比例して減衰する率
            float Atten2; // Atten2 はライトの距離の二乗に比例して減衰する率
            if (controlLight == -1)
                DxLib::GetLightRangeAtten(&Range, &Atten0, &Atten1, &Atten2);
            else
                DxLib::GetLightRangeAttenHandle(controlLight, &Range, &Atten0, &Atten1, &Atten2);

            // ライトの位置に球を描画 [ポイントライト]
            float scale = 10.0f;
            VECTOR lightPos2 = DxLib::GetLightPositionHandle(PointLightHandle);
            DxLib::DrawSphere3D(lightPos2, scale, 4, GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);

            if (lightType == DX_LIGHTTYPE_POINT) // [ポイントライト情報]
            {
                DxLib::DrawFormatString(0, 200 + 30,  GetColor(255, 255, 255), "Position     %f %f %f", lightPos2.x, lightPos2.y, lightPos2.z);
                DxLib::DrawFormatString(0, 200 + 120, GetColor(255, 255, 255), "Range        %f", Range);
                DxLib::DrawFormatString(0, 200 + 150, GetColor(255, 255, 255), "Attenuation  %f %f %f", Atten0, Atten1, Atten2);
            }
        }

        editor.Draw(); // エディタのDraw処理

        ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    WaitKey();
    // DXライブラリの後始末
    DxLib_End();
    // ソフトの終了
    return 0;
}



TestVS.fxを変更して光の蓄積値の計算にマテリアルの設定値を反映させましょう。


(前略).........

// main関数
VS_OUTPUT main( VS_INPUT VSInput )
{
    VS_OUTPUT VSOutput ;
    float4 lLocalPosition; // ローカル座標
    float4 lWorldPosition; // ワールド座標
    float4 lViewPosition; // 視線ビュー座標
   
    float3 lWorldNrm; // ワールド法線ベクトル
    float3 lViewNrm; // 視線ビュー法線ベクトル
   
    // ライティングの計算に必要な定義
    float3 lLightHalfVec; // ハーフベクトルの計算 norm( ( norm( 頂点位置からビュー視点へのベクトル ) + ライトの方向 ) )
    float4 lLightLitParam; // ライトのパラメータ 反射角度などの内積の値をもとめておく
    float4 lLightLitDest; // lit関数に上記のライトの内積パラメータを入力して光の減衰率の係数をもとめる
    float3 lLightDir; // 光源の向いている方向のベクトル(ディレクショナルライトやスポットライトの方向ベクトルがここに来る)
   
    // ディフューズ:拡散反射光 と スペキュラ:鏡面反射光の合計トータル蓄積値
    float3 lTotalDiffuse; // 拡散反射光のトータル蓄積値
    float3 lTotalSpecular; // 鏡面反射光のトータル蓄積値
   
    // 頂点座標変換 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 開始 )
    // ローカル座標のセット
    lLocalPosition.xyz = VSInput.Position;
    lLocalPosition.w = 1.0f;
    // ローカル座標をワールド座標に変換 [3D座標がスクリーンに投影されるための行列変換の過程] https://light11.hatenadiary.com/entry/2019/01/27/160541
    lWorldPosition.x = dot(lLocalPosition, g_Base.LocalWorldMatrix[0]);
    lWorldPosition.y = dot(lLocalPosition, g_Base.LocalWorldMatrix[1]);
    lWorldPosition.z = dot(lLocalPosition, g_Base.LocalWorldMatrix[2]);
    lWorldPosition.w = 1.0f;
    // ワールド座標をビュー座標に変換 [ワールド座標のカメラの位置が原点(0,0,0)になる行列変換] https://light11.hatenadiary.com/entry/2019/01/27/160541
    lViewPosition.x = dot(lWorldPosition, g_Base.ViewMatrix[0]);
    lViewPosition.y = dot(lWorldPosition, g_Base.ViewMatrix[1]);
    lViewPosition.z = dot(lWorldPosition, g_Base.ViewMatrix[2]);
    lViewPosition.w = 1.0f;
    // 視線ビュー座標をプロジェクション射影座標に変換 https://light11.hatenadiary.com/entry/2019/01/27/160541
    VSOutput.Position.x = dot(lViewPosition, g_Base.ProjectionMatrix[0]);
    VSOutput.Position.y = dot(lViewPosition, g_Base.ProjectionMatrix[1]);
    VSOutput.Position.z = dot(lViewPosition, g_Base.ProjectionMatrix[2]);
    VSOutput.Position.w = dot(lViewPosition, g_Base.ProjectionMatrix[3]);
    // 頂点座標変換 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 終了 )

   
    // 法線をビュー空間の角度に変換 =====================================================( 開始 )
    // ローカル法線ベクトル:VSInput.Normal を ワールド法線ベクトル:lWorldNrm に変換
    lWorldNrm.x = dot(VSInput.Normal, g_Base.LocalWorldMatrix[0].xyz);
    lWorldNrm.y = dot(VSInput.Normal, g_Base.LocalWorldMatrix[1].xyz);
    lWorldNrm.z = dot(VSInput.Normal, g_Base.LocalWorldMatrix[2].xyz);
    // ワールド法線ベクトル:lWorldNrm を ビュー法線ベクトル:lViewNrm に変換
    lViewNrm.x = dot(lWorldNrm, g_Base.ViewMatrix[0].xyz);
    lViewNrm.y = dot(lWorldNrm, g_Base.ViewMatrix[1].xyz);
    lViewNrm.z = dot(lWorldNrm, g_Base.ViewMatrix[2].xyz);
    // 法線をビュー空間の角度に変換 =====================================================( 終了 )

   
    //[※注意!ディレクショナルライト→スポットライト→ポイントライトの順に勝手にg_Common.Light[0],[1],[2]は並べ変わる]
    // https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=5605
   
    // ディレクショナルライトの処理 g_Common.Light[0]を使う******************************( 開始 )
    // ライトの方向セット
    lLightDir = g_Common.Light[0].Direction;
    // ライトディフューズカラーとライトスペキュラカラーの角度減衰計算 =======( 開始 )
    // 法線とライトの逆方向ベクトルとの内積を lLightLitParam.x にセット
    lLightLitParam.x = dot(lViewNrm, -lLightDir);
    // ハーフベクトルの計算 norm( ( norm( 頂点位置から視点へのベクトル ) + ライトの方向 ) )
    lLightHalfVec = normalize(normalize(-lViewPosition.xyz) - lLightDir);
    // 法線とハーフベクトルの内積を lLightLitParam.y にセット [法線とライトベクトルの内積で平面がカメラに向いている割合計算] https://someiyoshino.info/entry/20171231/1514692823
    lLightLitParam.y = dot(lLightHalfVec, lViewNrm);
    // スペキュラ反射率を lLightLitParam.w にセット
    lLightLitParam.w = g_Common.Material.Power;
    // ライト計算 [HLSL言語のlit関数] https://learn.microsoft.com/ja-jp/windows/win32/direct3dhlsl/dx-graphics-hlsl-lit
    lLightLitDest = lit(lLightLitParam.x, lLightLitParam.y, lLightLitParam.w);
    // ライトディフューズカラーとライトスペキュラカラーの角度減衰計算 =======( 終了 )
    // カラー計算 ===========================================================( 開始 )
    // ディフューズライト蓄積値 += ディフューズ角度減衰計算結果 * ライトのディフューズカラー * マテリアルディフューズカラー + ライトのアンビエントカラー
    lTotalDiffuse += lLightLitDest.y * g_Common.Light[0].Diffuse * g_Common.Material.Diffuse.xyz + g_Common.Light[0].Ambient.xyz;
    // スペキュラライト蓄積値 += スペキュラ角度減衰計算結果 * ライトのスペキュラカラー
    lTotalSpecular += lLightLitDest.z * g_Common.Light[0].Specular;
    // カラー計算 ===========================================================( 終了 )
    // ディレクショナルライトの処理 *****************************************************( 終了 )

    // ディフューズカラー = ディフューズライト蓄積値 + マテリアルのアンビエントカラーとグローバルアンビエントカラーを乗算したものとマテリアルエミッシブカラーを加算したもの
    VSOutput.Diffuse.xyz = lTotalDiffuse + g_Common.Material.Ambient_Emissive.xyz;
    // ディフューズのアルファはマテリアルのディフューズカラーのアルファをそのまま使う
    VSOutput.Diffuse.w = g_Common.Material.Diffuse.w;

    // スペキュラカラー = スペキュラライト蓄積値 * マテリアルのスペキュラカラー
    VSOutput.Specular.xyz = lTotalSpecular * g_Common.Material.Specular.xyz;
   
   
    // テクスチャ座標変換行列による変換を行った結果のテクスチャ座標をセット
    VSOutput.TexCoords0.x = dot(VSInput.TexCoords0, g_OtherMatrix.TextureMatrix[0][0]);
    VSOutput.TexCoords0.y = dot(VSInput.TexCoords0, g_OtherMatrix.TextureMatrix[0][1]);


    // 出力パラメータを返す
    return VSOutput ;
}

TestVS.fxを変更したら保存して、コマンドプロンプトから再びShaderCompiler.exeのコマンドでビルドしなおします。(キーボードの上キーを押すと過去に実行したコマンドが出てきて楽)

いかがでしょう?実行したら、サイコロの表面にディレクショナルライトの緑色(赤と混ざって黄色っぽい)とサイコロ自身の自己発光の赤が表示されましたか?
キーボードの上下左右PageDown,PageUpボタンを押して、ディレクショナルライトの方向が変わると影になる面は赤に光っていることを確認してみましょう。

さて、マテリアルの設定値には、ディフューズ:拡散光、以外に
スペキュラ:鏡面反射光の色
Power値:スペキュラ鏡面反射の角度による減衰強度(反射の鋭さ)を決める
エミッシブ:自己発光の色(ライトの光とは関係なく、3Dの物体自体が自分で発光して表面が光って見せるための色の設定)
アンビエント:環境光の反射色(ディフューズはカメラとポリゴン面に垂直な法線の影響を受けるが、アンビエントは法線の影響なくライトが当たる範囲内なら表面が光ってみえる)
などの設定値があります。
簡単にイメージするならば、
スペキュラ:鏡面反射 は スプーンのテカテカを表現するもので、Power値はそのスプーンの表面のキラキラ具合(Power値が大きいと反射する部分の面積が小さくするどく反射)
エミッシブ:自己発光色は 魔法石が内部から自分で光って見えたり、夜のネオン看板の文字が自己発光するような表現に使うイメージ
アンビエント:環境光 は 直接光が当たっていない裏側でも周りにある光でうっすらと裏側にも色がついて見えるようなイメージ
といった風に覚えておくとよいです。

計算式を見ると、マテリアルのエミッシブとアンビエントは VSOutput.Diffuse.xyz = lTotalDiffuse + g_Common.Material.Ambient_Emissive.xyz; のように法線の計算式はすっとばして、Totalに直接足してある形です。
このように、数式レベルでどういった計算がされるかを知った上で、3D空間のライティングをイメージすることが大事です。

これを機に、計算方式には名前があるので勉強しておきましょう。
まず、ライトの光源の逆ベクトルとポリゴンの面からの法線内積dotを計算している計算部分、これはランバート(Lambert)の反射モデル(wiki)だ。
つぎに、Power値を設定して鏡面反射を計算している部分、こちらはフォン(Phong)のライティング(wiki)だ。
よく出てくるこの2つの言葉ぐらいは基礎として覚えておいた方がいい。古典的シェーディングの用語だ。
そして、古典的シェーディングと違う、最近のシェーディング方式は、物理ベースシェーディング(PBS:Physically-Based Shading:wiki)と呼ばれる。BRDFなど複雑な計算をするものがある。
DXライブラリのMV1のデフォルトのシェーダーの中身をみると、ランバート反射やフォンの記述があるので古典的シェーディングをしているとわかる。


さて、では次は、ディレクショナルライトだけでなくポイントライトの光の蓄積値も足し算して、ポイントライトの光もサイコロに反射するようにしてみよう。

main.cppのコードを変更してサイコロの表面のエミッシブ:自己発光させないようにしておきましょう。色が混ざってみにくいので。


(前略)....
    MATERIALPARAM diceMaterial; // サイコロの表面を設定するためのマテリアル構造体
    {   // ディフューズ色,アンビエント色,スペキュラ色,エミッシブ色,
        // ディフューズ拡散光の反射色(ポリゴンの面の法線角度が影響)
        COLOR_F    Diffuse; Diffuse.r = 1.0f; Diffuse.g = 1.0f; Diffuse.b = 1.0f; Diffuse.a = 1.0f;
        // アンビエント環境光の反射色(法線の影響なくライトが当たる範囲内なら表面が光ってみえる)
        COLOR_F    Ambient; Ambient.r = 0.3f; Ambient.g = 0.3f; Ambient.b = 0.3f; Ambient.a = 1.0f;
        // エミッシブ自己発光の色(ライトの光ではなく、3Dの物体自体が自分で発光して表面が光って見える)
        COLOR_F    Emissive; Emissive.r = 0.0f; Emissive.g = 0.0f; Emissive.b = 0.0f; Emissive.a = 1.0f; // 光があたっていなくても赤色に光るようにEmissive.r = 0.5f;にする
        // スペキュラ鏡面反射光の色(スプーンなどの表面のように面の法線と視点の角度が狭いと鋭く反射して見える)
        COLOR_F    Specular; Specular.r = 0.5f; Specular.g = 0.5f; Specular.b = 0.5f; Specular.a = 1.0f;
        float Power = 20.0f; // スペキュラ反射の角度による減衰強度、大きい数字になると反射する面積が小さくなり鋭くキラッと反射して見える
        diceMaterial.Diffuse = Diffuse;
        diceMaterial.Ambient = Ambient;
        diceMaterial.Emissive = Emissive;
        diceMaterial.Specular = Specular;
        diceMaterial.Power = Power;
    }

(後略).........

TestVS.fxを変更して光の蓄積値の計算にポイントライトの光を反映させましょう。


(前略).........

// main関数
VS_OUTPUT main( VS_INPUT VSInput )
{
    VS_OUTPUT VSOutput ;
    float4 lLocalPosition; // ローカル座標
    float4 lWorldPosition; // ワールド座標
    float4 lViewPosition; // 視線ビュー座標
   
    float3 lWorldNrm; // ワールド法線ベクトル
    float3 lViewNrm; // 視線ビュー法線ベクトル
   
    // ライティングの計算に必要な定義
    float3 lLightHalfVec; // ハーフベクトルの計算 norm( ( norm( 頂点位置からビュー視点へのベクトル ) + ライトの方向 ) )
    float4 lLightLitParam; // ライトのパラメータ 反射角度などの内積の値をもとめておく
    float4 lLightLitDest; // lit関数に上記のライトの内積パラメータを入力して光の減衰率の係数をもとめる
    float3 lLightDir; // 光源の向いている方向のベクトル(ディレクショナルライトやスポットライトの方向ベクトルがここに来る)
    float3 lLightTemp; // 頂点とライト位置との距離を2乗する前に一旦Tempに保管してそれを2乗する
    float lLightDistancePow2; // 頂点とライト位置との距離の2乗
    float lLightGen; // ライトの減衰率の計算

   
    // ディフューズ:拡散反射光 と スペキュラ:鏡面反射光の合計トータル蓄積値
    float3 lTotalDiffuse; // 拡散反射光のトータル蓄積値
    float3 lTotalSpecular; // 鏡面反射光のトータル蓄積値
   
    // 頂点座標変換 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 開始 )
    // ローカル座標のセット
    lLocalPosition.xyz = VSInput.Position;
    lLocalPosition.w = 1.0f;
    // ローカル座標をワールド座標に変換 [3D座標がスクリーンに投影されるための行列変換の過程] https://light11.hatenadiary.com/entry/2019/01/27/160541
    lWorldPosition.x = dot(lLocalPosition, g_Base.LocalWorldMatrix[0]);
    lWorldPosition.y = dot(lLocalPosition, g_Base.LocalWorldMatrix[1]);
    lWorldPosition.z = dot(lLocalPosition, g_Base.LocalWorldMatrix[2]);
    lWorldPosition.w = 1.0f;
    // ワールド座標をビュー座標に変換 [ワールド座標のカメラの位置が原点(0,0,0)になる行列変換] https://light11.hatenadiary.com/entry/2019/01/27/160541
    lViewPosition.x = dot(lWorldPosition, g_Base.ViewMatrix[0]);
    lViewPosition.y = dot(lWorldPosition, g_Base.ViewMatrix[1]);
    lViewPosition.z = dot(lWorldPosition, g_Base.ViewMatrix[2]);
    lViewPosition.w = 1.0f;
    // 視線ビュー座標をプロジェクション射影座標に変換 https://light11.hatenadiary.com/entry/2019/01/27/160541
    VSOutput.Position.x = dot(lViewPosition, g_Base.ProjectionMatrix[0]);
    VSOutput.Position.y = dot(lViewPosition, g_Base.ProjectionMatrix[1]);
    VSOutput.Position.z = dot(lViewPosition, g_Base.ProjectionMatrix[2]);
    VSOutput.Position.w = dot(lViewPosition, g_Base.ProjectionMatrix[3]);
    // 頂点座標変換 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 終了 )

   
    // 法線をビュー空間の角度に変換 =====================================================( 開始 )
    // ローカル法線ベクトル:VSInput.Normal を ワールド法線ベクトル:lWorldNrm に変換
    lWorldNrm.x = dot(VSInput.Normal, g_Base.LocalWorldMatrix[0].xyz);
    lWorldNrm.y = dot(VSInput.Normal, g_Base.LocalWorldMatrix[1].xyz);
    lWorldNrm.z = dot(VSInput.Normal, g_Base.LocalWorldMatrix[2].xyz);
    // ワールド法線ベクトル:lWorldNrm を ビュー法線ベクトル:lViewNrm に変換
    lViewNrm.x = dot(lWorldNrm, g_Base.ViewMatrix[0].xyz);
    lViewNrm.y = dot(lWorldNrm, g_Base.ViewMatrix[1].xyz);
    lViewNrm.z = dot(lWorldNrm, g_Base.ViewMatrix[2].xyz);
    // 法線をビュー空間の角度に変換 =====================================================( 終了 )

   
    //[※注意!ディレクショナルライト→スポットライト→ポイントライトの順に勝手にg_Common.Light[0],[1],[2]は並べ変わる]
    // https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=5605
   
    // ディレクショナルライトの処理 g_Common.Light[0]を使う******************************( 開始 )
    // ライトの方向セット
    lLightDir = g_Common.Light[0].Direction;
    // ライトディフューズカラーとライトスペキュラカラーの角度減衰計算 =======( 開始 )
    // 法線とライトの逆方向ベクトルとの内積を lLightLitParam.x にセット
    lLightLitParam.x = dot(lViewNrm, -lLightDir);
    // ハーフベクトルの計算 norm( ( norm( 頂点位置から視点へのベクトル ) + ライトの方向 ) )
    lLightHalfVec = normalize(normalize(-lViewPosition.xyz) - lLightDir);
    // 法線とハーフベクトルの内積を lLightLitParam.y にセット [法線とライトベクトルの内積で平面がカメラに向いている割合計算] https://someiyoshino.info/entry/20171231/1514692823
    lLightLitParam.y = dot(lLightHalfVec, lViewNrm);
    // スペキュラ反射率を lLightLitParam.w にセット
    lLightLitParam.w = g_Common.Material.Power;
    // ライト計算 [HLSL言語のlit関数] https://learn.microsoft.com/ja-jp/windows/win32/direct3dhlsl/dx-graphics-hlsl-lit
    lLightLitDest = lit(lLightLitParam.x, lLightLitParam.y, lLightLitParam.w);
    // ライトディフューズカラーとライトスペキュラカラーの角度減衰計算 =======( 終了 )
    // カラー計算 ===========================================================( 開始 )
    // ディフューズライト蓄積値 += ディフューズ角度減衰計算結果 * ライトのディフューズカラー * マテリアルディフューズカラー + ライトのアンビエントカラー
    lTotalDiffuse += lLightLitDest.y * g_Common.Light[0].Diffuse * g_Common.Material.Diffuse.xyz + g_Common.Light[0].Ambient.xyz;
    // スペキュラライト蓄積値 += スペキュラ角度減衰計算結果 * ライトのスペキュラカラー
    lTotalSpecular += lLightLitDest.z * g_Common.Light[0].Specular;
    // カラー計算 ===========================================================( 終了 )
    // ディレクショナルライトの処理 *****************************************************( 終了 )

    // ポイントライトの処理 g_Common.Light[2]を使う ***************************************( 開始 )
    // ライトの位置からカメラのビュー位置へ向かう単位ベクトルの計算
    lLightDir = normalize(lViewPosition.xyz - g_Common.Light[2].Position.xyz);
    // 距離減衰値計算 ======================================================( 開始 )
    // 頂点とライト位置との距離の二乗を求める
    lLightTemp = lViewPosition.xyz - g_Common.Light[2].Position.xyz;
    lLightDistancePow2 = dot(lLightTemp, lLightTemp);
    // 減衰率の計算 lLightGen = 1 / ( 減衰値0 + 減衰値1 * 距離 + 減衰値2 * ( 距離 * 距離 ) )
    lLightGen = 1.0f / (g_Common.Light[2].Attenuation0 + g_Common.Light[2].Attenuation1 * sqrt(lLightDistancePow2) + g_Common.Light[2].Attenuation2 * lLightDistancePow2);
    // 有効距離外だったら減衰率を最大にする処理 [HLSLのstep(y,x)関数 xがy以上なら1,未満なら0] https://learn.microsoft.com/ja-jp/windows/win32/direct3dhlsl/dx-graphics-hlsl-step
    lLightGen *= step(lLightDistancePow2, g_Common.Light[2].RangePow2);
    // 距離減衰値計算 =======================================================( 終了 )
    // ライトディフューズカラーとライトスペキュラカラーの角度減衰計算 ===================( 開始 )
    // 法線とライトの逆方向ベクトルとの内積を lLightLitParam.x にセット
    lLightLitParam.x = dot(lViewNrm, -lLightDir);
    // ハーフベクトルの計算 norm( ( norm( 頂点位置から視点へのベクトル ) + ライトの方向 ) )
    lLightHalfVec = normalize(normalize(-lViewPosition.xyz) - lLightDir);
    // 法線とハーフベクトルの内積を lLightLitParam.y にセット https://someiyoshino.info/entry/20171231/1514692823
    lLightLitParam.y = dot(lLightHalfVec, lViewNrm);
    // スペキュラ反射率を lLightLitParam.w にセット
    lLightLitParam.w = g_Common.Material.Power;
    // ライトパラメータ計算
    lLightLitDest = lit(lLightLitParam.x, lLightLitParam.y, lLightLitParam.w);
    // ライトディフューズカラーとライトスペキュラカラーの角度減衰計算 ==================( 終了 )
    // カラー計算 ===========================================================( 開始 )
    // ディフーズライト蓄積値 += 距離・スポットライト角度減衰値 * ( ディフーズ角度減衰計算結果 * マテリアルディフューズカラー * ライトのディフーズカラー + ライトのアンビエントカラーとマテリアルのアンビエントカラーを乗算したもの )
    lTotalDiffuse += lLightGen * (lLightLitDest.y * g_Common.Light[2].Diffuse * g_Common.Material.Diffuse.xyz + g_Common.Light[2].Ambient.xyz);
    // スペキュラライト蓄積値 += スペキュラ角度減衰計算結果 * 距離・スポットライト減衰 * ライトのスペキュラカラー
    lTotalSpecular += lLightGen * lLightLitDest.z * g_Common.Light[2].Specular;
    // カラー計算 ===========================================================( 終了 )
    // ポイントライトの処理 ************************************************************( 終了 )


    // ディフューズカラー = ディフューズライト蓄積値 + マテリアルのアンビエントカラーとグローバルアンビエントカラーを乗算したものとマテリアルエミッシブカラーを加算したもの
    VSOutput.Diffuse.xyz = lTotalDiffuse + g_Common.Material.Ambient_Emissive.xyz;
    // ディフューズのアルファはマテリアルのディフューズカラーのアルファをそのまま使う
    VSOutput.Diffuse.w = g_Common.Material.Diffuse.w;
    // スペキュラカラー = スペキュラライト蓄積値 * マテリアルのスペキュラカラー
    VSOutput.Specular.xyz = lTotalSpecular * g_Common.Material.Specular.xyz;
   
   
    // テクスチャ座標変換行列による変換を行った結果のテクスチャ座標をセット
    VSOutput.TexCoords0.x = dot(VSInput.TexCoords0, g_OtherMatrix.TextureMatrix[0][0]);
    VSOutput.TexCoords0.y = dot(VSInput.TexCoords0, g_OtherMatrix.TextureMatrix[0][1]);


    // 出力パラメータを返す
    return VSOutput ;
}

TestVS.fxを変更したら保存して、コマンドプロンプトから再びShaderCompiler.exeのコマンドでビルドしなおします。(キーボードの上キーを押すと過去に実行したコマンドが出てきて楽)


いかがでしょう?キーボードのLボタンを押してポイントライトをコントロールするようにして、上下左右PageDown,PageUpボタンを押して、ポイントライトの位置を変えるとサイコロの表面の色にポイントライトのピンク色が見えますか?

ポイントライトのシェーダーの計算コードとディレクショナルライトの計算コードを比較してみてみましょう。
一番の違いは、lLightGen距離減衰に関する計算があるかないかです。


    // 距離減衰値計算 ======================================================( 開始 )
    // 頂点とライト位置との距離の二乗を求める
    lLightTemp = lViewPosition.xyz - g_Common.Light[2].Position.xyz;
    lLightDistancePow2 = dot(lLightTemp, lLightTemp);
    // 減衰率の計算 lLightGen = 1 / ( 減衰値0 + 減衰値1 * 距離 + 減衰値2 * ( 距離 * 距離 ) )
    lLightGen = 1.0f / (g_Common.Light[2].Attenuation0 + g_Common.Light[2].Attenuation1 * sqrt(lLightDistancePow2) + g_Common.Light[2].Attenuation2 * lLightDistancePow2);
    // 有効距離外だったら減衰率を最大にする処理 [HLSLのstep(y,x)関数 xがy以上なら1,未満なら0] https://learn.microsoft.com/ja-jp/windows/win32/direct3dhlsl/dx-graphics-hlsl-step
    lLightGen *= step(lLightDistancePow2, g_Common.Light[2].RangePow2);
    // 距離減衰値計算 =======================================================( 終了 )


(中略)...
    // ディフーズライト蓄積値 += 距離・スポットライト角度減衰値 * ( ディフーズ角度減衰計算結果 * マテリアルディフューズカラー * ライトのディフーズカラー + ライトのアンビエントカラーとマテリアルのアンビエントカラーを乗算したもの )
    lTotalDiffuse += lLightGen * (lLightLitDest.y * g_Common.Light[2].Diffuse * g_Common.Material.Diffuse.xyz + g_Common.Light[2].Ambient.xyz);
    // スペキュラライト蓄積値 += スペキュラ角度減衰計算結果 * 距離・スポットライト減衰 * ライトのスペキュラカラー
    lTotalSpecular += lLightGen * lLightLitDest.z * g_Common.Light[2].Specular;

ディレクショナルライトは3D世界全体をどこにいても一方向に照らす光なので、光の減衰を気にする必要がないですが、
ポイントライトはそのライトのまわりだけを照らす光なので、遠ざかると光が減衰していく必要があります。
なので、頂点の位置とポイントライトの位置の距離の2乗を求めて、
遠くなるほど lLightGen の数値が減衰するような計算式を使って、最終的に lLightGen を g_Common.Light[2]の拡散光などの光にかけ算して、光の強さを弱めてます

このように計算式を かけ算 や 割り算(分母) や 距離 などで読み解いて他のライトとの違いに着目すれば、ライティングの計算は意外に納得できる意味合いをもっている実感が湧くとよいなと思います。


では、最後に締めとして、一番複雑なスポットライトの光の蓄積値も足し算して、スポットライトの光もサイコロに反射するようにしてみよう。

TestVS.fxを変更して光の蓄積値の計算にスポットライトの光を反映させましょう。


(前略).........

// main関数
VS_OUTPUT main( VS_INPUT VSInput )
{
    VS_OUTPUT VSOutput ;
    float4 lLocalPosition; // ローカル座標
    float4 lWorldPosition; // ワールド座標
    float4 lViewPosition; // 視線ビュー座標
   
    float3 lWorldNrm; // ワールド法線ベクトル
    float3 lViewNrm; // 視線ビュー法線ベクトル
   
    // ライティングの計算に必要な定義
    float3 lLightHalfVec; // ハーフベクトルの計算 norm( ( norm( 頂点位置からビュー視点へのベクトル ) + ライトの方向 ) )
    float4 lLightLitParam; // ライトのパラメータ 反射角度などの内積の値をもとめておく
    float4 lLightLitDest; // lit関数に上記のライトの内積パラメータを入力して光の減衰率の係数をもとめる
    float3 lLightDir; // 光源の向いている方向のベクトル(ディレクショナルライトやスポットライトの方向ベクトルがここに来る)
    float3 lLightTemp; // 頂点とライト位置との距離を2乗する前に一旦Tempに保管してそれを2乗する
    float lLightDistancePow2; // 頂点とライト位置との距離の2乗
    float lLightGen; // ライトの減衰率の計算
    float lLightDirectionCosA; // カメラのビュー位置からライト方向へのベクトル と ライトの方向のベクトルの内積( 即ち Cos a )
   
    // ディフューズ:拡散反射光 と スペキュラ:鏡面反射光の合計トータル蓄積値
    float3 lTotalDiffuse; // 拡散反射光のトータル蓄積値
    float3 lTotalSpecular; // 鏡面反射光のトータル蓄積値
   
    // 頂点座標変換 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 開始 )
    // ローカル座標のセット
    lLocalPosition.xyz = VSInput.Position;
    lLocalPosition.w = 1.0f;
    // ローカル座標をワールド座標に変換 [3D座標がスクリーンに投影されるための行列変換の過程] https://light11.hatenadiary.com/entry/2019/01/27/160541
    lWorldPosition.x = dot(lLocalPosition, g_Base.LocalWorldMatrix[0]);
    lWorldPosition.y = dot(lLocalPosition, g_Base.LocalWorldMatrix[1]);
    lWorldPosition.z = dot(lLocalPosition, g_Base.LocalWorldMatrix[2]);
    lWorldPosition.w = 1.0f;
    // ワールド座標をビュー座標に変換 [ワールド座標のカメラの位置が原点(0,0,0)になる行列変換] https://light11.hatenadiary.com/entry/2019/01/27/160541
    lViewPosition.x = dot(lWorldPosition, g_Base.ViewMatrix[0]);
    lViewPosition.y = dot(lWorldPosition, g_Base.ViewMatrix[1]);
    lViewPosition.z = dot(lWorldPosition, g_Base.ViewMatrix[2]);
    lViewPosition.w = 1.0f;
    // 視線ビュー座標をプロジェクション射影座標に変換 https://light11.hatenadiary.com/entry/2019/01/27/160541
    VSOutput.Position.x = dot(lViewPosition, g_Base.ProjectionMatrix[0]);
    VSOutput.Position.y = dot(lViewPosition, g_Base.ProjectionMatrix[1]);
    VSOutput.Position.z = dot(lViewPosition, g_Base.ProjectionMatrix[2]);
    VSOutput.Position.w = dot(lViewPosition, g_Base.ProjectionMatrix[3]);
    // 頂点座標変換 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 終了 )

   
    // 法線をビュー空間の角度に変換 =====================================================( 開始 )
    // ローカル法線ベクトル:VSInput.Normal を ワールド法線ベクトル:lWorldNrm に変換
    lWorldNrm.x = dot(VSInput.Normal, g_Base.LocalWorldMatrix[0].xyz);
    lWorldNrm.y = dot(VSInput.Normal, g_Base.LocalWorldMatrix[1].xyz);
    lWorldNrm.z = dot(VSInput.Normal, g_Base.LocalWorldMatrix[2].xyz);
    // ワールド法線ベクトル:lWorldNrm を ビュー法線ベクトル:lViewNrm に変換
    lViewNrm.x = dot(lWorldNrm, g_Base.ViewMatrix[0].xyz);
    lViewNrm.y = dot(lWorldNrm, g_Base.ViewMatrix[1].xyz);
    lViewNrm.z = dot(lWorldNrm, g_Base.ViewMatrix[2].xyz);
    // 法線をビュー空間の角度に変換 =====================================================( 終了 )

   
    //[※注意!ディレクショナルライト→スポットライト→ポイントライトの順に勝手にg_Common.Light[0],[1],[2]は並べ変わる]
    // https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=5605
   
    // ディレクショナルライトの処理 g_Common.Light[0]を使う******************************( 開始 )
    // ライトの方向セット
    lLightDir = g_Common.Light[0].Direction;
    // ライトディフューズカラーとライトスペキュラカラーの角度減衰計算 =======( 開始 )
    // 法線とライトの逆方向ベクトルとの内積を lLightLitParam.x にセット
    lLightLitParam.x = dot(lViewNrm, -lLightDir);
    // ハーフベクトルの計算 norm( ( norm( 頂点位置から視点へのベクトル ) + ライトの方向 ) )
    lLightHalfVec = normalize(normalize(-lViewPosition.xyz) - lLightDir);
    // 法線とハーフベクトルの内積を lLightLitParam.y にセット [法線とライトベクトルの内積で平面がカメラに向いている割合計算] https://someiyoshino.info/entry/20171231/1514692823
    lLightLitParam.y = dot(lLightHalfVec, lViewNrm);
    // スペキュラ反射率を lLightLitParam.w にセット
    lLightLitParam.w = g_Common.Material.Power;
    // ライト計算 [HLSL言語のlit関数] https://learn.microsoft.com/ja-jp/windows/win32/direct3dhlsl/dx-graphics-hlsl-lit
    lLightLitDest = lit(lLightLitParam.x, lLightLitParam.y, lLightLitParam.w);
    // ライトディフューズカラーとライトスペキュラカラーの角度減衰計算 =======( 終了 )
    // カラー計算 ===========================================================( 開始 )
    // ディフューズライト蓄積値 += ディフューズ角度減衰計算結果 * ライトのディフューズカラー * マテリアルディフューズカラー + ライトのアンビエントカラー
    lTotalDiffuse += lLightLitDest.y * g_Common.Light[0].Diffuse * g_Common.Material.Diffuse.xyz + g_Common.Light[0].Ambient.xyz;
    // スペキュラライト蓄積値 += スペキュラ角度減衰計算結果 * ライトのスペキュラカラー
    lTotalSpecular += lLightLitDest.z * g_Common.Light[0].Specular;
    // カラー計算 ===========================================================( 終了 )
    // ディレクショナルライトの処理 *****************************************************( 終了 )

    // スポットライトの処理 g_Common.Light[1]を使う****************************************( 開始 )
    // ライトの位置からカメラのビュー位置へ向かう単位ベクトルの計算
    lLightDir = normalize(lViewPosition.xyz - g_Common.Light[1].Position.xyz);
    // 距離・スポットライト減衰値計算 =======================================( 開始 )
    // 距離減衰計算 ------------------
    // 頂点とライト位置との距離の二乗を求める
    lLightTemp = lViewPosition.xyz - g_Common.Light[1].Position.xyz;
    lLightDistancePow2 = dot(lLightTemp, lLightTemp);
    // 減衰率の計算 lLightGen = 1 / ( 減衰値0 + 減衰値1 * 距離 + 減衰値2 * ( 距離 * 距離 ) )
    lLightGen = 1.0f / (g_Common.Light[1].Attenuation0 + g_Common.Light[1].Attenuation1 * sqrt(lLightDistancePow2) + g_Common.Light[1].Attenuation2 * lLightDistancePow2);
    // スポットライト減衰計算 --------
    // ライト方向ベクトルとライト位置から頂点位置へのベクトルの内積( 即ち Cos a )を計算
    lLightDirectionCosA = dot(lLightDir, g_Common.Light[1].Direction); // ↓cosの式の由来 https://zenn.dev/artemisia/articles/213664a98659df
    // スポットライト減衰計算  pow( falloff, ( ( Cos a - Cos f ) / ( Cos q - Cos f ) ) )  ※SpotParam0 =  cos( Phi / 2.0f ) ※SpotParam1 = 1.0f / ( cos( Theta / 2.0f ) - cos( Phi / 2.0f ) )
    lLightGen *= saturate(pow(abs(max(lLightDirectionCosA - g_Common.Light[1].SpotParam0, 0.0f) * g_Common.Light[1].SpotParam1), g_Common.Light[1].FallOff));
    // 有効距離外だったら減衰率を最大にする処理
    lLightGen *= step(lLightDistancePow2, g_Common.Light[1].RangePow2);
    // 距離・スポットライト減衰値計算 =======================================( 終了 )
    // ライトディフューズカラーとライトスペキュラカラーの角度減衰計算 =======( 開始 )
    // 法線とライトの逆方向ベクトルとの内積を lLightLitParam.x にセット
    lLightLitParam.x = dot(lViewNrm, -lLightDir);
    // ハーフベクトルの計算 norm( ( norm( 頂点位置から視点へのベクトル ) + ライトの方向 ) )
    lLightHalfVec = normalize(normalize(-lViewPosition.xyz) - lLightDir);
    // 法線とハーフベクトルの内積を lLightLitParam.y にセット https://someiyoshino.info/entry/20171231/1514692823
    lLightLitParam.y = dot(lLightHalfVec, lViewNrm);
    // スペキュラ反射率を lLightLitParam.w にセット
    lLightLitParam.w = g_Common.Material.Power;
    // ライト計算
    lLightLitDest = lit(lLightLitParam.x, lLightLitParam.y, lLightLitParam.w);
    // ライトディフューズカラーとライトスペキュラカラーの角度減衰計算 =======( 終了 )
    // カラー計算 ===========================================================( 開始 )
    // ディフーズライト蓄積値 += 距離・スポットライト角度減衰値 * ( ディフーズ角度減衰計算結果 * マテリアルディフューズカラー * ライトのディフーズカラー + ライトのアンビエントカラーとマテリアルのアンビエントカラーを乗算したもの )
    lTotalDiffuse += lLightGen * (lLightLitDest.y * g_Common.Light[1].Diffuse * g_Common.Material.Diffuse.xyz + g_Common.Light[1].Ambient.xyz);
    // スペキュラライト蓄積値 += スペキュラ角度減衰計算結果 * 距離・スポットライト減衰 * ライトのスペキュラカラー
    lTotalSpecular += lLightGen * lLightLitDest.z * g_Common.Light[1].Specular;
    // カラー計算 ===========================================================( 終了 )
    // スポットライトの処理 *************************************************************( 終了 )


    // ポイントライトの処理 g_Common.Light[2]を使う ***************************************( 開始 )
    // ライトの位置からカメラのビュー位置へ向かうベクトルの計算
    lLightDir = normalize(lViewPosition.xyz - g_Common.Light[2].Position.xyz);
    // 距離減衰値計算 ======================================================( 開始 )
    // 頂点とライト位置との距離の二乗を求める
    lLightTemp = lViewPosition.xyz - g_Common.Light[2].Position.xyz;
    lLightDistancePow2 = dot(lLightTemp, lLightTemp);
    // 減衰率の計算 lLightGen = 1 / ( 減衰値0 + 減衰値1 * 距離 + 減衰値2 * ( 距離 * 距離 ) )
    lLightGen = 1.0f / (g_Common.Light[2].Attenuation0 + g_Common.Light[2].Attenuation1 * sqrt(lLightDistancePow2) + g_Common.Light[2].Attenuation2 * lLightDistancePow2);
    // 有効距離外だったら減衰率を最大にする処理 [HLSLのstep(y,x)関数 xがy以上なら1,未満なら0] https://learn.microsoft.com/ja-jp/windows/win32/direct3dhlsl/dx-graphics-hlsl-step
    lLightGen *= step(lLightDistancePow2, g_Common.Light[2].RangePow2);
    // 距離減衰値計算 =======================================================( 終了 )
    // ライトディフューズカラーとライトスペキュラカラーの角度減衰計算 ===================( 開始 )
    // 法線とライトの逆方向ベクトルとの内積を lLightLitParam.x にセット
    lLightLitParam.x = dot(lViewNrm, -lLightDir);
    // ハーフベクトルの計算 norm( ( norm( 頂点位置から視点へのベクトル ) + ライトの方向 ) )
    lLightHalfVec = normalize(normalize(-lViewPosition.xyz) - lLightDir);
    // 法線とハーフベクトルの内積を lLightLitParam.y にセット https://someiyoshino.info/entry/20171231/1514692823
    lLightLitParam.y = dot(lLightHalfVec, lViewNrm);
    // スペキュラ反射率を lLightLitParam.w にセット
    lLightLitParam.w = g_Common.Material.Power;
    // ライトパラメータ計算
    lLightLitDest = lit(lLightLitParam.x, lLightLitParam.y, lLightLitParam.w);
    // ライトディフューズカラーとライトスペキュラカラーの角度減衰計算 ==================( 終了 )
    // カラー計算 ===========================================================( 開始 )
    // ディフーズライト蓄積値 += 距離・スポットライト角度減衰値 * ( ディフーズ角度減衰計算結果 * マテリアルディフューズカラー * ライトのディフーズカラー + ライトのアンビエントカラーとマテリアルのアンビエントカラーを乗算したもの )
    lTotalDiffuse += lLightGen * (lLightLitDest.y * g_Common.Light[2].Diffuse * g_Common.Material.Diffuse.xyz + g_Common.Light[2].Ambient.xyz);
    // スペキュラライト蓄積値 += スペキュラ角度減衰計算結果 * 距離・スポットライト減衰 * ライトのスペキュラカラー
    lTotalSpecular += lLightGen * lLightLitDest.z * g_Common.Light[2].Specular;
    // カラー計算 ===========================================================( 終了 )
    // ポイントライトの処理 ************************************************************( 終了 )

    // ディフューズカラー = ディフューズライト蓄積値 + マテリアルのアンビエントカラーとグローバルアンビエントカラーを乗算したものとマテリアルエミッシブカラーを加算したもの
    VSOutput.Diffuse.xyz = lTotalDiffuse + g_Common.Material.Ambient_Emissive.xyz;
    // ディフューズのアルファはマテリアルのディフューズカラーのアルファをそのまま使う
    VSOutput.Diffuse.w = g_Common.Material.Diffuse.w;
    // スペキュラカラー = スペキュラライト蓄積値 * マテリアルのスペキュラカラー
    VSOutput.Specular.xyz = lTotalSpecular * g_Common.Material.Specular.xyz;
   
   
    // テクスチャ座標変換行列による変換を行った結果のテクスチャ座標をセット
    VSOutput.TexCoords0.x = dot(VSInput.TexCoords0, g_OtherMatrix.TextureMatrix[0][0]);
    VSOutput.TexCoords0.y = dot(VSInput.TexCoords0, g_OtherMatrix.TextureMatrix[0][1]);


    // 出力パラメータを返す
    return VSOutput ;
}

TestVS.fxを変更したら保存して、コマンドプロンプトから再びShaderCompiler.exeのコマンドでビルドしなおします。(キーボードの上キーを押すと過去に実行したコマンドが出てきて楽)

いかがでしょう?実行したらキーボードのLボタンを押して、スポットライトに切り替えて、上下左右PageDown,PageUpボタンを押して、スポットライトを動かすとスポットライトの当たった面が水色に表示されましたか?

スポットライトとポイントライトを比較すると、違いを生み出しているコアとなっている計算の個所は以下の部位です。


(前略).........

    // スポットライト減衰計算 --------
    // ライト方向ベクトルとライト位置から頂点位置へのベクトルの内積( 即ち Cos a )を計算
    lLightDirectionCosA = dot(lLightDir, g_Common.Light[1].Direction); // ↓cosの式の由来 https://zenn.dev/artemisia/articles/213664a98659df
    // スポットライト減衰計算  pow( falloff, ( ( Cos a - Cos f ) / ( Cos q - Cos f ) ) )  ※SpotParam0 = cos( Phi / 2.0f )SpotParam1 = 1.0f / ( cos( Theta / 2.0f ) - cos( Phi / 2.0f ) )
    lLightGen *= saturate(pow(abs(max(lLightDirectionCosA - g_Common.Light[1].SpotParam0, 0.0f) * g_Common.Light[1].SpotParam1), g_Common.Light[1].FallOff));
    // 有効距離外だったら減衰率を最大にする処理
    lLightGen *= step(lLightDistancePow2, g_Common.Light[1].RangePow2);
    // 距離・スポットライト減衰値計算 =======================================( 終了 )

(後略).........

HLSL言語の提供元のMicroSoftのサイトの説明を見に行きましょう。

スポットライトの設定 は 光の円の 外側の円錐の角度内側の円錐の角度 が設定でき、
フォールオフ(FallOff)の値の設定でその外側と内側の間の部分を鋭くしたりぼやかしたりできます。
その減衰を決める計算のメインとなっているのが、上記のコードの pow( falloff, ( ( Cos a - Cos f ) / ( Cos q - Cos f ) ) )のCosを使った式だということがわかります。
その 外側の円錐の角度 や 内側の円錐の角度 の設定をしているのは、
以下のmain.cppのスポットライトを生成している部分のコードになります。0.7fが外側の円錐の角度,0.6fが内側の円錐の角度です。1000.0fはライトの有効距離、0.391586fと0.001662fと0.0fは距離減衰パラメータAtten0,1,2
( FallOffはDX内部的には1.0fになっているものと思われる )


(前略).........

    // ★g_Common.Light[ 1 ]
    // スポットライトを作成する
    SpotLightHandle = DxLib::CreateSpotLightHandle(VGet(0.0f, 0.0f, 0.0f), VGet(0.0f, -1.0f, 0.0f), 0.7f, 0.6f, 1000.0f, 0.391586f, 0.001662f, 0.0f);
    // スポットライトのアンビエントカラーを無効にする
    //DxLib::SetLightAmbColorHandle(SpotLightHandle, GetColorF(0.0f, 0.0f, 0.0f, 0.0f));
    // スポットライトのディフューズカラーを水色にする
    DxLib::SetLightDifColorHandle(SpotLightHandle, GetColorF(0.0f, 1.0f, 1.0f, 0.0f));

(後略).........

このように設定値の意味と計算式をセットで追っかけながら、わからないなりに1度は勉強しておくことがゲームを描画する技術の本体に触れる機会となります。
一度、目を通しておくだけで、うろ覚えながら「ああ、あの分子と分母にCosがある変わった計算式の部分ね」とシェーダのコードのかたまりの単位で思い出せるだけでもよいし、
Unityのシェーダーについてのネットの記事を見かけたときに、アレルギーを起こさずにちょっと試してみようかな、と思えるようになれば、一歩ステップアップする機会が増えることにつながります。
難しい計算式も、実際にコードになると、2行や3行で書かれていたりするものです。
数式自体までは理解できなくとも、ネットで調べると、実はコアとなる数式のコード自体を書くことは実は数行コードを書けばできたりするのです。

そして、プログラマは数学や物理の学者ではないので、実はプログラマに求められるのは数式をプログラミングに落とし込んで、パラメータが何に影響するかまでを把握できれば仕事にはなるわけです。