はじめに
この記事は、VCI Advent Calendar 2019の14日目の記事です。
バーチャルキャストの世界の中で、データを3次元のグラフとして描画したい。
無理やり実装ですが、それを実現したVCI「ぐらんまてぃか」の実現方法を記載するものになります。
3D世界にグラフを描きたい!
バーチャルキャストでVCIのスクリプトが使えるようになって、やりたいと思ったこと。
その一つがSFアニメなので出てくるグラフ描写。
あれを実データでちゃんと描画したいとわたしは思ったのでした。
要件は以下のようなもの
- Vキャス内で意図した数値データをグラフにして描画したい
- カラフルで半透明できれいにしたい
- 同じ物を使って、いろんなグラフが描けるようにしたい
今回の実現方法は以下の通り
考え方
- 当たったアイテムの名前をデータ受け渡し文字列として使い「データ構文」を決めておく
- そこそこの数の小さな箱と球体を本体VCIに格納しておく
- 小さな箱と球体のXYZ方向への拡大縮小を調整してグラフを書く
実際の処理の流れ
- 当たったアイテムの名前から配列を作る
- 配列の2つ目の文字列でなんのグラフを描くのかを判定する
- グラフ毎に配列の3つめの文字列以降を使って描画処理を実行する
「データ構文」に従った名前を持つアイテムを「ぐらんまてぃか」に当てればグラフが描画できる
といった形で要件を実装します。
本体VCIを作る
データ構文を考える
まず、データ描画に使うデータ構文を考えます。
- "_"を区切り文字とする。
- 区切った時の1番目の文字列で、本VCI「ぐらんまてぃか」対応であるかを判定する。
- 区切った時の2番目の文字列で、どんなグラフを描画するか判定する。
- 区切った時の3番目以降の文字列は、グラフによって利用意味を変える。
現時点では「棒グラフ(縦)」「棒グラフ(横)」「ネットワークグラフ」に対応するようにしました。
構文に従った文字列は以下のようになります
- 棒グラフ(縦)
MEME_BarChart_12_12_3_41_46_11_43_23_5_8_9_12_3
- 棒グラフ(横)
MAME_BarGraph_1_5_21_21_2_11_13_30_52_8_19_22_7
- ネットワークグラフ
MAME_Network_Node_a_12_b_12_c_3_d_41_e_46_f_11_g_43_h_23_i_5_j_8_Edge_a-b_3_a-c_1_a-d_4_a-g_3_b-j_6_d-f_4_d-g_6_e-f_3_e-h_4_g-h_1_g-i_3_g-j_3_h-j_2
グラフ毎の構文については、後半で解説します。
実際の処理とコード
1. アイテム名から配列を作る
アイテムが触れたらアイテム名の文字列内の"_"を区切り文字として分離し、配列にします。
function split(str, ts)
-- 引数がないときは空tableを返す
if ts == nil then return {} end
local t = {} ;
i=1
for s in string.gmatch(str, "([^"..ts.."]+)") do
t[i] = s
i = i + 1
end
return t
end
function onTriggerEnter(item, hit)
local array = split(hit, "_")
~~~~~~~~~~~~以下略~~~~~~~~~~~~
split構文がデフォルトではないようなので、コピペでsplit構文は作ってあります。
onTriggerEnter(item, hit)
は、Unity上の設定でisTriggerにチェックをつけて出力したVCIの場合にアイテム同士が触れると発生してくれるイベントです。
- 第一引数(item)が触れられたアイテムの名前
- 第二引数(hit)は触れたアイテムの名前
になります。
ここでの処理は、触れたアイテムの名前文字列(hit)を"_"で区切って配列(array)にしています。
2. 配列の2つ目の文字列でなんのグラフを描くのかを判定する
~~~~~~~~~~~~以下略~~~~~~~~~~~~
if array[2] == "BarChart" then
~~~~~~~~~~~~以下略~~~~~~~~~~~~
elseif array[2] == "BarGraph" then
~~~~~~~~~~~~以下略~~~~~~~~~~~~
elseif array[2] == "Network" then
~~~~~~~~~~~~以下略~~~~~~~~~~~~
考えた構文に従って、配列の2番目の文字列で描画するグラフに処理を分岐させます。
3.グラフ毎に配列の3つめの文字列以降を使って描画処理を実行する
3-A.棒グラフ
--- 棒グラフ(縦)
for v in pairs(array) do
if v > 2 then
local item = vci.assets.GetSubItem("Cubeword"..(v-2))
item.SetVelocity(Vector3.__new(0,0,0))
item.SetLocalPosition(Vector3.__new(0,0,0))
item.SetRotation(Quaternion.__new(0,0,0,1.0))
item.SetLocalPosition(Vector3.__new(0.15*v,0.2+0.1*array[v]/2,0))
item.SetLocalScale(Vector3.__new(0.1,0.1*array[v],0.1))
end
end
区切った3番目以降の値は、棒グラフの縦の高さとして処理します。
local item = vci.assets.GetSubItem("Cubeword"..(v-2))
で箱を指定し、array[v]のサイズにY軸(縦)方向に拡大、起点からY軸の+ー両方に拡大するので、位置も半分サイズ分あげます。
3-B.ネットワークグラフ
ネットワークグラフは、3文字以降に「Node」か「Edge」の文字列が存在するかどうかで、そこからの処理を切り替えます。
Nodeがネットワークの点になる部分で、Edgeがネットワークの線の部分です。
文字列を再度みます。
MAME_Network_Node_a_12_b_12_c_3_d_41_e_46_f_11_g_43_h_23_i_5_j_8_Edge_a-b_3_a-c_1_a-d_4_a-g_3_b-j_6_d-f_4_d-g_6_e-f_3_e-h_4_g-h_1_g-i_3_g-j_3_h-j_2
-
Node
Node_a_12_b_12_c_3...
の部分は、点aを描画し、サイズは12です。点bを描画し、サイズは12です。点cを描画し、サイズは3です。というような意味になります。 -
Edge
Edge_a-b_3_a-c_1_a-d_4
の部分は、点aと点bの間に線を描き、その線の太さは3です。点aと点cの間に線を描き、その線の太さは1です。点aと点dの間に線を描き、その線の太さは4です。というような意味になります。
コードは以下のようになっています。
--- ネットワーク図
local flg = ""
local node = {}
local nodeids = {}
local edge = {}
local nodeNo = 0
local edgeNo = 0
for v in pairs(array) do
if array[v]=="Node" then
flg = "Node"
elseif array[v]=="Edge" then
flg = "Edge"
else
if flg == "Node" and v%2==0 then
nodeNo = nodeNo +1
node[array[v]] = {0.5*math.random(10),0.5*math.random(10),0.5*math.random(10)}
nodeids[array[v]] = nodeNo
local item = vci.assets.GetSubItem("SphereWord"..nodeNo)
item.SetLocalPosition(Vector3.__new(node[array[v]][1],node[array[v]][2],node[array[v]][3]))
item.SetLocalScale(Vector3.__new(0.02*array[v+1],0.02*array[v+1],0.02*array[v+1]))
elseif flg == "Edge" and v%2==1 then
edgeNo = edgeNo+1
local set = split(array[v], "-")
local ds = math.sqrt((node[set[1]][1]-node[set[2]][1])*(node[set[1]][1]-node[set[2]][1]) + (node[set[1]][2]-node[set[2]][2])*(node[set[1]][2]-node[set[2]][2]) + (node[set[1]][3]-node[set[2]][3])*(node[set[1]][3]-node[set[2]][3]))
local item = vci.assets.GetSubItem("Cubeword"..edgeNo)
item.SetVelocity(Vector3.__new(0,0,0)) -- 動きオフ
item.SetRotation(Quaternion.__new(0,0,0,1.0)) -- 角度初期化
item.SetLocalPosition(Vector3.zero) -- 位置初期化
item.SetLocalRotation(Quaternion.LookRotation(Vector3.__new(node[set[2]][1],node[set[2]][2],node[set[2]][3])-Vector3.__new(node[set[1]][1],node[set[1]][2],node[set[1]][3]))) -- 方向を指定
item.SetLocalPosition(Vector3.__new((node[set[1]][1]+node[set[2]][1])/2,(node[set[1]][2]+node[set[2]][2])/2,(node[set[1]][3]+node[set[2]][3])/2)) -- 位置を指定
item.SetLocalScale(Vector3.__new(0.02,0.02,ds))
edge[array[v]] = array[v+1]
end
end
end
実態は棒グラフと同じようなことをやっています。
点は球体を指定されたサイズで置く。
線は箱を引き伸ばして、球を結ぶように見えるように置く。
本当は重みづけだったり、配置の工夫だったりいろいろやってみたいことはありますが、とりあえず描画はこんな感じ。
終わりに
こんな感じで、今回は名前でグラフを描画するVCIをどう実現したかをかきました。
こちらを作ったのは、まだ、message構文などがない頃でしたが、現在ではmessage構文などもあるので、それを使って描画したいデータの情報を受け渡すという方がスタンダードかもしれないですね。