読者です 読者をやめる 読者になる 読者になる

なんとなく

誰得感満載な記事が多いかも。Mono関係とLinuxのサーバ関係、レビューとか。

LibOpenMetaverseを利用してSIMのアバターリストと位置を得る

もくじ

  • はじめに
  • SLサーバとクライアント
  • LibOpenMetaverseってなに?
  • SIMにいるアバターのリストとSIMにいるアバターの位置の描画(実装)
    • ログイン
    • SIMにいるアバターのリストの作成
    • SIMにいるアバターの位置の描画
  • まとめ

はじめに

セカンドライフの技術系の人が集まって、アドベントカレンダーに挑戦することになったということなので、当方も参加してみます。
セカンドライフ 技術系 Advent Calendar に参加してしています。

セカンドライフ(以下、SL)の技術?そもそもオワコンだし。。。という人もいるかもしれないけど、コミュニケーションツールの一つとして利用し、Viewerをちょこちょこ作っている(リリースしてない^^;)ものとして、ぜひとも記事をということで書いてみます。

おそらく、SL内の技術系のお話が多そうだけど、この記事では、RL*1とSLをつなぐところであるViewerで「どういうことやってるの?」っていう視点で、さらに焦点を絞って書いていきます。
Viewerにはいろいろな機能があって焦点を絞らないと、たぶん一人でアドベントカレンダー埋めるのも簡単なくらいになりそうです。

内容は、ある程度プログラム言語を知っていて、Viewerってどんな感じで動いているのか興味がある人向けです。
ただ、今までそんなことに興味なかった方にもこんな感じで動いてるんだと知ってほしいですし、もちろん、そもそもSLのクライアント(Viewer)って???っていう人にも知ってほしいです。

この記事では、LibOpenMetaverse(後述)を使って

  • SIM内にいる人のリスト
  • SIMのどこにアバターがいるのか

を表示してみます。

SLサーバとクライアント

まずは、SLの仕組みについて簡単に触れないたいと思います。ホントに簡単ですよ。
ものすごくざっくり言えば、

サーバとクライアント(Viewer)が通信する(クライアントからサーバにデータを送って、サーバが処理したデータをクライアントが得る)。

っていう仕組みですよね?いわゆるクライアントサーバシステム。

SLサーバについて

SLサーバの仕様は明確にされてないけど、OpenSim*2のサーバ構成とほぼ同じようになっているのではないかと思われます。
機能としてのサーバを以下に示します。

  • ユーザ情報を管理するサーバ
  • 複数のSIM(Grid)を管理するサーバ
  • Inventory(Assetのインデックス)を管理するサーバ
  • Assetデータベースサーバ
  • 1つのSIMを管理するサーバ

上記にあげたものがサーバ群として、SLのサービスを提供していると考えられます。

クライアントについて

SLではクライアントはViewerと呼ばれ、

のViewerが存在します。

Viewerは、上述したサーバと通信しデータをやり取りし、入出力を行なっています。

LibOpenMetaverseってなに?

さっきからちょくちょくLibOpenMetaverseって単語が出現していますが、一体なんぞや?っていう説明です。
簡単にいうとライブラリです。主にサーバとの通信の汎用性の高い処理をまとめて、再利用可能にしたものです。
ViewerがViewerとして動くための処理がまとまったものなのです。

説明を箇条書きにすると以下になります。

メインは、通信のライブラリで、一部画像処理や3Dのレンダリングもサポートしています。
使用することによってSLやOpenSimのサーバと通信し、データを取得できるライブラリです。

ログインやログアウト、IMやチャット、インベントリ、テクスチャの取得などなど、ViewerからSLのサーバにデータを送って、サーバが処理したのをViewerが受け取るまでの処理を受け持ってくれます。

詳細(どのような内容か)については
http://lib.openmetaverse.org/wiki/Documentation#API_Documentation
を参照していただきたいです。今後、時間があったら、別途ざっと紹介したいです。

