Java 3D LWJGL 〜Tutorial #3 OBJファイルをロードする〜

前回に引き続き、次はOBJファイルのロード(読み込み)を行います。

Git bookのページはこちら
参照するGithubはこちらです。

OBJファイルロード

作成したものは、以下のものです。(コピーして作成)

手順

Gitをクローンする

まずは、学習に使用するGitプロジェクト(LWJGL3のチュートリアル)をクローンします。
ここのリンクにGit bookがある(PDF)のでこのプロジェクトで行なっている内容の解説をしています(英語ですが。。。)

プロジェクトを移植する

これは、プロジェクトをクローンした時に、フォルダの階層が、1プロジェクト=chapter1-29になっているのでプロジェクトとして動きません。(まとめてクローンした場合)

まとめてクローンした場合は、プロジェクト単位で抜き出してやる必要があります。ビルドパスの関係ですね。。。

自分の場合は以下のようにやりました。

単純にクローン下プロジェクトの必要な部分(プログラムのパッケージ以下)をコピーして貼り付けてやるだけです。
そして、プログラムのビルドエラーが出ると思うのでそれを解消してやる必要があります。

ビルドエラーの原因としては、「クラスが見つからない」がほとんどです。なので、参照されるクラスが全てビルドパス上に存在すれば良いということです。

セットアップ時には、resourcesファイルも使用するのでこれも忘れないようにコピーしてあげましょう。

OBJファイルのロード

OBJファイルを読み込んでロードする。これの詳細は[Gitbookを見た方が良い]()のですが、如何せん英語なんだなぁ。。。コードと翻訳機能で、読んで見た所以下のようにまとまりました。
Chapter9にOBJファイルの中身の解説
Chapter10に光の当て方の解説

OBJファイルをロードするのには

An OBJ file defines the vertices, texture coordinates and polygons that compose a 3D model.

と記載があるように、

  1. 各頂点
  2. テクスチャを貼り付けるための座標
  3. ポリゴンの座標

上記のデータをもとに、表示しますのでここの情報をOBJファイルからロードして必要な情報をモデルに設定します。

学習の進め方

これは自分の一番やりやすかった方法ですが、記載しておきます。

  1. Githubから参考にするプロジェクト(ソース)をクローン
  2. 説明などがあるドキュメント(今回はGit book)を用意
  3. 対象のChapterを読みながら、プログラムを読む。
  4. アプリを動かす。

シンプルかつ、時間がかかるけど一番効率の良い方法だと思います。

ポイントになるコード


上にあるように、DummyGameクラスがポイントになります。

public class DummyGame implements IGameLogic
上に示すように、このクラスにはIGameLogicというインターフェースが実装されているので、このインターフェースクラスで定義されているメソッドをオーバーライドすれば、基本的には動くものが作成できます。

サンプルとして「DummyGame」があると思うと飲み込みやすいのではないでしょうか?

そして実装するメソッドは以下のIGameLogoicの中身を見ればわかります。

public interface IGameLogic {
   // 初期表示する内容を実装します、今回はロードしたOBJファイルです。
    void init(Window window) throws Exception;
    // ここには、入力した時どのようなアクションを起こすか定義します。
    void input(Window window, MouseInput mouseInput);
   // 画面の更新処理です。
    void update(float interval, MouseInput mouseInput);
    // レンダリング(画面更新):ここら辺がまだ理解できていません。。。
    void render(Window window);
    // 画面の初期化、再描画するのに必要
    void cleanup();
}

今後の予定

とりあえずは、OBJファイルがロードできたのでここからアニメーションさせる方法を考えたいと思っています。Git bookにあるアニメーションのソースをそのまま起動できませんでしたので(Macだからか?)もとに戻り(Chapter10)サイド学習を進める方向にシフトします。

でわでわ。。。

関連ページのメニュー

LWJGLの学習履歴
Git bookの内容を学習しました。



Java 3D LWJGL 〜Tutorial #2 入力を受け付ける〜

前回作成したコードでは、画面を表示するだけでした。
今度は、入力を受け付ける処理を追加します。
参考にするチュートリアルは動画ですが、こちらになります。

入力を受け付ける

入力、つまりボタンを押下した時のハンドリング部分の実装になります。
くだけた言い方をすると、ボタンを押した時の動きを実装する部分(メソッド)を作る準備をするということです。

早速、コードを写経したので、それを見て見ましょう。
<メインクラス>

public class GameMain {
    public static void main(String[] args) {
        Window win = new Window(800, 500, "LWJGL Tutorial");
        win.create();

        //// ゲームループ ////
        while (!win.closed()) {
            win.update();
            if (win.isKeyPressed(GLFW.GLFW_KEY_A)) {
                System.out.println("Pressed Key A");
            }
            if (win.isKeyReleased(GLFW.GLFW_KEY_A)) {
                System.out.println("Released Key A");
            }
            win.swapBuffers();
        }
    }
}

<ウィンドウクラス>

public class Window {
    /** 画面の幅 */
    private int width;
    /** 画面の高さ */
    private int height;
    /** 画面のタイトル */
    private String title;
    /** 画面の(プログラムレベルの)番号 */
    private long window;

    /** キーボート配列の数だけ配列を用意 */
    private boolean[] keys = new boolean[GLFW.GLFW_KEY_LAST];
    /** マウスのボタン数だけ配列を用意 */
    private boolean[] mouseButtons = new boolean[GLFW.GLFW_MOUSE_BUTTON_LAST];

    /**
     * 画面を作成する、コンストラクター。
     * 
     * @param width 画面の幅
     * @param height 画面の高さ
     * @param title 画面のタイトル
     */
    public Window(int width, int height, String title) {
        this.width = width;
        this.height = height;
        this.title = title;
    }

    /**
     * 画面を作成する処理。
     */
    public void create() {
        if (!GLFW.glfwInit()) {
            System.err.println("Error: Coudn not GLFW");
            System.exit(-1);
        }
        window = GLFW.glfwCreateWindow(width, height, title, 0, 0);
        if (window == 0) {
            System.err.println("Error: window coud not be created");
            System.exit(-1);
        }
        GLFWVidMode videoMode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor());
        GLFW.glfwSetWindowPos(window, (videoMode.width() - width) / 2, (videoMode.height() - height) / 2);
    }

    /** 画面を閉じる */
    public boolean closed() {
        return GLFW.glfwWindowShouldClose(window);
    }

    /** 画面を更新する */
    public void update() {
        for (int i = 0; i < GLFW.GLFW_KEY_LAST; i++) keys[i] = isKeyDown(i);
        for (int i = 0; i < GLFW.GLFW_MOUSE_BUTTON_LAST; i++) keys[i] = isMouseDown(i);
        GLFW.glfwPollEvents();
    }

    public void swapBuffers() {
        GLFW.glfwSwapBuffers(window);
    }

    /** 
     * キーボートの入力を受付
     * @param keyCode this{@link #keys}の配列番号
     * @return キーボードが押下されているか判定した結果
     */
    public boolean isKeyDown(int keyCode) {
        return GLFW.glfwGetKey(window, keyCode) == 1;
    }

    /** 
     * マウスの入力を受付
     * @param mouseButton this{@link #mouseButtons}の配列番号
     * @return マウスのボタンが押されているか判定した結果
     */
    public boolean isMouseDown(int mouseButton) {
        return GLFW.glfwGetMouseButton(window, mouseButton) == 1;
    }

    /** キーボードのボタンが押下されているか判定 */
    public boolean isKeyPressed(int keyCode) {
        return isKeyDown(keyCode) && !keys[keyCode];
    }

    /** キーボードのボタンが離されているか判定 */
    public boolean isKeyReleased(int keyCode) {
        return isKeyDown(keyCode) && keys[keyCode];
    }

    /** マウスのボタンが押下されているか判定 */
    public boolean isMouseButtonPressed(int mouseButton) {
        return isKeyDown(mouseButton) && !mouseButtons[mouseButton];
    }

    /** マウスのボタンが離されているか判定 */
    public boolean isMouseButtonReleased(int mouseButton) {
        return isKeyDown(mouseButton) && mouseButtons[mouseButton];
    }

    /** マウスのX軸座標取得 */
    @Deprecated
    public double getMouseX() {
        DoubleBuffer buf = BufferUtils.createDoubleBuffer(1);
        GLFW.glfwGetCursorPos(window, buf, null);
        return buf.get(0);
    }

    /** マウスのY軸座標取得 */
    @Deprecated
    public double getMouseY() {
        DoubleBuffer buf = BufferUtils.createDoubleBuffer(1);
        GLFW.glfwGetCursorPos(window, null, buf);
        return buf.get(0);
    }
}

