2008年4月27日日曜日

Native library loading framework for Applet (Native Capable)

NyARToolkitのデモAppletを作る際、JMFやJOGLなど実行時にネイティブライブラリを必要とするAPI利用のためにクラスローダ等を実装したのですが、全体をフレームワーク化したので公開します。
ライセンスはLGPLとしています。

(2008/5/8 追記) テンポラリファイルが削除されないバグを修正しました。
File.deleteOnExit関数がWindows上では正しく機能しない既知のバグ
によるものです。



Applet、Java Web Startまたはスタンドアロンアプリケーションにおいて、ネイティブライブラリを可能な限り同じコードで利用できることを目的として作成したフレームワークです。(JWSにはネイティブを扱う機構が存在します)
動作には、nativecapable.jarのほかにjavassist.jar、ant.jarが必要です。
(バイナリ&ソース&サンプルに同封)

何かに使って見た際にでも感想などいただけると幸せです^^

以下に使用方法を説明します。

1. NativeCapableModuleの実装

ネイティブライブラリを利用したいモジュールはNativeCapableModuleインタフェースを実装します。NativeCapableModuleインタフェースでは4つのメソッドが定義されています。

       //モジュールの初期化処理を記述します
      public boolean initModule();
       //モジュールの停止及び破棄処理を記述します
      public boolean destroyModule();
       //GUIアプリケーションの場合、
       //最上位に配置されるコンポーネントを返す処理を記述します
      public Component getRootComponent();
       //モジュールの開始処理を記述します
       public boolean startModule();

NativeCapableModuleの実装クラスはApplet及びスタンドアロンアプリケーションとして利用することができます。

2.Appletとして実装する

Appletとして実装する場合はNativeCapableAppletクラスを継承して利用します。
NativeCapableAppletクラスはJAppletクラスを継承し、NativeCapableインタフェースを実装しています。
Java3DTestAppletをもとに説明します。(バイナリ&ソース&サンプルに含まれる)

public class Java3DTestApplet extends NativeCapableApplet {
    public void init() {
               //Moduleの生成を行うインスタンスを生成します
             NativeCapableModuleBuilder builder = new NativeCapableModuleBuilder(
           this, "java3d.Java3DTestModule");

               //ネイティブライブラリを利用するクラスのロードについて設定します -  ①
             Collection col = new HashSet();
             col.add("javax.media.j3d");
              builder.setTargetPackages(col);
             builder.setDelegateTargets(false);

               //Moduleインスタンスを生成します
              NativeCapableModule module = builder.newModule();
               //モジュールの初期化
              module.initModule();
               //モジュールからAppletに追加するコンポネントを取得して追加します
              add(module.getRootComponent());

               //必要に応じてstartやdestroyを呼び出します。
              //module.startModule();
              //module.destroyModule();
}

① クラスのロードについて
NativeCapableModuleBuilder#setDelegateTarget(boolean)メソッドによりターゲット指定されたパッケージに属するクラスをNativeCapableClassLoaderでロードするか、指定されたもの意外をロードするかを設定することができます。
NyARToolkitのデモであるJavaSimpleLiteAppletでは次のように使用しています。

