「SketchUpのモデルをSATCHで使う」の続編。

目次

SketchUpのモデルをSATCHで使う
(本編はこちら)

SATCHのLuaを修正

Eclipseでアプリ作成

実装予定のジェスチャ

TOP




SATCHのLuaを修正

画像認識後の画面はこんな感じ。





ピンチアウトして拡大。





ピンチインして縮小。





SATCHを使っている場合、javaコードでタッチイベントは拾えませんでした。

java単体なら、ActivityやViewで実装するところですが、ここではSATCHのLuaにイベントリスナーと、処理の一部を実装します。

また、本来ならイベントを拾った後の処理(ピンチイン/アウトの判定)もLuaに記述するところですが、 スクリーンタッチのイベントなどは実機でしか確認できないので(多分)、ここではイベント時のデータをjava側に送っておき、
そのデータでピンチイン/アウトの判定を行い、再度結果をLua側に送って動作させる、というまことに面倒な手続きになっています。

その気がある方は、Lua側にすべて実装するように書き直してみてください(^^)。

SATCH側

本編では、Houseは図面上に乗るような形でしたが、ここでは変更して、OrientationのLocal Xは0。



Luaは本編で作成した、house_manager.luaです。

本編では、ダミーのようなコードを1行入れただけでした、が大幅に変更。

local im = getInputManager()
local scene = getCurrentScene()
local touchscreen = nil

if isTouchScreenDeviceAvailable() then
touchscreen  = TouchScreen  (im:getDevice(TIINPUT_TOUCHSCREENDEVICE  ))
end

local touches = nil
local s = ""

local componentInterface = getComponentInterface()

--add
ref = Object3D(scene:getObjectByName("entity"));
local initialScale =  Vector3()
local currentScale =  Vector3()
local newScale =  Vector3()

--①
ref:getScale(initialScale)
--②
local down_flag = false
--③
local scale_param = 0.1

if touchscreen then
    touchscreen:acquire()
    repeat
        touches = touchscreen:getTouches()
        if #touches > 0 then
            
            down_flag = true
            
            --④
            s = "Down:" .. #touches .. "/"
            
            for i, touch in pairs(touches) do
            	--⑤
                s = s .. touch.position.x .. "," .. touch.position.y .. "," .. touch.taps .. "/"
                --⑥
                componentInterface:executeAppFunc("sendLog", s)
                
            end
            
            
            
        else
            if down_flag == true then
            	--⑦
                componentInterface:executeAppFunc("sendLog", "Up")
                down_flag = false
                
            end
        end
        
        --⑧
        isCommand, command = componentInterface:pullCommand()
        if isCommand then
            if command["CommandName"]=="fingerAction" then
                
                
                local f_type = command["arg0"] --pinchin/pinchout
                local f_dist = command["arg1"] --distance 現状では使いません
            
                ref:getScale(currentScale)
                
                    --pinchout
                if f_type == "pinchout" then
                    
                    local s_x = currentScale:getX() + scale_param
                    local s_y = currentScale:getY() + scale_param
                    local s_z = currentScale:getZ() + scale_param
                    
                    newScale:setX(s_x)
                    newScale:setY(s_y)
                    newScale:setZ(s_z)
                    ref:setScale(newScale)
                    
                end
                
                --pinchin
                if f_type == "pinchin" then
                    
                    local s_x = currentScale:getX() - scale_param
                    local s_y = currentScale:getY() - scale_param
                    local s_z = currentScale:getZ() - scale_param
                    
                    if s_x < initialScale:getX() then
                        s_x = 0.3
                        s_y = 0.3
                        s_z = 0.3
                    end
                    
                    newScale:setX(s_x)
                    newScale:setY(s_y)
                    newScale:setZ(s_z)
                    ref:setScale(newScale)
                    
                end
                
            end
        end
    
    
    until coroutine.yield()
end

①:
   初期のスケールをinitialScaleにセット
   
②:
   タップダウンイベントが起こった場合のフラグ
   タップアップ(擬似イベント)が起こった時、一度だけイベントを拾うためのもの
③:
   スケールを拡大・縮小する際のインターバル値
   
④:
   #touchesは、指が何本スクリーンに触れているかの数
   