とりあえず写経して動かして見ました。

しかし、マウスのイベントが取れない事象に遭遇したので、それをなんとかしてどうにかしました。

原因は、イベントが取得できるであろうと思った下のコードでイベントが取得できなかったところです。

/** 
 * マウスの入力を受付
 * @param mouseButton this{@link #mouseButtons}の配列番号
 * @return マウスのボタンが押されているか判定した結果
 */
public boolean isMouseDown(int mouseButton) {
    return GLFW.glfwGetMouseButton(window, mouseButton) == 1;
}

こちらのサイトで見てみると、イベントハンドラが実装されているようなので、そちらを使用します。
コードは下の2種類あるようです。
PatternA: コールバッククラスを設定する

= new GLFWCursorPosCallback() {
    @Override
    public void invoke(long window, double xpos, double ypos) {

    }
});

PatternB: ラムダ式でコールバック処理を設定する

// Java 8
glfwSetCursorPosCallback(window, posCallback = GLFWCursorPosCallback.create((window, xpos, ypos) -> {
    // ここにマウスが押下された時の処理を実装する
}));

背景の実装

下の2つの動画で、出てきた。変数FPSについて自分が理解したことを記載します。

この動画のはじめの部分

最終的に、追加実装したコードは下のようなものです。GetterとSetterを覗きます。(フィールド変数のGetterを作成しました。)
Githubにソースをコミットしました。

一部抜粋してきます。
FPSをWindowクラスに追加してやりました。これは、ボタン押下時の反応速度?(フレーム数)がなんとかとチュートリアルで言っていたのを聞いた程度しか理解していません。

public boolean isUpdating() {
    double nextTime = getTime();
    double passedTime = nextTime - time;
    processedTime += passedTime;
    time = nextTime;

while(processedTime >  1.0/fps_cap) {
processedTime -= 1.0/fps_cap;
        return true;
    }
    return false;
}

この処理で、どうやらフレーム数がなんとか。。。課題として今後理解する必要がありそうだ。

実行した結果は下のような形で、背景も赤くなりました。

関連ページ一覧

http://zenryokuservice.com/wp/2018/10/27/java-game-lwjgl-gitbook-chapter5-2%E3%80%9C%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E8%A9%B3%E7%B4%B0%E3%80%9C/

<開発準備>

http://zenryokuservice.com/wp/2018/05/02/set-up-1-eclipse-java/

<LWJGLのGitBokに関して>

  1. Chapter1[外枠の表示のみ]
  2. Chapter2-1〜クラスの構成〜
  3. Chapter2-2〜インターフェースの使い方と詳細〜
  4. Chapter2-3〜GameEngineクラス(サンプルクラス)〜/li>
  5. Chapter2-4〜Windowクラス(サンプルクラス)〜
  6. Chapter3〜描画処理を読む〜
  7. Chapter4〜シェーダについて〜
  8. Chapter5-1〜レンダリングについて〜

<Java Basic>

  1. Java Basic Level 1 〜Hello Java〜
  2. Java Basic Level2 〜Arithmetic Calculate〜
  3. Java Basic Level3 〜About String class〜
  4. Java Basic Level 4〜Boolean〜
  5. Java Basic Level 5〜If Statement〜
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 〜Traning of If statement〜
  8. Java Basic Level8 〜How to use for statement〜
  9. Java Basic Level 8.5 〜Array〜
  10. Java Basic Level 8.5 〜Array〜
  11. Java Basic Level 10 〜While statement 〜
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2〜


Java 3D LWJGL 〜Tutorial #1 Windowを表示する〜

OBJファイルを読み込んで3DモデルをJavaで表示するためにLWJGLを使用することにしました。

早い話が、OBJファイルからモデルを読み込んで表示するにはOBJファイルの中身を
Blenderなどで作成した3Dモデルを動かすためにはさらに、細かい部分の理解が必要になるので、

結局LWJGLでやることにしました。
JOGLでやろうと思ったけど、チュートリアルや、ドキュメントが少ないので、やはり今回も使わない方向でいきます。

以前、やったチュートリアルはLWJGL2なので、ちょいと違う部分がありますが、基本的なことはあまり変わりません。
下の部分に、関連ページとしてリンクを記載しておきます。

Tutorial#1

今回からLWJGLのチュートリアルを進めてきます。
このチュートリアルには、セットアップ(環境構築)手順も載っていました。

まずは、写経から入ります。
この動画で作成しているコードを写経していきます。
作成したコードは、下のようなコードになりました。

2クラスあります。
画面を起動するためのMainクラス自分が作成したものは「GameMainクラス」にしています。

public class GameMain {
    public static void main(String[] args) {
        Window win = new Window(800, 500, "LWJGL Tutorial");
        win.create();
        while (!win.closed()) {
            win.update();
            win.swapBuffers();
        }
    }
}

「Windowクラス」

public class Window {
    private int width;
    private int height;
    private String title;
    private long window;

    public Window(int width, int height, String title) {
        this.width = width;
        this.height = height;
        this.title = title;
    }

    public void create() {
        if (!GLFW.glfwInit()) {
            System.err.println("Error: Coudn not GLFW");
            System.exit(-1);
        }
        window = GLFW.glfwCreateWindow(width, height, title, 0, 0);
        if (window == 0) {
            System.err.println("Error: window coud not be created");
            System.exit(-1);
        }
        GLFWVidMode videoMode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor());
        GLFW.glfwSetWindowPos(window, (videoMode.width() - width) / 2, (videoMode.height() - height) / 2);
    }

    public boolean closed() {
        return GLFW.glfwWindowShouldClose(window);
    }

    public void update() {
        GLFW.glfwPollEvents();
    }

    public void swapBuffers() {
        GLFW.glfwSwapBuffers(window);
    }
}

今回の作成した2つのクラスは、以下のような役割分担になっています。
GameMain: Windowクラスを起動する
Window: LWJGLで使用しているGLFWクラスを使用して画面を作成する処理を書きます。

今回はGitにあるソースをコピーとか参照することができなかったので、動画を見て写経しました。それで、下のコードを間違えていました、すると表示した時に画面が端っこに行ってしまいました。
GLFW.glfwSetWindowPos(window, (videoMode.width() + width) / 2, (videoMode.height() + height) / 2);

そして、このコードがよくわかりませんでした。
どの値を計算しているか?これがわからなかったという次第です。

なのでちょいと、いじって見ました、
タイトルの部分と、上の計算している部分のコードをチョチョっといじりました。

結局は、上のよくわからなかった部分は画面表示の初期位置を指定するものでした。

次回は、チュートリアルの2番目をやります。

関連ページ一覧

http://zenryokuservice.com/wp/2018/10/27/java-game-lwjgl-gitbook-chapter5-2%E3%80%9C%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E8%A9%B3%E7%B4%B0%E3%80%9C/

<開発準備>

http://zenryokuservice.com/wp/2018/05/02/set-up-1-eclipse-java/

<LWJGLのGitBokに関して>

  1. Chapter1[外枠の表示のみ]
  2. Chapter2-1〜クラスの構成〜
  3. Chapter2-2〜インターフェースの使い方と詳細〜
  4. Chapter2-3〜GameEngineクラス(サンプルクラス)〜/li>
  5. Chapter2-4〜Windowクラス(サンプルクラス)〜
  6. Chapter3〜描画処理を読む〜
  7. Chapter4〜シェーダについて〜
  8. Chapter5-1〜レンダリングについて〜

<Java Basic>

  1. Java Basic Level 1 〜Hello Java〜
  2. Java Basic Level2 〜Arithmetic Calculate〜
  3. Java Basic Level3 〜About String class〜
  4. Java Basic Level 4〜Boolean〜
  5. Java Basic Level 5〜If Statement〜
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 〜Traning of If statement〜
  8. Java Basic Level8 〜How to use for statement〜
  9. Java Basic Level 8.5 〜Array〜
  10. Java Basic Level 8.5 〜Array〜
  11. Java Basic Level 10 〜While statement 〜
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2〜


