Open13

点群データを用いたマインクラフトワールドの構築および再現性

たくのろじぃ | Takumi Okawaたくのろじぃ | Takumi Okawa

タイトルww論文かよwww
マイクラで3Dの地形って再現できるのかなぁと思い、点群データを使ってみることにした。にー兄さんに相談したらG空間情報センターにいいデータが転がっているとのことだったので、とりあえずダウンロード。

https://www.geospatial.jp/ckan/dataset/virtual-shizuoka-mw/resource/1879dffe-f449-42e5-8371-2273300fa8b8?inner_span=True

なんかバーチャル静岡というらしい。この地図から特定の区域を選択すればダウンロードできる。

たくのろじぃ | Takumi Okawaたくのろじぃ | Takumi Okawa

これ、LAStools のフォルダをCドライブ直下に入れないとダメみたいですね。他のディレクトリに入れても上記のスレッドのようなエラーが表示されます。なんでなんすかね?(ちなみにそのディレクトリ先のパスは指定したつもりだった。)
とりあえず実行中みたいなのでしばらく放置。

約30分経過したけどまだ処理中...大丈夫なのかこれ???

たくのろじぃ | Takumi Okawaたくのろじぃ | Takumi Okawa

まって、las から txt へ変換はできたんだけど読み方が全然わからん。自分の想像としては x, y, z, R, G, B で構成されているものだと思っていたけど

-86400.000 -136826.560 0.310
-86400.000 -136858.500 0.250
-86400.000 -137085.710 0.320
-86400.000 -137087.930 0.320
...

どうやって読むのこれ???
これ一つ一つが座標ってこと???

たくのろじぃ | Takumi Okawaたくのろじぃ | Takumi Okawa

こっちのほうが使いやすい気がするなぁ
あと、点を大きくするとちょっとマイクラ感もある!

CloudCompare 上で File > Save > ASCII Cloud 形式で保存
このとき、ヘッダーにタイトルをつけるといい感じにしてくれる

たくのろじぃ | Takumi Okawaたくのろじぃ | Takumi Okawa

ここからデータの補正を行っていく。例えば、x座標はとんでもない数値になっているけど、基準点をゼロにしておきたいので補正項を加えておいたほうが良いかも。ちなみに座標の取り方は画像の通り。

なんでZ軸が高さやねん!(マイクラはY軸が高さ)

たくのろじぃ | Takumi Okawaたくのろじぃ | Takumi Okawa

ジャストアイディアなアルゴリズム

  1. csv形式を読み込む
  2. Splitでリストに格納
  3. 0番目に格納されている各座標の値を0になるように調整(補正項計算)
  4. 新たな座標としてリストに書き込み
  5. 色データは以前書いた記事(ユークリッド距離計算)を参考にブロック近似(本当は教師あり学習とかでブロックを判別できたらいいけどね)
  6. /setblock <x> <y> <z> <blockId> の形式で書き込み& txt ファイル書き出し

ちょっと疲れてきたので後日やろうかな...

たくのろじぃ | Takumi Okawaたくのろじぃ | Takumi Okawa

とりあえず補正項計算したあとに座標 (x, y, z) と色データ (R, G, B) だけを抽出してテキスト形式に吐き出すプログラムを書いてみた。徹夜テンションで書いているのでもっと効率化できるところとかありそうだけど、今はとりあえず動くもので。

Program.cs
internal class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Please enter CSV file.");
        var path = Console.ReadLine();
        var pointList = GetPointList(path);
        Console.WriteLine("load complete.\nwriting...");
        WriteFile(pointList);
        Console.WriteLine("Done.");
    }

    static List<Point> GetPointList(string path)
    {
        var points = new List<Point>();
        Console.WriteLine("loading...");
        try
        {
            using (StreamReader sr = new StreamReader(path))
            {
                string line = "";
                double def_x;
                double def_y;

                line = sr.ReadLine();
                var first_col = line.Split(",");
                def_x = double.Parse(first_col[0]);
                def_y = double.Parse(first_col[1]);

                while ((line = sr.ReadLine()) != null)
                {
                    var col = line.Split(",");
                    points.Add(new Point(double.Parse(col[0]) - def_x, double.Parse(col[1]) - def_y, double.Parse(col[2]), int.Parse(col[13]), int.Parse(col[14]), int.Parse(col[15])));
                }

            }
            return points;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            return null;
        }
    }

    static void WriteFile(List<Point> points)
    {
        try
        {
            using (StreamWriter sw = new StreamWriter(@"C:\Users\<user>\Desktop\output.txt"))
            {
                points.ForEach(point =>
                {
                    sw.WriteLine($"{point.x},{point.y},{point.z},{point.R},{point.G},{point.B}");
                });
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            return;
        }
    }
}
Point.cs
public struct Point
{
    public double x;
    public double y;
    public double z;
    public int R;
    public int G;
    public int B;

    public Point(double x, double y, double z, int R, int G, int B)
    {
        this.x = x;
        this.y = y;
        this.z = z;
        this.R = R;
        this.G = G;
        this.B = B;
    }
}

だいぶスッキリしたかも。

たくのろじぃ | Takumi Okawaたくのろじぃ | Takumi Okawa

とりあえず、座標を書き出すところで

sw.WriteLine($"/setblock {(int)point.x} {(int)point.z} {(int)point.y} stone");

と変更し、コマンド生成するプロジェクトと、マイクラにコマンドを実行するプロジェクトを作ってやってみた。

using MinecraftConnection;
internal class Program
{
    static MinecraftCommands command = new MinecraftCommands("127.0.0.1", 25575, "minecraft");

    static void Main(string[] args)
    {
        Console.WriteLine("Please enter minecraft command list txt");
        var path = Console.ReadLine();
        var commandList = GetCommandList(path);
        Console.WriteLine("execute commands...");
        commandList.ForEach(list =>
        {
            command.SendCommand(list);
        });
        Console.WriteLine("Done.");
    }

    static List<string> GetCommandList(string path)
    {
        var commands = new List<string>();
        Console.WriteLine("loading...");
        try
        {
            using (StreamReader sr = new StreamReader(path))
            {
                string line = "";
                while ((line = sr.ReadLine()) != null)
                {
                    commands.Add(line);
                }
            }
            return commands;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            return null;
        }

    }
}

コマンドは約350万行(つまり点群も350万点ある)

全部石ブロックだけど、点群データをマイクラでも表現できた。
あとはブロックの種類を判定して組み込むだけやな!寝る!

たくのろじぃ | Takumi Okawaたくのろじぃ | Takumi Okawa

色空間ミスってしまったので、マイクラのブロックの色空間をRGBにするか、点群データの色空間をLabに変換するかのどちらかを取らないといかん。