⑤:
   タップされている位置を情報として変数に格納
   
⑥:
   情報をjava側に通知
   
⑦:
   タップイベントが終わった時、一度だけ、java側に通知
   
⑧:
   java側からのコマンド待ち受け
   コマンドの種類によって、スケールを変更


Scenario_aフォルダーをデスクトップに作り、シナリオをここにExportして、コンテンツを作成。

TOP


Eclipseでアプリ作成

Lua側から送られてくるデータのサンプルログ(ピンチインの動作)

Down:の右横の数字

2はタップの指の数。マルチでタップした場合も、タイミングによっては、1が送られてくる場合もある。

スラッシュで囲まれているのは座標。必要なのは、2本の指の座標が記述されている部分。

座標値の部分の3つ目の、1の数字。tapsの値ですが、今回は不要。

06-01 16:40:40.216: I/house(31965): Down:2/132.66667175293,193.33334350586,1/
06-01 16:40:40.216: I/house(31965): Down:2/132.66667175293,193.33334350586,1/433.33334350586,81.333343505859,1/
06-01 16:40:40.266: I/house(31965): Down:2/159.33334350586,192.66667175293,1/
06-01 16:40:40.266: I/house(31965): Down:2/159.33334350586,192.66667175293,1/386,108,1/
06-01 16:40:40.276: I/house(31965): Down:2/189.33334350586,188,1/
06-01 16:40:40.286: I/house(31965): Down:2/189.33334350586,188,1/369.33334350586,114,1/
06-01 16:40:40.286: I/house(31965): Down:2/207.33334350586,188,1/
06-01 16:40:40.286: I/house(31965): Down:2/207.33334350586,188,1/363.33334350586,116.66667175293,1/
06-01 16:40:40.306: I/house(31965): Down:2/224,184.66667175293,1/
06-01 16:40:40.306: I/house(31965): Down:2/224,184.66667175293,1/358.66668701172,118,1/
06-01 16:40:40.356: I/house(31965): Down:2/266.66668701172,176.66667175293,1/
06-01 16:40:40.356: I/house(31965): Down:2/266.66668701172,176.66667175293,1/343.33334350586,124.66667175293,1/



Eclipseを起動して、プロジェクトを新規に作成

File -> New -> Other

Android -> Android Project

プロジェクト名は「house」。

Project Name -> house
Build Target -> Android 2.3.3
Package Name -> com.kddi.satch.house

プロジェクトの"assets"フォルダにScenarioフォルダーを作成、その中に作成済みのScenario_aフォルダーをコピー。

プロジェクトフォルダーに以下のlibsフォルダーをコピー。
これをダウンロードして解凍後コピー。
libs

プロジェクトをRefresh。

satch.jarを右クリックしてビルドパスを通します。
Build Path -> Add to Build Path

プロジェクトにパッケージを追加

File -> New -> Package
Name -> com.kddi.satch.houseactivity

このパッケージに、抽象クラスを作成。

com.kddi.satch.houseactivityを選んで、

File -> New -> Class
Name ->HouseActivity_simple
Modifiers -> Public/abstract

コードは以下の通り。

【HouseActivity_simple.java】

package com.kddi.satch.houseactivity;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.os.Handler;
import android.view.KeyEvent;
import android.view.View;
import android.widget.FrameLayout;

import com.kddi.satch.LoadScenarioStatus;
import com.kddi.satch.ARViewer;


public abstract class HouseActivity_simple extends Activity  {
    protected abstract String getSampleScenarioName();
    protected abstract String getSampleLogTag();
    protected boolean _isInitializedCorrectly;
    protected ARViewer _kddiComponent;
    protected FrameLayout _frameLayout;
    
    public void resetMembers(){
        _isInitializedCorrectly = false;
        _frameLayout = null;
        _kddiComponent = null;
    }
    private static final int DIALOG_EXIT = 0;
    