Java 3D LWJGL 〜セットアップ手順〜

LWJGLのセットアップを行います。
JOGLとか、Java3D系のフレームワークを試して見ましたが、3Dモデルをゲーム的に使用するのであれば、LWJGLが一番適当なフレームワークに思えるのでこちらを使用するための手順を記載します。

参考にするサイトはこちらです。(英語)

ライブラリの作成した時の動画を下に載せます。Eclipseでのライブラリ追加を行いました。

Zipのダウンロード

こちらのリンクからダウンロードサイトに移動できます。そして、下の方に「〜.zip」があるのでそれをダウンロードします。

そして、ダウンロードできたら、ライブラリ用のフォルダを作成します。上の動画では「LWJGL3」というフォルダを作成し、それをライブラリとして使用しました。細かいところは動画で実行しています。

追伸、全てをダウンロードすると(カスタムでは無い方)動画のように、jarファイルの仕分けをしないといけませんが、リンク先のとこだと、対象になるOSを選択してくれるので楽です。

Eclipseにライブラリを追加

上の動画にもありますが、手順を記載しておきます。

  1. 対象になるプロジェクトを右クリック
  2. 「Add Library」ライブラリの追加でユーザーライブラリを選択
  3. ライブラリを作成してあれば下のようにライブラリを指定する
    下のものはライブラリを作成済みで、追加済みなのでエラーになっています。
  4. ライブラリが無い時は作成する
    3の画像で右のほうに「User Libraries」というボタンがあるのでそれをクリック
  5. 下のような画面が立ち上がる
  6. Newボタンを押下
  7. 適当な名前を入力してライブラリ名にする(今回はテスト的に「NEW」と入力)
  8. NEWライブラリ(フォルダ)にJARファイルを追加(Add External Jarを選択する)

そして、忘れてはいけないのが、Nativeのことです。

上のような感じで、

  1. ライブラリを開く
  2. Nativeライブラリロケーションを指定
    これは、ダウンロードしたnative-<OS名>のあるディレクトリを指定するだけで良いです

これで、ライブラリフォルダが作成できるので作成したライブラリを選択してApplyしてやればオK!

実行テストをする

コードをコピーして、動かそうと思ったら、下のコードでエラーが出ました。

import static org.lwjgl.system.MemoryStack.*;
import static org.lwjgl.system.MemoryUtil.*;

この部分は、Nativeライブラリのロケーションを追加していないと出ます。

そして実行!

WARNING: nextEventMatchingMask should only be called from the Main Thread!

はて?何だろう?調べてみるとこちらのサイトで解決策がありました。MacOSの場合、上のようなエラーが出るということなので。。。
-XstartOnFirstThread をVMの引数に渡してやればOK!ということでした。
実行結果です。

とりあえずは、LWJGLを動かせたので、これでセットアップは完了ということになります。

Vertex3fクラスが無い

しかし、これで終わりではありませんでした。
今回の目的は、*.objファイルから3Dモデルを読み込みことなので、「Vertex3f」などのクラスも使用したい。。。

なので、こちらのJARファイルも必要なのでビルドパスに追加します。

そして、こちらのGithubリポジトリからObjFileLoaderをダウンロードします。 (パッケージ名などの細かいエラーは省略)

Displayクラスが無い

まだまだありました。。。org.lwjgl.opengl.Displayがlwjgl.jarにあるはずだけど、見つからず・・・。
探してみたところorg.lwjgl.utilにあるということでJARファイルをダウンロードしてきました

MavenでDisplayModeクラスを追加

Mavenを使用する場合です。POM.xmlファイルに下の依存関係を追加します。
注意としては<dependencies>タグの中に追加するところです。

<dependency>
    <groupId>org.lwjgl.lwjgl</groupId>
    <artifactId>lwjgl</artifactId>
    <version>2.9.1</version>
</dependency>

ビルドエラーはとりあえず解消

今までのファイルをインポートすることで、ビルドエラーは解消できると思います。
そして、チュートリアルのページを見つけました。とりあえずこれを進めないことには、Objファイルが読み込みできないと思いました。

まとめ

  1. セットアップの手順はここのページを見て行う
  2. JARファイルのダウンロードは、ここのページで行う、使用しているOSに対応したJARをダウンロードできる
  3. lwjgl_util.jarをダウンロード(追加)する
  4. pom.xmlにlwjglを追加する(上記の依存関係を追加する)
    <dependency>
    <groupId>org.lwjgl.lwjgl</groupId>
    <artifactId>lwjgl</artifactId>
    <version>2.9.1</version>
    </dependency>

でわ、でわ。。。



Eclipse アプリ作成 Lv7〜Cubeを日付順に並べる〜

イントロダクション

ここ数日の戦いを経て、ようやく先に進めます。※下は戦闘履歴です。

  1. Eclipse アプリ作成 Lv5〜惨敗:CubeにTextureを貼る〜「惨敗」
  2. Java 3DGame LWJGL Retry Lv3 Texture〜動かして理解する3〜「負け越し」
  3. Java 3DGame LWJGL Retry Lv4 デバック〜動かして理解する4〜「黒星」
  4. Java 3DGame LWJGL Retry Lv5 遊んでみる〜動かして理解する5〜「引分け」
  5. Java 3DGame LWJGL Retry Lv6 遊んでみる2〜動かして理解する6〜「白星」

家計簿作成を再開しようと思いますが、その前に戦果の方を見ていただきたく思います。

<現在>

家計簿のためのカレンダー部分が完成しました。

現状では、まだまだわかりづらいのは否めません。。。

しかし、初めに比べれば。。。

<作成当初>

家計簿作成再始動

今回はよりカレンダーらしくするために、カレンダークラスを使用してカレンダーの様にCubeを配置する様にプログラムを変更します。

まずはカレンダー作成

Javaの基本的なクラス(java.util.Calendar)を使用します。作成するクラスは「CalendarPosit」クラスです。

<要件の整理:どんな機能を持たせるか考える>

Cubeを使ってカレンダーを作成しますので。以下の要件を満たす必要があります。→プログラム設計になります。

  1. 対象月の月初の曜日を取得する
  2. 今月の最大日数を取得する
  3. Calendarクラスから取得した曜日が何曜日か判定する
  4. 日曜始まりをデフォルトとして、月曜始まりの場合でも曜日の値(Calendar.DAY_OF_WEEK)を返却する(配列の位置)

実行結果:木のCubeも移動しています

<月曜始まり>の実行結果(isStartMon=true)

<日曜始まり>の実行結果(isStartMon=false)


実装内容:ソースはこちら(Gitでみる)

