SketchUpのモデルでSATCHのアプリ作成
SketchUpのモデルをSATCHで使う
(本編はこちら)
画像認識後の画面はこんな感じ。
ピンチアウトして拡大。
ピンチインして縮小。
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①:
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/
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; } }
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(); } }
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