    public void initComponent(){
        _isInitializedCorrectly = false;
        _kddiComponent = new ARViewer(this);
        // This FrameLayout must be empty (but initialized) when you pass it to the kddiComponent.initialize() method.
        _frameLayout = new FrameLayout(this);
        _kddiComponent.initialize(_frameLayout);
        _isInitializedCorrectly = true;
    }
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Add null to AR Viewer Library compornent's reference.
        resetMembers();
    }
    
    

    
    
    
    @Override
    public void onRestart() {
        super.onRestart();
    }
    
    @Override
    public void onStart() {
        super.onStart();
        // Create AR Viewer Library compornent.
        initComponent();
        postInitComponent();
        initContentView();
        if (_isInitializedCorrectly) {
            // Do authorize and madia is loaded.
            // You must call loadScenario() method.
            loadScenario();
        }
    }
    
    @Override
    public void onResume(){
        super.onResume();
        if (_isInitializedCorrectly) {
            // GL context is recreated and media is reloaded.
            _kddiComponent.onResume();
            reservePlayScenario();
        }
    }
    
    @Override
    public void onPause() {
        // When the activity is paused the GL context is destroyed, so all media is unloaded.
        if (_isInitializedCorrectly) {
            cancelReservePlayScenario();
        if (_kddiComponent.checkLoadScenarioStatus() == LoadScenarioStatus.COMPLETE) {
            _kddiComponent.pauseScenario();
        }
            _kddiComponent.onPause();
        }
        super.onPause();
    }
    
    @Override
    public void onStop() {
        releaseContentView();
        // Destroy AR Viewer Library Objects.
        if (_isInitializedCorrectly){
            _kddiComponent.terminate();
            _kddiComponent = null;
            _frameLayout = null;
        }
        super.onStop();
    }
    
    @Override
    public void onDestroy() {
        // Destroy AR Viewer Library Objects.
        if (_isInitializedCorrectly){
            _frameLayout = null;
            _kddiComponent = null;
        }
        super.onDestroy();
        resetMembers(); // forced clean
    }
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent msg){
        switch(keyCode){
            case android.view.KeyEvent.KEYCODE_BACK :
            showDialog( DIALOG_EXIT );
            return true;
        }
        return false;
    }
    
    public void postInitComponent(){
        // override this if you need to do some special handling on the component after standard initialization
        if (_isInitializedCorrectly) {
            _kddiComponent.activateAutoFocusOnDownEvent(true);
        }
    }
    
    public void initContentView(){
        // override this if you need to do some special handling on the component after standard initialization
        if (_isInitializedCorrectly) {
            // you'll probably use some other UI object as the content view that itself will embed the component's frame layout -- here you can change all this
            // by default, the frame layout containing DFusion will be the activity content view
            setContentView(_frameLayout);
        }
    }
    
    public void releaseContentView(){
        // override this if you need to do some special handling on the component after standard initialization
        if (_isInitializedCorrectly) {
            // do here the release of the your UI instances (if customized)
        }
    }
    
    public void loadScenario(){
        ApplicationInfo appInfo = null;
        PackageManager packMgmr = getApplicationContext().getPackageManager();
        try {
            appInfo = packMgmr.getApplicationInfo(getPackageName(), 0);
        } catch (NameNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException("Unable to locate assets, aborting...");
        }
        String dpdfile = appInfo.sourceDir + getSampleScenarioName();
        _kddiComponent.loadScenario(dpdfile);
    }
    
    // Set polling rate for loading the media.
    private final int REPEAT_INTERVAL = 100;
    private Handler handler = new Handler();
    private Runnable runnable = null;
    
    
    //
    private void reservePlayScenario(){
        if (runnable == null){
            runnable = new Runnable(){
                @Override
                public void run(){
                    LoadScenarioStatus status = _kddiComponent.checkLoadScenarioStatus();
                    if (status == LoadScenarioStatus.CANCEL){
                        // cancel(appli suspend)
                    }else if (status == LoadScenarioStatus.COMPLETE){
                        // Ready to play scenario
                        _frameLayout.setVisibility(View.VISIBLE);
                        _kddiComponent.playScenario();
                    }else if (
                        status == LoadScenarioStatus.ERROR_NETWORK_UNUSABLE ||
                        // faild to load a media becase of no network connection.
                        status == LoadScenarioStatus.ERROR_NETWORK ||
                        // faild to load a media becase of network error.
                        status == LoadScenarioStatus.ERROR_SOFTWAREKEY ||
                        // faild to load a media becase software key has not be found on server.
                        status == LoadScenarioStatus.ERROR_CONTENT_STOPPED ||
                        // faild to load a media becase content has stopped.
                        status == LoadScenarioStatus.ERROR_SERVER ||
                        // faild to load a media becase of server error.
                        status == LoadScenarioStatus.ERROR_ETC
                        // faild to load a media becase of another error.
                        ){
                    // error
                    }else{
                        handler.postDelayed(this, REPEAT_INTERVAL);
                    }
                }
            };
            handler.postDelayed(runnable, REPEAT_INTERVAL);
        }
    }
    
    //
    
    private void cancelReservePlayScenario(){
        if (handler != null && runnable != null){
            handler.removeCallbacks(runnable);
            runnable = null;
        }
    }
    
    protected Dialog onCreateDialog(int id) {
        Dialog dialog;
        switch(id) {
            case DIALOG_EXIT:
            {
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setMessage(
                    "Really want to quit the sample?"
                )
                .setCancelable(true)
                .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        finish();
                    }})
                    .setNegativeButton("No", new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            dialog.cancel();
                        }});
                    dialog = builder.create();
                    break;
            }
            default:
            dialog = null;
        }
        return dialog;
    }
}