isStartMonのハンドルを行なっているクラス(DummyGame#init())のソース(2018/11/10現在)

    //// 参照しているフィールド変数(抜粋) ////
    /**
     *  曜日の表示順
     *  true: 月曜始まり false: 日曜始まり
     */
    private boolean isStartMon;
    private static final String[] WEEK_TEXTURE_SUN = new String[]{"Sun", "Mon", "Tue", "Wed", "Thi", "Fri", "Sat"};

    @Override
    public void init(Window window) throws Exception {
        renderer.init(window);
        isStartMon = true;// 月曜始まり、日曜始まりは「false」
        CalendarPosit pos = new CalendarPosit();
        // 月初の曜日
        int firstDayOfWeek = pos.getStartPoint(isStartMon);
        // 今月の最大日数
        int maxDayOfMonth = pos.getMaxDayOfMonth();
        // Cubeの高さ
        ArrayList<float[]> floats = new ArrayList<>();
        floats.add(new float[] {0.0001f, 0.12f, 0.3f, 0.001f, 0.1f, 0.25f, 0.1f});
        floats.add(new float[] {0.15f, 0.19f, 0.23f, 0.2f, 0.08f, 0.13f, 0.12f});
        floats.add(new float[] {0.1f, 0.2f, 0.4f, 0.001f, 0.2f, 0.05f, 0.15f});
        floats.add(new float[] {0.11f, 0.12f, 0.3f, 0.001f, 0.1f, 0.25f, 0.1f});
        floats.add(new float[] {0.12f, 0.13f, 0.14f, 0.015f, 0.16f, 0.17f, 0.18f});
        floats.add(new float[] {0.12f, 0.13f, 0.14f, 0.015f, 0.16f, 0.17f, 0.18f});

        // Cubeの底面のサイズ(正方形)
        final float cubeSize = 0.1f;
        // x軸の初期値
        final float xInit = -0.5f;
        // y軸の初期値
        final float yInit = -0.8f;
        // z軸の初期値
        final float zInit = -2;
        // x軸の増減幅
        final float xWidth = 0.185f;
        // y軸の増減幅
        final float yWidth = 0.033f;
        // z軸の増減幅
        final float zWidth = 0.1f;
        ArrayList arr = new ArrayList();
        
        // マスのカウント
        int box = 0;
        // 日数
        int day = 0;
        // 初めの1回目だけ曜日のテキストプレートを作成する
        boolean isCreateTexture = false;
        // 1ヶ月分(5週間分のマスを作る)
        for(int j = 1; j <= 6; j++) {
        	// 開始点より一列ずらす
        	// X軸の開始点
        	float xStart = xInit - (0.1f * j);
        	// Y軸の開始点
        	float yStart = yInit + (0.06f * j) ;
        	// Z軸の開始点
        	float zStart = zInit - (0.16f * j);
            // 1週間分
        	float[] weekArr = floats.get(j-1);
        	
        	// 1回目だけテクスチャを作成する
        	isCreateTexture = j == 1 ? true: false;
            for(int i = 1; i <= 7; i++) {
            	float xAdd = xWidth * i;
            	float yAdd = yWidth * i;
            	float zAdd = zWidth * i;
        		// 日付をインクリメント
        		box++;
            	if (isCreateTexture) {
            		arr.add(putOnTexturePlate(xStart + xAdd + 0.01f , yStart + yAdd - 0.03f, zStart - zAdd + 0.03f, i-1));
            	}
           	    if (box < firstDayOfWeek) { System.out.println("開始: " + firstDayOfWeek); continue; } day++; if (day >= maxDayOfMonth) {
            		break;
            	}
            	float val = weekArr[i - 1];
        		arr.add(createCube(val,
        				cubeSize, xStart + xAdd, yStart + yAdd, zStart - zAdd));
            }
        }
        // 配列の要素数を指定する
        GameItem[] items = new GameItem[arr.size()];
        // 配列の取り出し
        gameItems = arr.toArray(items);
        
        // DEBUG
        //debug();
    }

ここでのポイントは月曜始まりと、日曜始まりを変更できる様にしたところです。

	/**
	 * 対象月の月初の曜日を取得する
	 * @return Calendar.MONDAY〜SUNDAY
	 */
	public int getStartPoint(boolean isStartMon) {
		int day_week = 0;
		cal.set(Calendar.DATE, 1);
		day_week = cal.get(Calendar.DAY_OF_WEEK);
		if (isStartMon) {
			day_week = day_week == 1 ? 7 : day_week - 1;
		}
		return day_week;
	}

赤字の部分が工夫の部分です、条件文を使って「if文」を使わずに1行で済むし可読性も良い。これを使わないと以下の様なコードになります。(if文を抜粋)

if (isStartMon) {
    if (day_week == 1) {
        day_week = 7;
    } else {
        day_week = day_week -1;
    }
 }

微妙なコードです、なぜかというとif文はネストすると確認しなくてはいけないことが増えるからです。→単純にコード量が多いのもあります。

一応「条件演算子」について、サンプルとして四捨五入の場合です。

/*
 * 返却する値 = 条件式 ? TRUE時の値(式) : FALSE時の値(式)
 * System.in() // 標準入力
 */
int i = System.in;
// 4以下は「0」、5以上は「10」を「response」に代入
int response = i < 5 ? 0 : 10;

そして作成したのが以下のクラス(Gitでみる)です。(ちょっとコード量が多いですが。。。)

package zenryokuservice.gui.lwjgl.kakeibo.util;
import java.util.Calendar;

/**
 * 家計簿の各数値を表示するための領域(日)を作成する
 * @author takunoji
 *
 * 2018/11/10
 */
public class CalendarPosit {
	/** 日本語の曜日 */
	private static final String[] ja_week = new String[] {"日", "月", "火", "水", "木", "金", "土"};
	/** 英語の曜日 */
	private static final String[] en_week = new String[] {"Sun", "Mon", "Tue", "Wed", "thu", "Fri", "Sat"};

	private Calendar cal = null;
	
	public CalendarPosit() {
		cal = Calendar.getInstance();
	}
	
	/**
	 * 今月の最大日数を返す
	 * @return 最大日数
	 */
	public int getMaxDayOfMonth() {
		return cal.getMaximum(Calendar.DATE);
	}

	/**
	 * 今月の最大日数を返す
	 * @move 今月の月からずらす月数
	 * move=1  -> 1ヶ月たす
	 * mobe=-1 -> 1ヶ月引く
	 * 
	 * 
	 * 現在:10月
	 * move=  1  -> 11月の最大日数
	 * move= -1 -> 9月の最大日数
	 * @return 最大日数
	 */
	public int getMaxDayOfMonth(int move) {
		Calendar tmpCal = cal;
		tmpCal.add(Calendar.MONTH, move);
		return tmpCal.getMaximum(Calendar.DATE);
	}

	/**
	 * 対象月の月初の曜日を取得する
	 * @return Calendar.MONDAY〜SUNDAY
	 */
	public int getStartPoint(boolean isStartMon) {
		int day_week = 0;
		cal.set(Calendar.DATE, 1);
		day_week = cal.get(Calendar.DAY_OF_WEEK);
		if (isStartMon) {
			day_week = day_week == 1 ? 7 : day_week - 1;
		}
		return day_week;
	}	

	/**
	 * 現在の年の指定した月初の曜日を返却する
	 * @param month 1-12を指定する
	 * @return 対象月の、月初曜日
	 */
	public int getStartPoint(int month, boolean isStartMon) {
		int day_week = 0;
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.MONTH, month - 1);
		cal.set(Calendar.DATE, 1);
		
		day_week = cal.get(Calendar.DAY_OF_WEEK);
		return day_week;
	}

	/**
	 * カレンダークラスを返却する。
	 * @return
	 */
	public Calendar getCalendar() {
		return this.cal;
	}
	/**
	 * 開始日付に対応する曜日の値を返す。
	 * @return 曜日の文字列
	 */
	public String getDayOfWeek(boolean isStartMon) {
		return ja_week[getStartPoint(isStartMon)];
	}
	/**
	 * 引数の値が何曜日か判定して返却する
	 * @param value Calendarクラスより取得した、DAY_OF_WEEK
	 * @return 対象の曜日の値
	 */
	public int chkWeek(int value, boolean isStartMon) {
		int day_of_week = 0;
		
		switch(value) {
		case Calendar.MONDAY:
			day_of_week = Calendar.MONDAY;
			break;
		case Calendar.TUESDAY:
			day_of_week = Calendar.TUESDAY;
			break;
		case Calendar.WEDNESDAY:
			day_of_week = Calendar.TUESDAY;
			break;
		case Calendar.THURSDAY:
			day_of_week = Calendar.TUESDAY;
			break;
		case Calendar.FRIDAY:
			day_of_week = Calendar.TUESDAY;
			break;
		case Calendar.SATURDAY:
			day_of_week = Calendar.TUESDAY;
			break;
		case Calendar.SUNDAY:
			day_of_week = Calendar.TUESDAY;
			break;
		default:
			// TODO-[例外処理の実装]
			System.out.println("想定外の曜日です[day_of_week]: " + day_of_week);
			System.exit(-1);
		}

		// 日曜始まり=true, 月曜始まり=false
		if (isStartMon == false) {
			day_of_week = day_of_week < 7 ? day_of_week + 1 : 1;
		}
		return day_of_week;
	}
}

いろんなパターンに対応できる様に先にコードだけ作りました。

関連ページ一覧