               Collection col = new HashSet();
             boolean dTargets = true;
              if (!dTargets) {
               col.add("javax.media.");
               col.add("com.sun.opengl.");
               col.add("jp.nyatla.");
                col.add("com.sun.media.");
                col.add("jp.ac.kyutech.ai.ylab.shiva");
             } else {
               col.add("java.");
                col.add("javax.swing.");
                col.add("sun.reflect.");
             }
            builder.setTargetPackages(col);
             builder.setDelegateTargets(dTargets);

3.スタンドアロンアプリケーションとして実装する

Appletの場合とほぼ同じですが、NativeCapableApplicationを利用します。
public class Java3DTestApplication {
    public static void main(String[] args) throws Exception {
           NativeCapableApplication nca = new NativeCapableApplication();
           NativeCapableModuleBuilder builder = new NativeCapableModuleBuilder(
         nca, "java3d.Java3DTestModule");

            Collection col = new HashSet();
            col.add("javax.media.j3d");
           builder.setTargetPackages(col);
            builder.setDelegateTargets(false);

           final NativeCapableModule module = builder.newModule();
            module.initModule();

           nca.add(module.getRootComponent());
           nca.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            nca.addWindowListener(new WindowAdapter() {
               public void windowClosing(WindowEvent e) {
                  module.destroyModule();
              }
           });
           nca.setBounds(100, 100, 640, 480);
           nca.setVisible(true);

           module.startModule();
}

4.ネイティブライブラリを配備

以下に説明する設定ファイルで指定したディレクトリに必要なネイティブライブラリを配備します。

ネイティブライブラリ設定ファイル(os.xml)
OSごとに利用するライブラリの設定をos.xmlに記述します。os.xmlはNativeCapableModuleのクラスファイルが存在するディレクトリルートに配備します。
配備例

記述例
<?xml version="1.0" encoding="UTF-8" ?>
<libraries>
<library arch="x86" name="Windows" path="native/x86/windows" />
<library arch="x86" name="Linux" path="native/x86/linux" />
</libraries>

archプロパティにはアーキテクチャをnameプロパティはOSの名前を指定します。
それぞれつぎのJavaプログラムで取得される文字列と前方一致を行います。
  System.getProperty("os.name");
  System.getProperty("os.arch");
pathプロパティにはネイティブライブラリの位置をos.xmlファイルからの相対パスで指定します。

5.AppletやJavaWebStart(JWS)として公開する場合の注意点
ネイティブライブラリを実行時に展開するためにサンドボックス外へのアクセスが必要となります。
そのため、AppletやJWSにする場合はJARファイルに署名を行う必要があります。
署名つきJARファイルの作成やJWSでのパーミッション設定などはここでは省略します。

2008年4月23日水曜日

NyARToolkit on Google Android!!

NyARToolkit を Google Android エミュレータ上で動作させました!!
ものすっごい、お馬鹿なミスで5日ぐらい悩まされましたが。。。

移植自体は非常にスムーズに、行きました。
OpenGL ESがdoubleが扱えなかったので、一部floatで取得する関数などを追加しましたが、NyARToolkit自体はほとんどいじらずに動きました。
これは一重にA虎@さんがシンプルにJavaで記述してくださった結果です。いやー、ありがたい!
とりあえず以下に画像をば。


現状ではエミュレータが動画キャプチャに対応していないため
キャプチャ部分は、JMFを使って書いたJavaサーバアプリにAndroidからソケット接続し画像を取得しています。
Live Camera Previews in Android
こちらを参考にさせていただきました。

ただし、エミュレータでは1つのアプリケーションが使えるメモリが最大16M程度であるらしく、
320*240*3の配列をコピーするのに4秒近く、マーカの認識に7秒かかってしまいます。
結果、一枚の画像がかかれるまでに10数秒です。とても使い物になりません。
(時々なぜか速くなって7秒台がでますが、、、)

何はともあれ、携帯電話でARが少しは近づいたかな、と。
さて、Applet, JWSまわりのフレームワーク化をまとめよう!

P.S.
A虎@さんがNyARToolkitの高速化を行っておられますので、新しいバージョンと統合次第、
ソースも公開します。

2008年4月20日日曜日

限定じゃんけんα!

先日弟とEカード(「賭博黙示録カイジ」というアニメでカイジと利根川が対決したゲーム)で遊んだ時に面白いゲームを思いついたので公開してみます。
(既に世の中にあるかもしれませんが、、、)

基本はジャンケン(グー、チョキ、パーのあれ)です。
そこに次のようなルールをつけます。
  1. プレイヤーは1対1で対戦
  2. 6ゲーム(あいこも一回とカウントしジャンケン6回)を1セットとし、4セット(計24ゲーム)行う
  3. グーで勝つと+5ポイント
  4. チョキ、パーで勝つと+2ポイント
  5. あいこの場合、負けているプレイヤーが+1ポイント(同点の場合はポイントなし)
  6. 6ゲーム目であいこの場合は勝敗がつくまで延長(両者のポイントが並んだ時点でセット終了)
  7. どちらかのプレイヤーが手を出さなかった場合手を出さなかったプレイヤーは無条件にチョキで負けたとし、勝ったプレイヤーが5ポイント獲得

(補足ルール - 当事者間で変更しても大丈夫です)
  • ジャンケンは基本的にお互いが手を決めてから行うものとする
  • 掛け声は「最初はグー、ジャンケンポン」が好ましい
  • どちらが掛け声を発してもいいですが、手が決まっていない場合は即座に「待って」と声をかける事で静止できるものとする
  • 「待って」は「最初はグー」を言い終わるまでに言わなければならない
  • 掛け声が始まってから「最初はグー」以前に「待って」が言われなかった場合ゲームは開始されたものとみなし、必ず双方ともに手を出さなければならない
    (片方が手を出さなかった場合は上記ルールを適用)
  • 紳士的にプレーすること

これだけでは解りにくいと思うので次に例を示します。

1セット目
ゲーム数プレイヤAプレイヤB勝敗A獲得ポイントB獲得ポイント解説
パーパーあいこ
パーパーあいこ
パーチョキB勝ち
グーグーあいこ負けているAに+1
パーグーA勝ち
パーグーA勝ちセット終了

1セット目結果:A-5ポイント、B-2ポイント

2セット目
ゲーム数プレイヤAプレイヤB勝敗A獲得ポイントB獲得ポイント解説
パーパーあいこ
グーチョキA勝ち
パーチョキB勝ち
グーグーあいこ負けているBに+1
パーグーA勝ち
パーパーあいこあいこなので延長
7(延長1)パーパーあいこあいこなので延長
8(延長2)チョキグーB勝ち10セット終了

2セット目結果:A-7ポイント、B-10ポイント

3セット目
ゲーム数プレイヤAプレイヤB勝敗A獲得ポイントB獲得ポイント解説
パーパーあいこ
パーパーあいこ
パーパーあいこ
パーグーA勝ち
パーパーあいこ負けているBに+1
パーパーあいこ同点でセット終了

3セット目結果:A-2ポイント、B-2ポイント

4セット目
ゲーム数プレイヤAプレイヤB勝敗A獲得ポイントB獲得ポイント解説
パーパーあいこ
パーパーあいこ
パーチョキB勝ち
グーパーB勝ち
グーグーあいこ負けているAに+1
パーグーA勝ちセット終了

4セット目結果:A-3ポイント、B-5ポイント

最終結果:A-17ポイント、B-19ポイントでBの勝ち

実際やってみると心理の読みあいがかなりエキサイティングです。
また、1回戦ではパーが常套手段となるなど考えどころも多くあると思います。
(心理)
チョキで負けた場合いきなり5ポイント差がつくから1回戦のチョキはかなり勇気がいる、、、
チョキはほぼ無いと考えれば、パーかグー、、、
パーなら最悪あいこスタート、、、ざわざわ、、、

ってな具合に。


もし遊んでみた方おられましたら、感想や攻略法なんか書き込んでもらえたら嬉しいです!!
では、エンジョイ限定ジャンケンα!!


(Eカード概要)
2人で行うゲームです。
皇帝、奴隷、市民という三つのカードをそれぞれ次のように配り
皇帝側プレイヤーには皇帝×1、市民×4
奴隷側プレイヤーは奴隷×1、市民×4
3ゲーム1セットで皇帝奴隷を入れ替えて合計4セット行うことで勝敗を決めるゲームです。
皇帝は市民に勝ち、市民は奴隷に勝ちます。ただし、皇帝は奴隷に負けます。
1ゲーム5回戦で、それぞれ一枚ずつ手持ちのカードから選んで対戦を行います。
皇帝側プレイヤーが勝つと1ポイント、奴隷側プレイヤーが勝つと5ポイント獲得です。
相手がどのタイミングで、皇帝を通してくるか、また奴隷で皇帝をうちに来るかを探り合う心理ゲームです。