例えば、オブジェクトに対してずっと座っている、つまりキャンプで稼ぐようなbotを簡単に作ったりするのもできるようです*4

サードパーティViewerでLibOpenMetaverseを利用しているのは、私が把握しているところで(ソースを見ることが可能なもの)、以下があります。

詳しくは、SLのwikiのサードパーティViewerを御覧ください。

この記事では、LibOpenMetaverseを使って主にユーザ情報を管理するサーバとSIMを管理するサーバとやりとりをして、得られた情報(データ)を利用して出力するということを行います。ただ、LibOpenMetaverseを扱う際には、サーバについては意識する必要はありません。

SIMにいるアバターのリストとSIMにいるアバターの位置の描画(実装)

では、実際にLibOpenMetaverseを使って、SIMにいるアバターのリストを作成しMinimapにSIMにいるアバターの位置を描画してみます。
今回は、Androidで挑戦してみようと思います。
Androidの言語はJavaじゃない?C#でライブラリは書かれているのにどうするの?
って疑問が浮かんだ方もいらっしゃると思いますが、そこは、Mono for Android*5というプロダクトで解決します。
有償ですが、実機にデプロイしなければ、無償で利用することができます。エミュレータで試せます。

また、前準備としてlibopenmetaverseのMono for Android向けのコンパイルなど余計なことはとりあえずおいておきましょう。
問題なく動くように少しソースを変えてコンパイルして使っています。
もし、WindowsやLinuxそしてMacであれば、http://lib.openmetaverse.org/wiki/Download
よりDLLをダウンロードして、プロジェクトにおいてDLLを参照させて使用します。

ログイン

まずは、ログインしないと始まりません。

libopenmetaverseでログインするは、以下のようにします。

using System;
using System.Collections.Generic;
using System.Text;
using OpenMetaverse;
 
namespace dvgtuViewer
{
    class Program
    {
        private GridClient Client = new GridClient();