<家計簿アプリ作成>

  1. Eclipse アプリ作成 Lv1〜家計簿を作る準備〜
  2. Eclipse アプリ作成 Lv2〜家計簿を作る土台作り〜
  3. Eclipse アプリ作成 Lv3〜3Dグラフ用Cube作り〜
  4. Eclipse アプリ作成 Lv4〜3Dグラフ用Cubeに高さを与える〜
  5. Eclipse アプリ作成 Lv5〜惨敗:CubeにTextureを貼る〜
  6. Eclipse アプリ作成 Lv7〜Cubeを日付順に並べる

Java Basic

  1. Java Basic Level 1 〜Hello Java〜
  2. Java Basic Level2 〜Arithmetic Calculate〜
  3. Java Basic Level3 〜About String class〜
  4. Java Basic Level 4〜Boolean〜
  5. Java Basic Level 5〜If Statement〜
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 〜Traning of If statement〜
  8. Java Basic Level8 〜How to use for statement〜
  9. Java Basic Level 8.5 〜Array〜
  10. Java Basic Level 9〜Training of for statement〜
  11. Java Basic Level 10 〜While statement 〜
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2〜
  14. サンプル実装〜コンソールゲーム〜
  15. Java Basic インターフェース・抽象クラスの作り方
  16. Java Basic クラスとは〜Step2_1〜
  17. Java Basic JUnit 〜テストスイートの作り方〜

Java 3DGame LWJGL Retry Lv7 遊んでみる3〜全部テクスチャにする〜

イントロダクション

ここのところ負け越しの1引分けでしたが、ついにやっつけました。

<戦闘履歴>

  1. Eclipse アプリ作成 Lv5〜惨敗:CubeにTextureを貼る〜「惨敗」
  2. Java 3DGame LWJGL Retry Lv3 Texture〜動かして理解する3〜「負け越し」
  3. Java 3DGame LWJGL Retry Lv4 デバック〜動かして理解する4〜「黒星」
  4. Java 3DGame LWJGL Retry Lv5 遊んでみる〜動かして理解する5〜「引分け」
  5. Java 3DGame LWJGL Retry Lv6 遊んでみる2〜動かして理解する6〜「白星」

最終的に以下の様なコードができました。主にいじったのは「DummyGame」クラスです。ポイントは以下になります。

  1. メッシュを作成するときはテクスチャを使用する様に修正
  2. メッシュを作成するときの各頂点(Vertex)の定義を見直す
  3. 同様に頂点を結びつける順番(indices)を見直す

<DummyGame#init()>

    @Override
    public void init(Window window) throws Exception {
        renderer.init(window);
        // Cubeの高さ
        ArrayList<float[]> floats = new ArrayList<>();
        floats.add(new float[] {0.0001f, 0.12f, 0.3f, 0.001f, 0.1f, 0.25f, 0.1f});
        floats.add(new float[] {0.15f, 0.19f, 0.23f, 0.2f, 0.08f, 0.13f, 0.12f});
        floats.add(new float[] {0.1f, 0.2f, 0.4f, 0.001f, 0.2f, 0.05f, 0.15f});
        floats.add(new float[] {0.11f, 0.12f, 0.3f, 0.001f, 0.1f, 0.25f, 0.1f});
        floats.add(new float[] {0.12f, 0.13f, 0.14f, 0.015f, 0.16f, 0.17f, 0.18f});
        // Cubeの底面のサイズ(正方形)
        final float cubeSize = 0.1f;
        // x軸の初期値
        final float xInit = -0.5f;
        // y軸の初期値
        final float yInit = -0.8f;
        // z軸の初期値
        final float zInit = -2;
        // x軸の増減幅
        final float xWidth = 0.185f;
        // y軸の増減幅
        final float yWidth = 0.033f;
        // z軸の増減幅
        final float zWidth = 0.1f;
        ArrayList arr = new ArrayList();
        
        // 初めの1回目だけ曜日のテキストプレートを作成する
        boolean isCreateTexture = false;
        // 1ヶ月分(5週間分のマスを作る)
        for(int j = 1; j <= 5; j++) {
        	// 開始点より一列文ずらす
        	// X軸の開始点
        	float xStart = xInit - (0.1f * j);
        	// Y軸の開始点
        	float yStart = yInit + (0.06f * j) ;
        	// Z軸の開始点
        	float zStart = zInit - (0.16f * j);
            // 1週間分
        	float[] weekArr = floats.get(j-1);
        	
        	// 1回目だけテクスチャを作成する
        	isCreateTexture = j == 1 ? true: false;
            for(int i = 1; i <= 7; i++) {
            	float xAdd = xWidth * i;
            	float yAdd = yWidth * i;
            	float zAdd = zWidth * i;
            	if (isCreateTexture) {
            		arr.add(putOnTexturePlate(xStart + xAdd + 0.01f , yStart + yAdd - 0.03f, zStart - zAdd + 0.03f, i-1));
            	}
            	float val = weekArr[i - 1];
        		arr.add(createCube(val,
        				cubeSize, xStart + xAdd, yStart + yAdd, zStart - zAdd));
            }
        }
        // 配列の要素数を指定する
        System.out.println("GmaeItems: " + arr.size());
        GameItem[] items = new GameItem[arr.size()];
        // 配列の取り出し
        gameItems = arr.toArray(items);
        
        // DEBUG
        //debug();
    }

<DummyGame#putOnTexturePlate()>

    /**
     * Texture作成メソッド
     * @param xPos
     * @param yPos
     * @param zPos
     * @param num 曜日の番号0:月〜7:日
     * @return GameItem
     */
    private GameItem putOnTexturePlate(float xPos, float yPos, float zPos, int num) {
    	float size = 0.08f;
    	float[] positions = new float[] {
    			-1 * size, size, size, // V0
    			-1 * size, -1 * size, size, //V1 
    			size, -1 * size, size, // V2
    			size, size, size, // V3
    			
    			-1 * size, size, -1 * size, // V4
    			size, -1 * size, size, //V5 
    			-1 * size, -1 * size, -1 * size, // V6
    			size, -1 * size, -1 * size, // V7
    			};
    	float[] textCoords = new float[]{
                0.0f, 0.0f, 
                0.0f, 1.0f,
                1.0f, 1.0f,
                1.0f, 0.0f,
            };
    	int[] indices = new int[] {
    			0, 1, 3, 3, 1, 2
			};
    	Texture texture = null;

    	try {
        	texture = new Texture("/textures/" + weekTexture[num] +  ".png");
    	} catch(Exception e) {
    		e.printStackTrace();
    	}
        TexturedMesh mesh = new TexturedMesh(positions, textCoords, indices, texture);
        GameItem gameItem = new GameItem(mesh);
        gameItem.setPosition(xPos, yPos, zPos);
        gameItem.setRotation(20, 30, 0);
        return gameItem;
    }

<DummyGame#createCube()>

    private GameItem createCube(float height, float cubeSize, float posX, float posY, float posZ) {
        // Create the Mesh
        float[] positions = new float[]{
            // VO
            -1 * cubeSize,  height,  cubeSize,
            // V1
            -1 * cubeSize, -1 * cubeSize,  cubeSize,
            // V2
            cubeSize, -1 * cubeSize,  cubeSize,
            // V3
            cubeSize,  height,  cubeSize,
            // V4
            -1 * cubeSize,  height, -1 * cubeSize,
            // V5
            cubeSize,  height, -1 * cubeSize,
            // V6
            -1 * cubeSize, -1 * cubeSize, -1 * cubeSize,
            // V7
            cubeSize, -1 * cubeSize, -1 * cubeSize,
        };
        float[] colours = new float[]{
            0.5f, 0.0f, 0.0f,
            0.0f, 0.5f, 0.0f,
            0.0f, 0.0f, 0.5f,
            0.0f, 0.5f, 0.5f,
            0.5f, 0.0f, 0.0f,
            0.0f, 0.5f, 0.0f,
            0.0f, 0.0f, 0.5f,
            0.0f, 0.5f, 0.5f,
        };
        int[] indices = new int[]{
            // Front face
            0, 1, 3, 3, 1, 2,
            // Top Face
            4, 0, 3, 5, 4, 3,
            // Right face
            3, 2, 7, 5, 3, 7,
            // Left face
            6, 1, 0, 6, 0, 4,
            // Bottom face
            2, 1, 6, 2, 6, 7,
            // Back face
            7, 6, 4, 7, 4, 5,
        };
    	Texture texture = null;
    	try {
        	texture = new Texture("/textures/wood1.png");
    	} catch(Exception e) {
    		e.printStackTrace();
    	}
        TexturedMesh mesh = new TexturedMesh(positions, colours, indices, texture);
        GameItem gameItem = new GameItem(mesh);
        gameItem.setPosition(posX, posY, posZ);
        gameItem.setRotation(20, 30, 0);
        return gameItem;
    }

