見出し画像

Processing でボクセルアート① MagicaVoxelとの連携

娘と一緒に Minecraftにはまっていた時期がありました。はじめはスケルトンやゾンビとの闘いや希少金属(金やプラチナ)を探すマイニングなどを楽しんでいましたが、次第に建築のほうに興味が移っていきました。
そのときにボクセルアートに出会ったのです。壮麗な宮殿や複雑な幾何学模様など、ブロックで表現できるアートに感動し魅了されました。自分もこういった作品を作りたいと思ったのですが、(いちおう)お忙しいので時間がありません。ちまちまブロックを置いていたら時間がない。そういうときこそプログラミングの出番です。
たとえば、Minecarft で「256個ブロックを縦に積み上げる」とします。もしサバイバルモードなら、自分が乗る足場も作らなきゃならないし、落ちたら即死ですし、大変な作業になります(クリエイティブモードでも面倒くさい)。でもプログラミングなら一瞬ですから!

import mcpi.minecraft as minecraft
mc = minecraft.Minecraft.create()
x, y, z = mc.player.getPos()
for i in range(256):
    setBlock(x, y + i, z, 1)  # STONE

思い返せば、私がプログラミングにはまったのは、ここからでした。面倒くさいことをやりたくない、自動化したいというものぐさな動機から、この世界にはいっていったんですね。
マイクラにまつわる昔語りは終わりにして、今回は「Processing でボクセルアート」に挑戦します。pythonモードを使用します。

ボクセルアート作成ソフト MagicaVoxel

MagicaVoxel は「なんで無料?」って思ってしまうくらい素晴らしいソフトです。現在も活発に開発が継続しており、機能が増え続けています。
MagicaVoxel
私も基本の操作しかできないのですが、解説サイトを見ながら最初の作品を作ってみました。「カエルさん」です。

magicavoxel

Processingの世界にMagicaVoxelデータを持ってきて、遊んでみようと思います。以前投稿した「日本語フォント」と同じような手順で進めていきます。

MagicaVoxelからデータを出力

export ply

MagicaVoxelの保存データ形式は .vox というMagicaVoxel専用形式です。残念ながら、このままではProcessingに読み込ませることができないので、作成したボクセルアートをテキストファイルとして出力する必要があります。

MagicaVoxel の右下「Export->ply」をクリックしてください。名前を「frog1.ply」にして任意の場所(ダウンロードフォルダなど)に保存します。PLYはPolygon File FormatもしくはStanford Triangle Formatとして知られている、3次元形状を保存するための一般的な形式です。テキストファイルになっているので、Python等のプログラミング言語で簡単に処理をすることができるのです。次は出力したPLYの中身をのぞいてみます。

PLYファイルを解析する

ply
format ascii 1.0
comment : frog1
comment : MagicaVoxel @ Ephtracy
element vertex 1352
property float x
property float y
property float z
property uchar red
property uchar green
property uchar blue
element face 338
property list uchar int vertex_index
end_header
-6 2 0 255 255 0
-6 2 1 255 255 0
-6 3 1 255 255 0
-6 3 0 255 255 0
-5 2 1 0 255 0
-5 2 2 0 255 0
-5 3 2 0 255 0
-5 3 1 0 255 0
-5 2 2 0 255 0
-5 2 3 0 255 0
-5 3 3 0 255 0
-5 3 2 0 255 0
2 -4 1 0 255 0
2 -4 2 0 255 0
2 -3 2 0 255 0
2 -3 1 0 255 0
-3 -4 1 0 255 0
-3 -4 2 0 255 0
-3 -3 2 0 255 0
-3 -3 1 0 255 0
...

「カエルさん」のPLYファイルは、1704行のテキストファイルです。テキストエディターで開くことができます。
14行目までがヘッダー部分になります。頂点が1352個、面の数が338枚あることが読み取れます。property を示しているところで、最初の3つは座標x, y, z を、次の3つが色の三原色 red, green, blue を示していることが読み取れました。
ヘッダー以降が、3次元形状を示す値になります。簡単に説明すると、4行で一組になっており、一つの面を示しています。

-6 2 0 255 255 0
-6 2 1 255 255 0
-6 3 1 255 255 0
-6 3 0 255 255 0

初めの4行を抜き出しました。各行、6つの数字を含み、はじめの3つは座標(x, y, z)、あとの3つが色(r, g, b)を示しています。
matplotlibモジュールを使って、座標をグラフに書いてみます。

plot four points

上図のグラフを読み取ると、Y-Z平面にある4つの点を示していることがわかります。4つの点は、辺の長さが 1 の正方形を表しています。
PLYファイルは3次元形状を表し、この4つの点を含むボックスの位置を保存しています。この4つの点のうち、x, y ,z の値がそれぞれ最小の点は (6, 2, 0)ですから、この点にブロックを置くことを指示しているのです。
PLYファイルの解析が終わりましたので、すべての行に対して、4行ずつセットにして、「ボックスを置く座標」を求めるコードを考えます。

