Yes Second Life

セカンドライフ向けWebサービスを作ってました。このブログではVR・メタバースのことを書いていきます

セカンドライフビューワ改造への道 - 改造編

この記事はセカンドライフ技術系アドベントカレンダー向けの記事です。

セカンドライフ技術系 Advent Calendar 2018 - Adventar

前回環境を構築したので、今回はいよいよ改造していきます。

で、どういう改造をするかなんですが、セカンドライフはたいていのことは内部スクリプトのLSLで実現できてしまうので かなり悩みました。最終的にLSLではビューワのメニューの拡張はできないので、右クリックメニューの拡張をすることにしました。

右クリックメニューに「Jsonへ出力」を追加し、オブジェクトの情報をJson形式で出力できるようにします。 メニューの拡張についてはWikiにドキュメントがあるので、ここを参考に進めていきます。

Adding a menu item - Second Life Wiki

f:id:sabro:20181225032329p:plain

XUI設定ファイルの編集

セカンドライフビューワではXUIというUIフレームワークが実装されていて、XMLに設定を書くことでUIを拡張できます。 メニューの拡張もXUIの設定で行います。 その設定XMLがどこにあるかを知るには、ソース全体のフォルダ構成を理解するのが早道です。

f:id:sabro:20181225032502p:plain

ソースコードのトップフォルダ、indraの配下には「llui」「llxml」のようにllで名前が始まるフォルダがたくさんあります。 これらがビューワの基礎ライブラリです。そしてそれらのライブラリを使用するビューワ本体のコードは「newview」配下に 格納されています。

XUIの設定XMLは「newview」フォルダ直下の「skins」フォルダにあります。右クリックメニューを追加するには 「indra\newview\skins\default\xui\en\menu_object.xml」ファイルを編集します。 menu_item_call.on_clickタグは、クリックされた際に呼ばれるイベントハンドラの設定を行っています。

indra\newview\skins\default\xui\en\menu_object.xml

  <menu_item_call
      enabled="false"
      label="Delete"
      name="Delete">
    <menu_item_call.on_click
        function="Object.Delete" />
    <menu_item_call.on_enable
        function="Object.EnableDelete" />
  </menu_item_call>
  <!-- Json出力メニューを追加 ↓↓↓ -->
  <menu_item_call
      enabled="true"
      label="ToJson"
      name="ToJson">
    <menu_item_call.on_click
        function="Object.ToJson" />
  </menu_item_call>
  <!-- Json出力メニューを追加 ↑↑↑ -->
  <menu_item_separator
       layout="topleft" />

メニューの日本語化をする場合は、jaフォルダの方のmenu_object.xmlを編集します。

indra\newview\skins\default\xui\ja\menu_object.xml

 <menu_item_call label="買う" name="Buy..."/>
    <menu_item_call label="削除" name="Delete"/>
  <!-- Json出力メニューを追加 ↓↓↓ -->
    <menu_item_call label="Jsonへ出力" name="ToJson"/>
  <!-- Json出力メニューを追加 ↑↑↑ -->
    <menu_item_call label="パーティクル所有者をブロック" name="Mute Particle"/>

イベントハンドラの実装

メニューの拡張はできたので、次はイベントハンドラを書いていきます。 メニューのイベントハンドラは、「newview」フォルダ直下の「llviewermenu.cpp」に書く決まりになっています。

indra\newview\llviewermenu.cpp

#include "llnotificationmanager.h"  // includeを追加
...
...
// Json出力のイベントハンドラ
void handle_object_tojson()
{
    // オブジェクトを選択していない場合はリターン
    if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) return;

    // 選択中のオブジェクトを取得
    LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection();
    
    // 選択中オブジェクトのルートオブジェクトを取得
    LLViewerObject* selected_objectp = selection->getFirstRootObject();

    // 選択しているオブジェクトがある場合はJson出力
    if (selected_objectp)
    {
        // Json文字列を構築
        std::ostringstream oss;
        oss << "{" << std::endl;
        oss << "    \"uuid\" : \"" << selected_objectp->getID().asString() << "\"" << std::endl;
        oss << "    \"position\" : \"" << selected_objectp->getPosition() << "\"" << std::endl;
        oss << "    \"scale\" : \"" << selected_objectp->getScale() << "\"" << std::endl;
        oss << "    \"rotation\" : \"" << selected_objectp->getRotation() << "\"" << std::endl;
        oss << "    \"permmodify\" : \"" << selected_objectp->permModify() << "\"" << std::endl;
        oss << "    \"permcopy\" : \"" << selected_objectp->permCopy() << "\"" << std::endl;
        oss << "    \"permmove\" : \"" << selected_objectp->permMove() << "\"" << std::endl;
        oss << "    \"numfacees\" : \"" << selected_objectp->getNumFaces() << "\"" << std::endl;
        oss << "    \"ismesh\" : \"" << selected_objectp->isMesh() << "\"" << std::endl;
        oss << "}";

        // LLChatクラスを作成し、Json文字列を設定
        LLChat chat;
        chat.mSourceType = CHAT_SOURCE_SYSTEM;  // システムからのチャットに設定
        chat.mText = oss.str();

        // LLSDクラス作成、関数の引数に渡すだけなので空でOK
        LLSD args;

        // チャット出力
        LLNotificationsUI::LLNotificationManager::instance().onChat(chat, args);
    }
}