上記のポイントを抑えサイドコードを直したところうまくいきました。


関連ページ一覧

<Java Basic>

  1. Java Basic for文 〜Step1_3_1〜
  2. Java Basic Level8 〜How to use for statement〜
  3. Java Basic Level 9〜Training of for statement〜
  4. Java 3DGame LWJGL GitBook chapter7-1〜Cube作成〜「動画あり」

Java 3DGame LWJGL Retry Lv6 遊んでみる2〜動かして理解する6〜

イントロダクション

ここ数日連敗が続いていましたが、昨日引き分けにもつれ込みました。

  1. Eclipse アプリ作成 Lv5〜惨敗:CubeにTextureを貼る〜「惨敗」
  2. Java 3DGame LWJGL Retry Lv3 Texture〜動かして理解する3〜「負け越し」
  3. Java 3DGame LWJGL Retry Lv4 デバック〜動かして理解する4〜「黒星」
  4. Java 3DGame LWJGL Retry Lv5 遊んでみる〜動かして理解する5〜「引分け」
  5. Java 3DGame LWJGL Retry Lv6 遊んでみる2〜動かして理解する6〜「白星」

今日は勝つ予定です。。。

前回(「引分け」)はLWJGL GitBook Chapter7テクスチャを張り替えたり表示を変えたりして遊びました。

そして「遊んでみる方が学習効率も高い」ということを確信しました。というわけで、このまま次のチャプタをやってみようと思います。

Chapter8〜Camera〜

チャプタとしては「カメラ」と書いてありますが、ポイントとしてはテクスチャを貼り付けたオブジェクトを複数生成する場合の処理方法の理解です。

<Chapter8の実行結果>

*******************************************************************

*******************************************************************

 テクスチャありとなしのキューブを作成する

色々やったけど、こんな感じで中途半端にテクスチャが貼れています。

結論

シェーダを2種類用意していないな・・・つまり、テクスチャありの状態でCubeを作成しているので、全てのCubeにテクスチャが貼り付けられている状態です。

『本当は、「テクスチャあり」が手前の7個でそのほかは「テクスチャなし」にしようと思っております。』

<2018/11/09追記>
→この考え方が負因でした、片方はシェーダを使用する、もう片方はシェーダを使用しない。こうなるとシェーダプログラムを2つ用意してそれぞれのメッシュにもそれぞれのシェーダプログラムを適用する必要があるので、まぁ面倒だし実用的でないと判断しました。つまり全部テクスチャを使用する様に修正しました。

 

<テクスチャなし>色を指定しています。

今回は辛勝といったところでしょうか。。。

でわでわ。。。

関連ページ一覧

<今回のやっていること>

  1. Eclipse アプリ作成 Lv1〜家計簿を作る準備〜
  2. Eclipse アプリ作成 Lv2〜家計簿を作る土台作り〜
  3. Eclipse アプリ作成 Lv3〜3Dグラフ用Cube作り〜
  4. Eclipse アプリ作成 Lv4〜3Dグラフ用Cubeに高さを与える〜
  5. Eclipse アプリ作成 Lv5〜惨敗:CubeにTextureを貼る〜
  6. Eclipse アプリ作成 Lv7〜Cubeを日付順に並べる〜

<Java Basic>

  1. Java Basic for文 〜Step1_3_1〜
  2. Java Basic Level8 〜How to use for statement〜
  3. Java Basic Level 9〜Training of for statement〜
  4. Java 3DGame LWJGL GitBook chapter7-1〜Cube作成〜「動画あり」

Java 3DGame LWJGL Retry Lv5 遊んでみる〜動かして理解する5〜

イントロダクション

この数日、連敗が続いている状態です。

  1. Eclipse アプリ作成 Lv5〜惨敗:CubeにTextureを貼る〜
  2. Java 3DGame LWJGL Retry Lv3 Texture〜動かして理解する3〜「負け越し」
  3. Java 3DGame LWJGL Retry Lv4 デバック〜動かして理解する4〜「黒星」

こんな感じで3連敗です。。。

元々は、OpenGLで作成したCubeにテクスチャを貼り付けようとしていました。

ここで、初心に戻り動くものを弄って遊んでみようとなりました。突破口になるかもしれません。

サンプルコード:「Chapter7-2のマイクラの様なブロックを回転させる

<起動結果>


遊んでみる

First Attack: テクスチャを弄ってみる

<DummyGame#init()>元々のコード

        float[] textCoords = new float[]{
            0.0f, 0.0f,// TRY2
            0.0f, 0.5f,// TRY3
            0.5f, 0.5f,
            0.5f, 0.0f,
            
            0.0f, 0.0f,
            0.5f, 0.0f,
            0.0f, 0.5f,
            0.5f, 0.5f,
            
            // For text coords in top face
            0.0f, 0.5f,// TRY1
            0.5f, 0.5f,
            0.0f, 1.0f,
            0.5f, 1.0f,

            // For text coords in right face
            0.0f, 0.0f,
            0.0f, 0.5f,

            // For text coords in left face
            0.5f, 0.0f,
            0.5f, 0.5f,

            // For text coords in bottom face
            0.5f, 0.0f,
            1.0f, 0.0f,
            0.5f, 0.5f,
            1.0f, 0.5f,
        };

テクスチャの座標を指定している部分です。こいつを弄って遊んでみようとなりました。

TRY1-①: 「0.0f, 0.5f」→「0.0f, 0.2f」に変更。

回転しているので座標がわかりづらい。。。

ちょっとハゲができました。

TRY1-②: 「0.0f, 0.5f」→「0.5f, 0.5f」に変更。

頂点の一部がかき消された様になっています。下のコードをみてみると頂点が被っていて、実際は3点のみになっています。

            // For text coords in top face
            0.5f, 0.5f,// 同じ
            0.5f, 0.5f,// 同じ
            0.0f, 1.0f,
            0.5f, 1.0f,

なるほど、テクスチャの頂点を指定している様です。

ここからが本題

元々のコード、緑色の部分に関しては未だになんなのかわかっていませんのでこいつを弄ってみます。

「TRY2」の部分を「0.0f, 0.0f」→「0.5f, 0.5f」に変更

おそらく、V0の座標を変更したので下のイメージの半分より下の部分がテクスチャとして認識されていると思われます。

Texture coordinates front face

テクスチャのイメージファイルを変更してみます。

<実行結果>

うーんパズルの様な感じになりました。ちょいと調節すると。。。

一面のみうまく表示されています。この状態のコードは以下です。

        float[] textCoords = new float[]{
            0.0f, 0.0f,
            1.0f, 0.0f,
            0.0f, 1.0f,
            1.0f, 1.0f,
            
            0.0f, 0.0f,
            0.5f, 0.0f,
            0.0f, 0.5f,
            0.5f, 0.5f,
            
            // For text coords in top face
            0.0f, 0.0f,
            1.0f, 0.0f,
            0.0f, 1.0f,
            1.0f, 1.0f,

            // For text coords in right face
            0.0f, 0.0f,
            0.0f, 0.5f,

            // For text coords in left face
            0.5f, 0.0f,
            0.5f, 0.5f,

            // For text coords in bottom face
            0.5f, 0.0f,
            1.0f, 0.0f,
            0.5f, 0.5f,
            1.0f, 0.5f,
        };

次のコードです。

        float[] textCoords = new float[]{
        	// Imageの取得する座標
            0.0f, 0.0f,
            1.0f, 0.0f,
            0.0f, 1.0f,
            1.0f, 1.0f,
            // この部分がわからない
            0.0f, 0.0f,
            1.0f, 0.0f,
            0.0f, 1.0f,
            1.0f, 1.0f,
            
            // For text coords in top face
            0.0f, 0.0f,
            1.0f, 0.0f,
            0.0f, 1.0f,
            1.0f, 1.0f,

            // For text coords in right face
            0.0f, 0.0f,
            0.0f, 1.0f,

            // For text coords in left face
            1.0f, 0.0f,
            1.0f, 1.0f,

            // For text coords in bottom face
            0.0f, 0.0f,
            1.0f, 0.0f,
            0.0f, 1.0f,
            1.0f, 1.0f,
        };