        static void Main(string[] args)
        {
            Client.Network.Login("Avatar FirstName", "Avatar LastName", "Avatars Password", "AgentName", "AgentVersion")
            以下略

OpenMetaverseというのが、LibOpenMetaverseのDLLです。
GridClient というクラスが通信するための主のクラスとしてありますのでこちらを利用します。
http://lib.openmetaverse.org/docs/0.9/#T_OpenMetaverse_GridClient.htm

Client.Network.Login();

というメソッドを発行して、ログインします。
Loginメソッドでは、任意のSIMの任意の座標にログインしたり、SLではないOpenSimのサーバに接続するためのURIを設定できたりします。
詳しくは、以下を参照ください。
http://lib.openmetaverse.org/docs/0.9/#Overload_OpenMetaverse_NetworkManager_Login.htm

SIMにいるアバターのリストの作成

接続状態を継続し、次は、SIMにいるアバターのリストの作成していきます。

SIMにいるアバターのリストの作成についてやることは、ログインした状態で、

  • 現在いるSIMのSIMにいる各々のアバターの情報(位置、UUID、名前)を得る。
  • 自アバターとの距離を計算する。
  • 自アバターを除いて、距離ごとにソートしたアバターリストを作成する。

です。

現在自アバターのいるSIMにいるアバターのリストについては、ログインした状態で、

Client.Network.CurrentSim.ObjectsAvatars

というAvatarの情報が入った箱があるので、Avatarを一つ一つ(ひとりひとり?)中身を見ていく事によって
実装できます。

距離の計算については、Vector3.Distance()という関数があります。
自アバターの位置については、Client.Self.SimPositionで得られます。
SIMにいるアバターの位置ついては、

Client.Network.CurrentSim.AvatarPositions

という箱に入っているので、それを一つ一つ見ていって、自アバターの位置をVector3.Distance()に渡すと距離計算することができます。

//前準備
//距離でソートできる入れ物を準備
private SortedDictionary<float, UUID> NearyAva = new SortedDictionary<float, UUID>();
//UUIDとアバター名の対応表を用意
private Dictionary<UUID, string> Key2Name = new Dictionary<UUID, string>();
//UIへの参照用の入れ物を用意
private List<string> NearyAvatar = new List<string>();

//SIM内のアバターの情報が入った箱がある
//ぐるぐる回してひとつひとつ見ていく
Client.Network.CurrentSim.ObjectsAvatars.ForEach(delegate(Avatar Ava){
	
	//自分のアバターと距離を計算
	//アバターの位置が格納された箱がある
	Client.Network.CurrentSim.AvatarPositions.ForEach(delegate(KeyValuePair<UUID, Vector3> kvp){
		if (kvp.Key == Ava.ID){
			//2点間の距離を求める関数もある
			Distination = Vector3.Distance(kvp.Value,Client.Self.SimPosition);
		}
	});
	
	//自分のアバターは考慮しない
	if (Ava.ID != Client.Self.AgentID){
		//UUIDと名前の格納
		if (Ava.ID != UUID.Zero){
			if (!Key2Name.ContainsKey(Ava.ID)){
				Key2Name.Add(Ava.ID,Ava.Name);
			}
		}
		
		//距離でソートできる入れ物に格納
		if (Distination != 0f){
			//同距離だった場合、キーが重複するので偽装
			if (NearyAva.ContainsKey(Distination)){
				Distination = Distination + 0.00001f;
			}
			NearyAva.Add(Distination,Ava.ID);
		}
	}
});

//距離でソートされたUUIDから名前を引きながら、UIへの参照用の入れ物を用意
//もしかするともっとうまい方法があるような気がする。C#得意な人教えて。
foreach(KeyValuePair<float, UUID> kvps in NearyAva){
	string AvaName2Key;
	Key2Name.TryGetValue(kvps.Value,out AvaName2Key);
	NearyAvatar.Add(AvaName2Key + "(" + (int)kvps.Key + "m)");
}

以上の画像のように取得できます。Akiba SIMのリストを取得したものです。

SIMにいるアバターの位置の描画

次に、MinimapでSIMにいるアバターの位置を描画していこうと思います。背景となるSIMのMinimapの取得については、今回は割愛します。*6

MinimapでSIMにいるアバターの位置を描画についてやることは、ログインした状態で、

  • SIMにいるアバターの位置を得る。
  • 各々のアバターの位置を描画する。
  • 自アバターを描画する。chat範囲(20m)で円を描画する。

です。

SIMにいるアバターの位置ついては、上述同様に

Client.Network.CurrentSim.AvatarPositions

という箱に入っているので、それを一つ一つ見ていって描画します。SIMの座標は下から上なので、注意が必要です。
また、Mono for AndroidでのBitmapへの書き込みになるので、通常のMono(Windows,linux,Mac)での描画と若干異なります。

//前準備
//SIMのY座標
private	int reversePointY = 256; 
//アバターの三角形の一辺の長さ
private	int Avahen = 8;
//自分アバターの三角形の一辺の長さ
private	int Ownhen= 10;
//Chatの範囲の半径
private	int ChatRange = 20;

Android.Graphics.Canvas picDrawpicDraw;

//描画するためのBitmap
Bitmap AddAvaSimMap;

//チャット範囲の円用のPaint
Android.Graphics.Paint ChatRangePaint = new Paint ();
ChatRangePaint.Color = Color.Red;
ChatRangePaint.AntiAlias = true;
ChatRangePaint.SetStyle(Paint.Style.Stroke);

//自分のアバターの三角形用のPaint
Android.Graphics.Paint OwnAvaPaint = new Paint ();
OwnAvaPaint.Color = Color.LightPink;
OwnAvaPaint.AntiAlias = true;
OwnAvaPaint.SetStyle(Paint.Style.Fill);

//アバターの三角形用のPaint
Android.Graphics.Paint AvaPaint = new Paint ();
AvaPaint.Color = Color.OrangeRed;
AvaPaint.AntiAlias = true;
AvaPaint.SetStyle(Paint.Style.Fill);

//Avatarの座標を格納
InternalDictionary<UUID, Vector3> AvaData = Client.Network.CurrentSim.AvatarPositions;

//Avatarの座標を一人ひとり?描画
AvaData.ForEach(delegate(KeyValuePair<UUID, Vector3> AvaKvp){

	//Avatorの位置を三角形で描画
	//Avatorの位置を求める
	int AvaPosX = (int)AvaKvp.Value.X;
	int AvaPosY = (int)AvaKvp.Value.Y;
	
	//Y座標は下から上への順なので、SIMのYから引いてあげる。
	AvaPosY = reversePointY - AvaPosY;

	//自分のアバター以外を三角形で描画する
	if(Client.Self.AgentID != AvaKvp.Key){
		Android.Graphics.Path AvaPath = new Path();
		AvaPath.MoveTo(AvaPosX,AvaPosY-(Avahen/2));
		AvaPath.LineTo(AvaPosX-(Avahen/2),AvaPosY+(Avahen/2));
		AvaPath.LineTo(AvaPosX+(Avahen/2),AvaPosY+(Avahen/2));

		picDraw.DrawPath(AvaPath,AvaPaint);
		picDraw.DrawBitmap (AddAvaSimMap, reversePointY, reversePointY, AvaPaint);
		AvaPath.Dispose();
		
	}
});

//自分の位置を三角形で描画
//自分の位置を求める
int PosX = (int)Client.Self.SimPosition.X;
int PosY = (int)Client.Self.SimPosition.Y;
PosY = reversePointY - PosY;

//三角形を構成
Android.Graphics.Path OwnPath = new Path();
OwnPath.MoveTo(PosX,PosY-(Ownhen/2));
OwnPath.LineTo(PosX-(Ownhen/2),PosY+(Ownhen/2));
OwnPath.LineTo(PosX+(Ownhen/2),PosY+(Ownhen/2));
picDraw.DrawPath(OwnPath,OwnAvaPaint);
picDraw.DrawBitmap (AddAvaSimMap, reversePointY, reversePointY, OwnAvaPaint);

//Say範囲を円で描画
//円の中心座標より、左上の座標を求める
picDraw.DrawCircle (PosX, PosY, ChatRange, ChatRangePaint);
picDraw.DrawBitmap (AddAvaSimMap, reversePointY, reversePointY, ChatRangePaint);

上記のソースでは、マップは表示されず、アバターを示す三角形と円だけが表示されます。

ちょっとAndroid3.0以降の機能であるfragmentの勉強も兼ねてリストとアバターの三角形を描いたミニマップを表示させてみました。
Timer関数を使用し、数秒ごとにデータを取得し、動的に表示することによってリアルタイムな状況を表すことができます。

まとめ

駆け足で説明して行きましたが、どのような印象を持たれたのでしょうか?

私としては、

  • LibOpenMetaverseを使用すれば、得られた値を使って、比較的簡単に表示できるということ
  • Viewerって内部でこういうふうに動いてるんだということ

を知っていただければと思っています。
質問などありましたら、コメントいただけると嬉しいです。

セカンドライフうちからも外からも楽しいことがありますように。

最後に

今回使用した部品を使ってそのうちアプリ公開できればなぁと思っています。
PC(Windows,Linux,Mac)で動くテキストViewerもプロジェクト*7で作成はしているのですが、公開してない現状です。
作成も滞ってます。これを機にまた再開しようかなとも思っています。

*1:現実世界:SLに対してRL(リアルライフ)ということが多い

*2:http://opensimulator.org/wiki/Main_Page/ja 今回は紹介しないけど、SLと通信のプロトコルレベルで互換性のあるメタバースサーバシステム

*3:http://openmetaverse.org/

*4:http://lib.openmetaverse.org/wiki/Developer_Portal

*5:http://xamarin.com/monoforandroid

*6:データの取得は面倒ではないのですが、jpeg2000という形式で取得できるのでそれを表示できる形式にする必要があるのでその説明が長くなるためです。。。

*7:http://www.dvgtu.org