最後にXUIのメニューとイベントハンドラを関連付けるコードを書きます。 「llviewermenu.cpp」の下部に右クリックメニューの関連付け処理が書かれている箇所があるので そこに追加しておきます。

indra\newview\llviewermenu.cpp

 // Object pie menu
    view_listener_t::addMenu(new LLObjectBuild(), "Object.Build");
    commit.add("Object.Touch", boost::bind(&handle_object_touch));
    commit.add("Object.SitOrStand", boost::bind(&handle_object_sit_or_stand));
    commit.add("Object.Delete", boost::bind(&handle_object_delete));
    view_listener_t::addMenu(new LLObjectAttachToAvatar(true), "Object.AttachToAvatar");
    view_listener_t::addMenu(new LLObjectAttachToAvatar(false), "Object.AttachAddToAvatar");
    view_listener_t::addMenu(new LLObjectReturn(), "Object.Return");
    commit.add("Object.Duplicate", boost::bind(&LLSelectMgr::duplicate, LLSelectMgr::getInstance()));
    view_listener_t::addMenu(new LLObjectReportAbuse(), "Object.ReportAbuse");
    view_listener_t::addMenu(new LLObjectMute(), "Object.Mute");

    enable.add("Object.VisibleTake", boost::bind(&visible_take_object));
    enable.add("Object.VisibleBuy", boost::bind(&visible_buy_object));

    commit.add("Object.Buy", boost::bind(&handle_buy));
    commit.add("Object.Edit", boost::bind(&handle_object_edit));
    commit.add("Object.Inspect", boost::bind(&handle_object_inspect));
    commit.add("Object.Open", boost::bind(&handle_object_open));
    commit.add("Object.Take", boost::bind(&handle_take));
    commit.add("Object.ShowInspector", boost::bind(&handle_object_show_inspector));
    enable.add("Object.EnableOpen", boost::bind(&enable_object_open));
    enable.add("Object.EnableTouch", boost::bind(&enable_object_touch, _1));
    enable.add("Object.EnableDelete", boost::bind(&enable_object_delete));
    enable.add("Object.EnableWear", boost::bind(&object_selected_and_point_valid));

    enable.add("Object.EnableStandUp", boost::bind(&enable_object_stand_up));
    enable.add("Object.EnableSit", boost::bind(&enable_object_sit, _1));

    view_listener_t::addMenu(new LLObjectEnableReturn(), "Object.EnableReturn");
    enable.add("Object.EnableDuplicate", boost::bind(&LLSelectMgr::canDuplicate, LLSelectMgr::getInstance()));
    view_listener_t::addMenu(new LLObjectEnableReportAbuse(), "Object.EnableReportAbuse");

    enable.add("Avatar.EnableMute", boost::bind(&enable_object_mute));
    enable.add("Object.EnableMute", boost::bind(&enable_object_mute));
    enable.add("Object.EnableUnmute", boost::bind(&enable_object_unmute));
    enable.add("Object.EnableBuy", boost::bind(&enable_buy_object));
    commit.add("Object.ZoomIn", boost::bind(&handle_look_at_selection, "zoom"));
    // この行を追加 ↓↓↓
    commit.add("Object.ToJson", boost::bind(&handle_object_tojson));

あとはVisualStudio上で「secondlife-bin」プロジェクトをビルドするとビルドフォルダ配下に セカンドライフの実行ファイルができます。

f:id:sabro:20181225043344p:plain

実際、起動してみるとこんな感じで出力されました。

f:id:sabro:20181225035625p:plain

今回、C++をほぼ初めて実践的に使ってみましたがなんとかビューワの改造ができてほっとしました。 アドベントカレンダーのネタということでオレオレ改造みたいな感じになりましたが、 本来はビューワをいじるならJiraを参照して挙がっているissueに対応するのがいいと思います。 腕に覚えのある方は挑戦してみてください( ̄∇  ̄ )