一面以外はうまくいっている様です。(多分底面がうまくいっていない)

ですが、なんとなく処理の流れ(座標の取り方)がわかってきました(そんな気がする。。。)、まだ仮説状態です。

まずは頂点定義の配列を眺めます

 
        float[] positions = new float[] {
            /* ①メッシュの使用する頂点を定義 */
            // V0
            -0.5f, 0.5f, 0.5f,
            // V1
            -0.5f, -0.5f, 0.5f,
            // V2
            0.5f, -0.5f, 0.5f,
            // V3
            0.5f, 0.5f, 0.5f,
            // V4
            -0.5f, 0.5f, -0.5f,
            // V5
            0.5f, 0.5f, -0.5f,
            // V6
            -0.5f, -0.5f, -0.5f,
            // V7
            0.5f, -0.5f, -0.5f,
            /* ②テクスチャで貼り付ける面を指定する */
            // For text coords in top face
            // V8: V4 repeated
            -0.5f, 0.5f, -0.5f,
            // V9: V5 repeated
            0.5f, 0.5f, -0.5f,
            // V10: V0 repeated
            -0.5f, 0.5f, 0.5f,
            // V11: V3 repeated
            0.5f, 0.5f, 0.5f,
            /* ③横のたて(1本分)を定義する */
            // For text coords in right face
            // V12: V3 repeated
            0.5f, 0.5f, 0.5f,
            // V13: V2 repeated
            0.5f, -0.5f, 0.5f,
            /* ④横のたて(1本分)を定義する */
            // For text coords in left face
            // V14: V0 repeated
            -0.5f, 0.5f, 0.5f,
            // V15: V1 repeated
            -0.5f, -0.5f, 0.5f,
            /* ⑤面を定義する */
            // For text coords in bottom face
            // V16: V6 repeated
            -0.5f, -0.5f, -0.5f,
            // V17: V7 repeated
            0.5f, -0.5f, -0.5f,
            // V18: V1 repeated
            -0.5f, -0.5f, 0.5f,
            // V19: V2 repeated
            0.5f, -0.5f, 0.5f,
        };

 

Cube coords

頂点を定義した後に、面を定義する様に座標点を設定していきます。

Texture coordinates front face

中途半端だけど。。。

コードはこちらにアップロードしてあります。2018/11/08


 

関連ページ一覧

<今回のやっていること>

  1. Eclipse アプリ作成 Lv1〜家計簿を作る準備〜
  2. Eclipse アプリ作成 Lv2〜家計簿を作る土台作り〜
  3. Eclipse アプリ作成 Lv3〜3Dグラフ用Cube作り〜
  4. Eclipse アプリ作成 Lv4〜3Dグラフ用Cubeに高さを与える〜
  5. Eclipse アプリ作成 Lv5〜惨敗:CubeにTextureを貼る〜
  6. Eclipse アプリ作成 Lv7〜Cubeを日付順に並べる〜

<Java Basic>

  1. Java Basic for文 〜Step1_3_1〜
  2. Java Basic Level8 〜How to use for statement〜
  3. Java Basic Level 9〜Training of for statement〜
  4. Java 3DGame LWJGL GitBook chapter7-1〜Cube作成〜「動画あり」

Java 3DGame LWJGL Retry Lv4 デバック〜動かして理解する4〜

イントロダクション

前々回、惨敗を喫してしまったのでリベンジきめます。前回は3次元描画ではなく2次元描画にレベルを落として動かしてみました。PNGファイルを読んでいるところまでは、確認できているのですが。。。そのあと描画するときに指定する座標に関してはまるで判らない状態なのでそこを解決しようと思います。

デバック方法

  1. まずはコードを見直す
  2. 想定通りの結果を得るための処理として正しいか、否か?
  3. 原因を探る
  4. 解決策を打つ

参照するドキュメント:LWJGL GitBookChapterのChapter5

カスタムするコード:LWJGL GitBook Chapter5

座標に関して(ソースを見直す)

ソースコード上では以下のようになっています。

<DummyGame#createFloor()>

    /**
     * 床のような土台のメッシュ(3Dモデル)を作成します
     * @return GameItem 床型のメッシュ
     */
    private GameItem createFloor() {
        float[] positions = new float[]{
            	// V0
                0.0f, 0.25f, 0.0f,
                // V1
                -0.0f, -0.0f, 0.0f,
                // V2
                0.5f, -0.0f, 0.0f,
                // V3
                0.5f, 0.25f, 0.0f,};
            int[] indices = new int[]{
                0, 1, 3, 3, 1, 2,};
            // 追記 2018/10/27
            float[] textCoord = new float[] {
            	    0.5f, 0.0f, 0.0f,   //V0の色(赤(R))
            	    0.0f, 0.5f, 0.0f,   // V1の色(緑(G))
            	    0.0f, 0.0f, 0.5f,   // V2の色(青(B))
            	    0.8f, 0.8f, 0.0f,}; // V3の色(黄)
            return new GameItem(new Mesh(positions, indices, textCoord));
    }

上記の赤字部分が理解できていない部分です。ここの部分は、元々色を指定していた部分です。変数名が「colour」から「textCoords」に変わっただけです。

現状の理解は、上のコードにあるように各頂点の色を指定している認識です。実行結果もそのようになりました。

これがTextureに変わることで下のようになってしまいました。

ちょっとホラーな感じがします(自分だけ?)

調査開始(正しい処理か?否か?)

まずは、未着手部分→シェーダについて調べます。現状のシェーダは以下のようになっています。

<Renderer#init()>

    public void init() throws Exception {
        shaderProgram = new ShaderProgram();
        shaderProgram.createVertexShader(Utils.loadResource("/vertex5.vs"));
        shaderProgram.createFragmentShader(Utils.loadResource("/fragment5.fs"));
        shaderProgram.link();
    }

上のコードから「/vertex5.vs」「/fragment5.fs」を参照していることを確認。