com.kddi.satch.houseパッケージにクラスを追加

com.kddi.satch.houseを選択して

File -> New -> Class
Name ->HouseSetting
Modifiers -> Public

【HouseSetting.java】

package com.kddi.satch.house;

import com.kddi.satch.ARViewer;
import com.kddi.satch.TermOfUseStatus;

import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceActivity;
import android.widget.Toast;

public class HouseSetting extends PreferenceActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.pref);
        
        boolean check = false;

        CheckBoxPreference checkbox = (CheckBoxPreference) findPreference(getString(R.string.key_chackbox));

        final ARViewer _kddiComponent = new ARViewer(this);
        final TermOfUseStatus status = _kddiComponent.getPolicy();
        if (status == TermOfUseStatus.AGREE){
            check = true;
        }

        checkbox.setChecked(check);

        checkbox.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
                    public boolean onPreferenceChange(Preference preference, Object newValue) {
                        boolean result = _kddiComponent.setPolicy((Boolean)newValue);
                        showToast(result);
                        return true;
                    }
                });
    }

    private void showToast(boolean b){
        String message = getString(R.string.failed);
        if (b){
            message = getString(R.string.success);
        }
        Toast.makeText(this, message, Toast.LENGTH_LONG).show();
    }
}


HouseActivity.javaを編集

Luaとやり取りしている部分です。

Lua側から送られるデータを見て、ピンチイン・アウトを判定。

タップダウン(Down)で送られるデータを順次、ArrayListに格納しておき、

Upが送られた時点で、データを解析。判定結果をLua側に返します。

以下の変数で、座標が1とか2で区別されているものがありますが、これはLua側から送られる指の順番に対応していて、ユニークです。

【HouseActivity.java】

package com.kddi.satch.house;

import com.kddi.satch.houseactivity.HouseActivity_simple;

import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;


public class HouseActivity extends HouseActivity_simple {
    private static final String THIS_CLASS_SHORT_NAME = HouseActivity.class.getName().replace("com.kddi.satch.house.", "");
    private static final String THIS_LOGTAG  = THIS_CLASS_SHORT_NAME;
    // Set AR scenario file path.
    private static final String SCENARIO_NAME = "/assets/Scenario/Scenario_a/house.dpd";
    protected String getSampleScenarioName() { return SCENARIO_NAME; }
    protected String getSampleLogTag() { return THIS_LOGTAG;   }
   //
    ArrayList<String> act = new ArrayList<String>();
    
    @Override
    public void postInitComponent(){
        super.postInitComponent();
        
        if (_isInitializedCorrectly) {
            _kddiComponent.activateAutoFocusOnDownEvent(true);
            _kddiComponent.registerCommunicationCallback("sendLog", this, "sendLog");
        }
        
    }
    