    2008年4月17日木曜日

    メタセコローダなARなのJWSとApplet

    A虎@さん作NyARMqoViewerをJWS及びAppletにしました。
    (ご本人からの依頼ありです。なくてもやったかもしれませんが、、、笑)

    Webサーバーにあるメタセコイアデータを表示するARプログラムです。 インターネットとARToolkitの連携をコンセプトにした試作品です。

    とのことです。相変わらず面白いこと考えるな~と、関心しきり。
    オリジナルはこちら
    NyARMqoViewer

    JWS版
    http://www.ylab.ai.kyutech.ac.jp/~shiva/jws/nyartoolkit/mqoloader.jnlp

    Applet版
    http://www.ylab.ai.kyutech.ac.jp/~shiva/applet/nyartoolkit/mqoloader.htm

    マーカーPDFファイル
    http://www.ylab.ai.kyutech.ac.jp/~shiva/jws/nyartoolkit/sample/pattHiro.pdf

    サンプルURL
    http://www.ylab.ai.kyutech.ac.jp/~shiva/jws/nyartoolkit/sample/sample.xml
    起動後の画面に上記URLを入れてConnectをクリックすると、サンプルを試してみることが出来ます。
    3Dデータは三次元CG@七葉さんからnh0072.zipをお借りしました。

    (追記)
    3Dモデルの位置がずれていたようです。
    A虎@さんが正しく設定されたものを公開してくださいましたので、こちらをお使いください。
    http://nyatla.jp/nyartoolkit/app/NyARMqoViewerAPP/sample/sample.xml