そして、フォルダー構成より以下の階層を確認(ビルドパスには「resources」を追加してあります。

<vertex5.vs>

#version 330

// 追記 2018/10/27
in  vec3 exColour;
out vec4 fragColor;

void main()
{
	fragColor = vec4(exColour, 1.0);
}

<fragment5.fs>

#version 330

layout (location =0) in vec3 position;

// 追記 2018/10/27
layout(location=1) in vec3 inColour;
out vec3 exColour;

void main()
{
	gl_Position = vec4(position, 1.0);
	// 追記 2018/10/27
	exColour = inColour;
}

以前追加したコードがあるようです。

これで考える材料が揃いました。

処理を追いかけます。参照するコードはイントロダクション部分にリンクがあるのでそちらをご覧ください。

  1. Main#main(): DummyGame, GameEngineをインスタンス化してGameEngine#satrt()を起動
  2. DummyGameのコンストラクタ: Rendererクラスのインスタンス化→フィールドで保持
  3. GameEngineのコンストラクタ: 自分自身をスレッド化してWindowクラスのインスタンス化、DummyGame(インターフェース)をフィールドに保持、Timerクラスをインスタンス化してフィールドに保持
  4. 「1」でGameEngine#start()が起動される、OSの判定をして結局GameEngine#run()が起動する
  5. GameEngine#init()とgameLoop()を起動する
  6. 最後にclearnup()が起動する
  7. GameEngine#init()でフィールドに保持した「Window」「Timer」「gameLogic(DummyGame)」のinit()を起動する
  8. 今回は描画部分を調査するのでDummyGame#init()(gameLogic.init())を眺める
  9. DummyGame#init()ではRenderer#init()とGameItem(Mesh)を作成している
  10. Renderer#init(): ShaderProgram をインスタンス化、shader, fragmentプログラムを読み込み最後にShapderProgram#link()を起動する
  11. 「glLinkProgram()」でShaderをインスタンス化したときにフィールドで保持したprogramIdをリンクする(OpenGLのメソッドなので深堀はしないでおく→ハマることが多い)そのほか設定する処理を走らせる。
  12. 今回の目玉四角形を作成する

そして、ドキュメントを読むと「Meshクラスを変更して、色の代わりにテクスチャ座標を含む浮動小数点数の配列を受け入れるようにします」とあるので単純に「colour」を「textCoords」に変更した状態が今のコードです。

上記の部分を修正して実行してみると。。。

まっくろくろすけでわ、あーりませんか?

試しに背景を白くしてみます。。。とその前に今までのソースは「import static  XXXX.GL?」というクラスをstaticでインポートしているのでどのメソッドがどのクラスのメソッドかわからなかったので「static」を消します。

<Rendererクラス>

package zenryokuservice.gui.lwjgl.tutoriral.gitbook.chapter5.game;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;

import org.joml.Matrix4f;

import zenryokuservice.gui.lwjgl.tutoriral.gitbook.chapter5.engine.Utils;
import zenryokuservice.gui.lwjgl.tutoriral.gitbook.chapter5.engine.Window;
import zenryokuservice.gui.lwjgl.tutoriral.gitbook.chapter5.engine.graph.GameItem;
import zenryokuservice.gui.lwjgl.tutoriral.gitbook.chapter5.engine.graph.Mesh;
import zenryokuservice.gui.lwjgl.tutoriral.gitbook.chapter5.engine.graph.ShaderProgram;
import zenryokuservice.gui.lwjgl.tutoriral.gitbook.chapter5.engine.graph.Transformation;

public class Renderer {

    private ShaderProgram shaderProgram;
    
    // 2018/11/08
    private Transformation transformation;

    public Renderer() {
    	transformation = new Transformation();
    }

    public void init() throws Exception {
        shaderProgram = new ShaderProgram();
        shaderProgram.createVertexShader(Utils.loadResource("/vertex5.vs"));
        shaderProgram.createFragmentShader(Utils.loadResource("/fragment5.fs"));
        shaderProgram.link();
        
        // 2018/11/08追記
        shaderProgram.createUnitform("projectionMatrix");
        shaderProgram.createUnitform("worldMatrix");
        shaderProgram.createUnitform("texture_sampler");
    }

    public void clear() {
        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
    }

    public void render(Window window, GameItem[] gameItems) {
        clear();

        if (window.isResized()) {
            GL11.glViewport(0, 0, window.getWidth(), window.getHeight());
            window.setResized(false);
        }

        shaderProgram.bind();
        int i = -1;
        for (GameItem item : gameItems) {
// Meshクラスに処理を移動
//            // Draw the mesh
//            glBindVertexArray(item.getMesh().getVaoId());
//            glEnableVertexAttribArray(++i);
//            // 追記 2018/10/27
//            glEnableVertexAttribArray(++i);
//            glDrawElements(GL_TRIANGLES, item.getMesh().getVertexCount(), GL_UNSIGNED_INT, 0);
        	// ワールド座標を設定する
        	Matrix4f worldMatrix = transformation.getWorldMatrix(item.getPosition(), item.getRotation(), item.getScale());
        	shaderProgram.setUniform("worldMatrix", worldMatrix);
        	item.getMesh().render();
        }

        // Restore state
        GL20.glDisableVertexAttribArray(0);
        GL30.glBindVertexArray(0);

        shaderProgram.unbind();
    }

    public void cleanup() {
        if (shaderProgram != null) {
            shaderProgram.cleanup();
        }
    }
}

今度は真っ白です、つまり何も描画していないことになります。

※背景の色を変えるのはWindow#colorの値を1.0fにしてやるとできます。

もう何がわからないので、ネットで探します。

LWJGLのWiki

色々探してみたが、Textureの座標とMeshの頂点座標の関係がわからない。。。

次は、貼れたテクスチャを解剖してみるかな?

参照するのはChapter7-1です。(Cubeの動画あり)

関連ページ一覧

<今回のやっていること>

  1. Eclipse アプリ作成 Lv1〜家計簿を作る準備〜
  2. Eclipse アプリ作成 Lv2〜家計簿を作る土台作り〜
  3. Eclipse アプリ作成 Lv3〜3Dグラフ用Cube作り〜

<Java Basic>

  1. Java Basic for文 〜Step1_3_1〜
  2. Java Basic Level8 〜How to use for statement〜
  3. Java Basic Level 9〜Training of for statement〜
  4. Java 3DGame LWJGL GitBook chapter7-1〜Cube作成〜「動画あり」

<サイトマップ>

 

 

 

Java 3DGame LWJGL Retry Lv3 Texture〜動かして理解する3〜

イントロダクション

前回、惨敗を喫してしまいました。Textureが結局判らずじまい。。。

悔しいので、2次元描画のプログラムをカスタムしてもう一度やってみます。

参照するドキュメント:LWJGL GitBookChapterのChapter5

カスタムするコード:LWJGL GitBook Chapter5

ここで描画した四角形にテクスチャを貼ろうと思います。

<元の画像>

元々のコードにTextureクラスを追加して、MeshクラスにTextureを作成するコードを加えたものを起動してみました。

ちなみにGitに登録しているソースです。下のような感じです。



そして、実行してみた結果は以下です。

想定と全く違うものです。。。原因は一体なんだろう?

ちなみに一部ですが、ソースはこんな感じです。

<Meshクラス>

    // 最後の「boolean」はコンストラクタのオーバーロードのためだけにつけたものです。エラー回避ようです。
    public Mesh(float[] positions, int[] indices, float[] textCoords, boolean flg) {
        FloatBuffer posBuffer = null;
        IntBuffer indicesBuffer = null;
        FloatBuffer textCoordBuffer = null;
        try {
        	texture = new Texture("/textures/Mon.png");
        } catch(Exception e) {
        	e.printStackTrace();
        }
        vboIdList = new ArrayList<>();
        try {
            vertexCount = indices.length;
            vaoId = glGenVertexArrays();
            vboIdList.add(vaoId);
            glBindVertexArray(vaoId);

            // Position VBO
            int posVboId = glGenBuffers();
            vboIdList.add(posVboId);
            posBuffer = MemoryUtil.memAllocFloat(positions.length);
            posBuffer.put(positions).flip();
            glBindBuffer(GL_ARRAY_BUFFER, posVboId);
            glBufferData(GL_ARRAY_BUFFER, posBuffer, GL_STATIC_DRAW);
            glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);

            // Texutre coordinates VBO:2018/11/07追記部分です。
            int vboId = glGenBuffers();
            vboIdList.add(vboId);
            textCoordBuffer = MemoryUtil.memAllocFloat(textCoords.length);
            textCoordBuffer.put(textCoords).flip();
            glBindBuffer(GL_ARRAY_BUFFER, vaoId);
            glBufferData(GL_ARRAY_BUFFER, textCoordBuffer, GL_STATIC_DRAW);
            glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0);
            
            // Index VBO
            idxVboId = glGenBuffers();
            vboIdList.add(idxVboId);
            indicesBuffer = MemoryUtil.memAllocInt(indices.length);
            indicesBuffer.put(indices).flip();
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idxVboId);
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW);
            
            glBindBuffer(GL_ARRAY_BUFFER, 0);
            glBindVertexArray(0);
        } finally {
            if (posBuffer != null) {
                MemoryUtil.memFree(posBuffer);
            }
            if (indicesBuffer != null) {
                MemoryUtil.memFree(indicesBuffer);
            }
            if (textCoordBuffer != null) {
                MemoryUtil.memFree(textCoordBuffer);
            }
        }
    }

これで、とりあえず動かしてみたのだけど、上記のキャプチャ通りの結果でした。ブログを書いていて気がついたけど「シェーダ・プログラム」を修正していなかった。。。

関連ページ一覧

<今回のやっていること>

  1. Eclipse アプリ作成 Lv1〜家計簿を作る準備〜
  2. Eclipse アプリ作成 Lv2〜家計簿を作る土台作り〜
  3. Eclipse アプリ作成 Lv3〜3Dグラフ用Cube作り〜

<Java Basic>

  1. Java Basic for文 〜Step1_3_1〜
  2. Java Basic Level8 〜How to use for statement〜
  3. Java Basic Level 9〜Training of for statement〜
  4. Java 3DGame LWJGL GitBook chapter7-1〜Cube作成〜「動画あり」

<サイトマップ>