    public void sendLog(String[] arrayOfString) {
        //Log.i("house",arrayOfString[0]);
        String pinch_type = "";
        boolean res = arrayOfString[0].equals("Up");
        if (res == true) {
            //
            int alist_len = act.size();
            if (alist_len != 0) {
                String data_info = "";
                
                int act_len = act.size();
                int[]temp_touches = new int[act_len];
                int[]touches = new int[act_len];
                int[]pos1_x = new int[act_len];
                int[]pos1_y = new int[act_len];
                int[]pos2_x = new int[act_len];
                int[]pos2_y = new int[act_len];
                
                for (int i = 0 ; i < act_len ; i++){
                    data_info = act.get(i);
                    String[] temp_info = data_info.split("/");
                    temp_touches[i] = Integer.parseInt(temp_info[0]);
                    touches[i] = Integer.parseInt(temp_info[0]);
                    
                    String[] coord1 = temp_info[1].split(",");
                    pos1_x[i] = (int)Float.parseFloat(coord1[0]);
                    pos1_y[i] = (int)Float.parseFloat(coord1[1]);
                    
                    
                    String[] coord2 = temp_info[2].split(",");
                    pos2_x[i] = (int)Float.parseFloat(coord2[0]);
                    pos2_y[i] = (int)Float.parseFloat(coord2[1]);
                    
                    //Log.i("data",pos2_x[i] + "");
                    
                }
                
                Arrays.sort(temp_touches);
                int max_touches = temp_touches[temp_touches.length - 1];
                
                int first_pos1_x = 0;
                int first_pos1_y = 0;
                int first_pos2_x = 0;
                int first_pos2_y = 0;
                
                int last_pos1_x = 0;
                int last_pos1_y = 0;
                int last_pos2_x = 0;
                int last_pos2_y = 0;
                
                int count = 0;
                
                for (int i = 0 ; i < act_len ; i++){
                    if (touches[i] == max_touches){
                        if (count == 0) {
                            first_pos1_x = pos1_x[i];
                            first_pos1_y = pos1_y[i];
                            
                            first_pos2_x = pos2_x[i];
                            first_pos2_y = pos2_y[i];
                            
                        }
                        
                        last_pos1_x = pos1_x[i];
                        last_pos1_y = pos1_y[i];
                        
                        last_pos2_x = pos2_x[i];
                        last_pos2_y = pos2_y[i];
                        
                        count++;
                    }
                }
                
                //最初の2点のと最後の2点の距離を比較
                //最初の距離が最後の距離より長い->pinchin
                //最初の距離が最後の距離より短い->pinchout
                
    
                int first_distance = (int)Math.sqrt(Math.pow(first_pos1_x - first_pos2_x, 2) + Math.pow(first_pos1_y - first_pos2_y, 2));
                int last_distance = (int)Math.sqrt(Math.pow(last_pos1_x - last_pos2_x, 2) + Math.pow(last_pos1_y - last_pos2_y, 2));
                
                if (first_distance > last_distance) {
                    Log.i("pinch","In");
                    pinch_type = "pinchin";
                }else if (first_distance < last_distance){
                    Log.i("pinch","Out");
                    pinch_type = "pinchout";
                }
                
            }
            
            
            if (pinch_type != "") {
            
                //
                String[] args = new String[2];
                    args[0] = pinch_type;
                    args[1] = "0";//dummy
                   _kddiComponent.enqueueCommand("fingerAction", args);
            }
                //
            act.clear();
        }else{
            String[] temp1 = arrayOfString[0].split(":");
            
            res = temp1[0].equals("Down");
            if (res == true) {
                String[] temp2 = temp1[1].split("/");
                
                int len = temp2.length;
                if (len == 3) {
                    act.add(temp1[1]);
                }
            }
        }
    }
}


TOP


実装予定のジェスチャ

こんな感じ。

⑥と⑦が、ピンチアウト・イン。





①:シングル・タップ
②:ダブル・タップ
③:フリック
④:スワイプ
⑤:サークル
⑥:ピンチアウト
⑦:ピンチイン
⑧:ツィスト
⑨、⑩、⑪:応用形

これらは、比較的単純なジェスチャです。時間と距離で判定できると思います。
⑤は、ベクトル角を使い、⑧は交差判定を使います。
実装用のアルゴリズムはまた後で。

TOP