PLYファイルからボックス座標を読み込む

# PLY よりボックスの座標を読み込む
box_positions = set()
with open(ply_file, 'r') as f:
    lines = f.read()
    lines = lines.replace('\r\n', '\n')
    lines = lines.strip()
    positions = [list(map(float, ln.split())) for ln in lines.split('\n') if is_included_six_numbers(ln)]

    number_of_faces = int(len(positions) / 4)
    for i in range(number_of_faces):
        vertex1 = positions[i * 4]
        vertex2 = positions[i * 4 + 1]
        vertex3 = positions[i * 4 + 2]
        vertex4 = positions[i * 4 + 3]  # no need
        x = min(vertex1[0], vertex2[0], vertex3[0])
        y = min(vertex1[1], vertex2[1], vertex3[1])
        z = min(vertex1[2], vertex2[2], vertex3[2])
        r = int(vertex1[3])
        g = int(vertex1[4])
        b = int(vertex1[5])

        box_positions.add((int(x), int(y), int(z), r, g, b))

PLYファイルを読み込んで、ボックスの位置を抜き出すコードです。ボックスの座標は、vertex1, vertex2, vertex3 の最小の点(x, y, z それぞれ)を計算して求めます。ボックスの色は、そのまま4つ目、5つ目、6つ目の数字を代入します。
変数 box_positions に計算して求めたボックス情報(位置と色)を保存しています。これでProcessing でボクセルアートを作画できるようになりました。

Processing でボクセルアート


frog1 error

プログラムを実行すると、カエルが出てくるはず。
あらあら、「ぶさいくカエルさん」になってしまいました(これはこれで味がありますが)。かなり、いい線行ってますが、もう一頑張り必要なようです。バグの原因を考えます。

バグの原因を考える

two boxes

先ほどのグラフをもう一度よく見てみましょう。4つの点を含むボックスは2つ考えることができるのです。4つの点に対して、+x方向と -x方向にボックスを置くことができることが、グラフから読み取れました。
バグの原因は、ボックスを置く位置が間違っていることです。2つ置くことができるボックスの、つねにプラス側に置いているので、先ほどの「ぶさいくなカエルさん」になってしまったのでした。
では、この4つの点はどちらのボックスを示しているのか? これは試行錯誤が必要でかなりの難題(私にとっては)です。結論を述べると、4つの点を結ぶ順番がカギになります(グラフの赤の矢印)。「矢印が右回りに見える方向にボックスを置く」が回答になります。

ボクセルアートの完成

# PLY よりボックスの座標を読み込む
box_positions = set()
with open(ply_file, 'r') as f:
    lines = f.read()
    lines = lines.replace('\r\n', '\n')
    lines = lines.strip()
    positions = [list(map(float, ln.split())) for ln in lines.split('\n') if is_included_six_numbers(ln)]

    number_of_faces = int(len(positions) / 4)
    for i in range(number_of_faces):
        vertex1 = positions[i * 4]
        vertex2 = positions[i * 4 + 1]
        vertex3 = positions[i * 4 + 2]
        vertex4 = positions[i * 4 + 3]  # no need
        x = min(vertex1[0], vertex2[0], vertex3[0])
        y = min(vertex1[1], vertex2[1], vertex3[1])
        z = min(vertex1[2], vertex2[2], vertex3[2])
        r = int(vertex1[3])
        g = int(vertex1[4])
        b = int(vertex1[5])

        # ボックスを置く方向を解析
        if vertex1[0] == vertex2[0] and vertex2[0] == vertex3[0]:  # y-z plane
            if vertex1[1] != vertex2[1]:
                x -= 1
        elif vertex1[1] == vertex2[1] and vertex2[1] == vertex3[1]:  # z-x plane
            if vertex1[2] != vertex2[2]:
                y -= 1
        else:  # x-y plane
            if vertex1[0] != vertex2[0]:
                z -= 1

        box_positions.add((int(x), int(y), int(z), r, g, b))

ボックスを置く方向を解析するコードを9行追加しました。修正したコードを実行してみましょう。

flog1 sucess

「カエルさん」を、Processing の中に持ってくることができました。苦労しましたが、ここまで来れば、あとは思いのまま、ボックス単位でボクセルアートをいじり倒すことができます。
今回の記事は長くなりましたので(疲れたー)、いじり倒して遊ぶのは次回のお楽しみにいたしましょう。
ここまでやってきて、「カエルさん」に愛着を感じたので、プロフィールの画像に選びました。よろしく。


前の記事
Processing で天地創造② 日本地図を作る
次の記事
Processing でボクセルアート② 変形させて遊んでみよう!

その他のタイトルはこちら


この記事が気に入ったらサポートをしてみませんか?