    個人発行の証明書なので警告が出ますが、めげずに実行してやってくださいm(_ _)m

    ちなみに、設定XMLファイルは次のような形式で記述します。
    (A虎@さんのWikiからダウンロードできるソースコード中に詳しい定義が書かれています)
    オリジナルのものは、絶対パス(http://やfile:///ではじまる)指定でしたが、
    改造して(設定XMLファイルからの)相対パス指定を可能にしてあります。
    (絶対パスも使えます。 jar:とかも使えるはず、、、です)
    また、mqoファイルをZIPファイルから読み込めるようにも拡張してあります。
    2つ以上mqoが入っている場合は先に見つかったほうが選ばれます)
    直接mqoファイルを指定することも出来ます。


    <?xml version="1.0"?>
    <root>
    <version>NyARMqoViewer/0.1</version>
    <config>
    <ar_code>
    <url>patt.hiro</url>
    <size>80.0</size>
    </ar_code>
    <ar_param>
    <url>camera_para.dat</url>
    <screen>
    <x>640</x>
    <y>480</y>
    </screen>
    </ar_param>
    <frame_rate>15.0</frame_rate>
    </config>
    <content>
    <scale>0.2</scale>
    <comment>TEST
    <mqo_file>nh0072.zip
    </content>
    </root>


    動作報告とかいただけたら嬉しいです^^

    2008年4月8日火曜日

    NyARToolkit Eclipse Plugin 開発スタート

    NyARToolkit Eclipse Pluginなるものを作りはじめてみました。
    「手軽にNyARToolkitでの開発、公開ができる環境づくり」がコンセプトです。
    需要あるのか解りませんが、概ね自己満足で(笑)

    とりあえず、プロジェクトを作るとライブラリを自動的に設定して、ソース、出力フォルダを生成するところまでを実装してみました。
    NyARToolkit Eclipse Plugin
    ダウンロードしてpluginsフォルダに配置してください。
    Windows XP, Eclipse3.3, JRE 1.6.0_05 でのみ動作を確認しています。

    Eclipseを日本語化しないで使っているので日本語版での動作方法は想像で書きます。
    英語版:
    1.Package Explorer で右クリック
    2.New->Project...
    日本語版:
    1.パッケージ・エクスプローラ で右クリック
    2.新規->プロジェクト...

    後は共通です。
    3.NyARToolkit Project Wizard -> NyARToolkit Project Wizard を選択後 Next(次へ)
    4.Project Nameを適当に入力して Finish(完了)

    これで、NyARToolkitでの開発に必要なライブラリが設定されたJava プロジェクトが生成されます。
    現時点では、ライブラリとしてJMF, JOGL及びJavassistが入っています(ネイティブも含む)

    WebStartやAppletとしてエクスポート的な機能をつけようと思っているのですが、中々難しい。。。

    Javaプロジェクトを作る

    プログラム中でJavaプロジェクトを生成する方法。
    ライブラリをコピーしたりする部分も含まれているが、、、きにしな~い。

    private void doFinish(final String projectName, String srcFolderName,
    String binFolderName, String libFolderName,
    final IProgressMonitor monitor) throws CoreException {

    // プロジェクトを作成
    IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
    IProject project = root.getProject(projectName);
    project.create(monitor);
    project.open(monitor);

    // Java Nature をプロジェクトに追加
    IProjectDescription description = project.getDescription();
    String[] natures = description.getNatureIds();
    String[] newNatures = new String[natures.length + 1];
    System.arraycopy(natures, 0, newNatures, 0, natures.length);
    newNatures[natures.length] = JavaCore.NATURE_ID;
    description.setNatureIds(newNatures);
    project.setDescription(description, monitor);
    // Java プロジェクトを作成
    final IJavaProject javaProject = JavaCore.create(project);

    Set entries = new HashSet();
    // Adding source path
    IPath sourcePath = javaProject.getPath().append(srcFolderName);
    //ソースフォルダを作成
    IFolder sourceDir = project.getFolder(new Path(srcFolderName));
    if (!sourceDir.exists()) {
    sourceDir.create(false, true, null);
    }
    // 出力先フォルダを作成
    IPath outputPath = javaProject.getPath().append(binFolderName);
    IFolder outputDir = project.getFolder(new Path(binFolderName));
    if (!outputDir.exists()) {
    outputDir.create(false, true, null);
    }
    // ソースフォルダ、出力フォルダを設定
    IClasspathEntry srcEntry = JavaCore.newSourceEntry(sourcePath,
    new IPath[] {}, outputPath);
    entries.add(srcEntry);

    // ライブラリ を追加
    final IPath libPath = javaProject.getPath().append(libFolderName);
    final IFolder libDir = project.getFolder(new Path(libFolderName));
    if (!libDir.exists()) {
    libDir.create(false, true, null);
    }
    final IPath conNativePath = javaProject.getPath().append(
    libFolderName + "/native");
    IFolder conNativeDir = project.getFolder(new Path(libFolderName
    + "/native"));
    if (!conNativeDir.exists()) {
    conNativeDir.create(false, true, null);
    }
    // ライブラリをコピー
    String[] jars = new String[] { "gluegen-rt.jar", "jmf.jar", "jogl.jar",
    "javassist.jar", "NyARToolKit.jar" };
    for (String string : jars) {
    InputStream is = getClass().getResourceAsStream(
    "/resources/lib/" + string);
    IFile f = libDir.getFile(string);
    f.create(is, true, null);
    }
    String[] libs = new String[] { "gluegen-rt.dll", "jmacm.dll",
    "jmam.dll", "jmcvid.dll", "jmdaud.dll", "jmdaudc.dll",
    "jmddraw.dll", "jmfjawt.dll", "jmg723.dll", "jmgdi.dll",
    "jmgsm.dll", "jmh261.dll", "jmh263enc.dll", "jmjpeg.dll",
    "jmmci.dll", "jmmpa.dll", "jmmpegv.dll", "jmutil.dll",
    "jmvcm.dll", "jmvfw.dll", "jmvh263.dll", "jogl_awt.dll",
    "jogl_cg.dll", "jogl.dll", "jsound.dll" };
    for (String string : libs) {
    InputStream is = getClass().getResourceAsStream(
    "/resources/native/" + string);
    IFile f = conNativeDir.getFile(string);
    f.create(is, true, null);
    }

    IClasspathContainer libContainer = new IClasspathContainer() {
    public IClasspathEntry[] getClasspathEntries() {
    System.out.println("get!");
    List ices = new ArrayList();
    try {
    IResource[] rs = libDir.members();
    for (IResource r : rs) {
    if (r instanceof IFile) {
    IFile f = (IFile) r;
    System.out.println("\t" + f);
    IClasspathEntry entry = JavaCore.newLibraryEntry(f
    .getFullPath(), null, null, false);
    ices.add(entry);
    }
    }
    } catch (CoreException e) {
    e.printStackTrace();
    }
    return ices.toArray(new IClasspathEntry[ices.size()]);
    }

    public String getDescription() {
    return "Application library container";
    }

    public int getKind() {
    return IClasspathContainer.K_APPLICATION;
    }

    public IPath getPath() {
    return libPath;
    }
    };

    JavaCore.setClasspathContainer(libPath,
    new IJavaProject[] { javaProject }, // value for 'myProject'
    new IClasspathContainer[] { libContainer }, null);
    // no source, no source, not exported

    IClasspathEntry libEntry = JavaCore.newContainerEntry(libPath, null,
    new IClasspathAttribute[] { JavaCore.newClasspathAttribute(
    JavaRuntime.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY,
    conNativePath.toString().substring(1)) }, false);
    entries.add(libEntry);

    // デフォルトJREを追加
    entries.add(JavaRuntime.getDefaultJREContainerEntry());
    javaProject.setRawClasspath(entries.toArray(new IClasspathEntry[entries
    .size()]), monitor);

    monitor.worked(1);
    }

    2008年4月3日木曜日

    NyARToolkitをJWSとAppletにする!

    MixiのJavaコミュで"A虎@"さんが公開されたARToolkitのJava実装NyARToolkitを
    JavaWebStart化、及びJava Applet化してみました。

    NyARToolkitのページ
    http://nyatla.jp/nyartoolkit/wiki/index.php

    JWSサンプル
    http://www.ylab.ai.kyutech.ac.jp/~shiva/jws/nyartoolkit/simplelite.jnlp

    Java Applet サンプル
    http://www.ylab.ai.kyutech.ac.jp/~shiva/applet/nyartoolkit/index.htm

    マーカー用PDF(NyARToolkitに同封されているものです)
    http://www.ylab.ai.kyutech.ac.jp/~shiva/jws/nyartoolkit/pattHiro.pdf


    ビデオキャプチャデバイスを接続した状態で起動してください。
    デバイス探索に2,3分かかる場合があります。
    証明書が個人で発行したものであるため、警告が出ますが無視してください。

    JMFインストール済みでない環境での動作が確認されていないので、
    動いた!!という方、報告いただけると嬉しいです^^