Files
rtabmap/app/android/src/com/introlab/rtabmap/RTABMapActivity.java

1133 lines
40 KiB
Java

package com.introlab.rtabmap;
import java.io.File;
import java.io.FilenameFilter;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Point;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Debug;
import android.text.Editable;
import android.text.InputType;
import android.util.Log;
import android.view.Display;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.google.atap.tangoservice.Tango;
// The main activity of the application. This activity shows debug information
// and a glSurfaceView that renders graphic content.
public class RTABMapActivity extends Activity implements OnClickListener {
// Tag for debug logging.
private static final String TAG = RTABMapActivity.class.getSimpleName();
// The minimum Tango Core version required from this application.
private static final int MIN_TANGO_CORE_VERSION = 6804;
// The package name of Tang Core, used for checking minimum Tango Core version.
private static final String TANGO_PACKAGE_NAME = "com.projecttango.tango";
public static final String EXTRA_KEY_PERMISSIONTYPE = "PERMISSIONTYPE";
public static final String EXTRA_VALUE_ADF = "ADF_LOAD_SAVE_PERMISSION";
// GLSurfaceView and renderer, all of the graphic content is rendered
// through OpenGL ES 2.0 in native code.
private Renderer mRenderer;
private GLSurfaceView mGLView;
ProgressDialog mProgressDialog;
// Screen size for normalizing the touch input for orbiting the render camera.
private Point mScreenSize = new Point();
private MenuItem mItemPause;
private MenuItem mItemSave;
private MenuItem mItemOpen;
private MenuItem mItemPostProcessing;
private MenuItem mItemExport;
private MenuItem mItemLocalizationMode;
private MenuItem mItemTrajectoryMode;
private String mOpenedDatabasePath = "";
private String mTempDatabasePath = "";
private String mNewDatabasePath = "";
private String mWorkingDirectory = "";
private int mMaxDepthIndex = 5;
private int mMeshAngleToleranceIndex = 1;
private int mMeshTriangleSizeIndex = 0;
private int mParamUpdateRateHzIndex = 1;
private int mParamTimeThrMsIndex = 1;
private int mParamMaxFeaturesIndex = 4;
private int mParamLoopThrMsIndex = 1;
private LinearLayout mLayoutDebug;
private int mTotalLoopClosures = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(R.string.menu_name);
// Query screen size, the screen size is used for computing the normalized
// touch point.
Display display = getWindowManager().getDefaultDisplay();
display.getSize(mScreenSize);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// Setting content view of this activity.
setContentView(R.layout.activity_rtabmap);
// Buttons for selecting camera view and Set up button click listeners.
findViewById(R.id.first_person_button).setOnClickListener(this);
findViewById(R.id.third_person_button).setOnClickListener(this);
findViewById(R.id.top_down_button).setOnClickListener(this);
// OpenGL view where all of the graphics are drawn.
mGLView = (GLSurfaceView) findViewById(R.id.gl_surface_view);
// Configure OpenGL renderer
mGLView.setEGLContextClientVersion(2);
// Configure the OpenGL renderer.
mRenderer = new Renderer();
mGLView.setRenderer(mRenderer);
mLayoutDebug = (LinearLayout) findViewById(R.id.debug_layout);
mLayoutDebug.setVisibility(LinearLayout.GONE);
mProgressDialog = new ProgressDialog(this);
mRenderer.setProgressDialog(mProgressDialog);
// Check if the Tango Core is out dated.
if (!CheckTangoCoreVersion(MIN_TANGO_CORE_VERSION)) {
Toast.makeText(this, "Tango Core out dated, please update in Play Store", Toast.LENGTH_LONG).show();
finish();
return;
}
mOpenedDatabasePath = "";
mTempDatabasePath = "";
mNewDatabasePath = "";
mWorkingDirectory = "";
mTotalLoopClosures = 0;
if(Environment.getExternalStorageState().compareTo(Environment.MEDIA_MOUNTED)==0)
{
File extStore = Environment.getExternalStorageDirectory();
mWorkingDirectory = extStore.getAbsolutePath() + "/" + getString(R.string.app_name) + "/";
extStore = new File(mWorkingDirectory);
extStore.mkdirs();
mTempDatabasePath = mWorkingDirectory + "rtabmap.tmp.db";
extStore = new File(mTempDatabasePath);
if(extStore.exists())
{
extStore.delete();
}
}
else
{
// show warning that data cannot be saved!
Toast.makeText(getApplicationContext(),
String.format("Failed to get external storage path (SD-CARD, state=%s). Saving disabled.",
Environment.getExternalStorageState()), Toast.LENGTH_LONG).show();
}
RTABMapLib.initialize(this);
RTABMapLib.openDatabase(mTempDatabasePath);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// Check which request we're responding to
if (requestCode == Tango.TANGO_INTENT_ACTIVITYCODE) {
// Make sure the request was successful
if (resultCode == RESULT_CANCELED) {
Toast.makeText(this, "Motion Tracking Permissions Required!",
Toast.LENGTH_SHORT).show();
finish();
}
}
}
@Override
protected void onResume() {
super.onResume();
Log.i(TAG, String.format("onResume()"));
if (Tango.hasPermission(this, Tango.PERMISSIONTYPE_MOTION_TRACKING)) {
mGLView.onResume();
mTotalLoopClosures = 0;
if(mItemOpen != null)
{
mItemOpen.setEnabled(false);
mItemPause.setChecked(false);
mItemSave.setEnabled(false);
mItemExport.setEnabled(false);
mItemPostProcessing.setEnabled(false);
}
if(RTABMapLib.onResume()!=0)
{
Toast.makeText(getApplicationContext(),
String.format("Failed to connect with Tango!"), Toast.LENGTH_SHORT).show();
}
} else {
Log.i(TAG, String.format("Asking for motion tracking permission"));
startActivityForResult(
Tango.getRequestPermissionIntent(Tango.PERMISSIONTYPE_MOTION_TRACKING),
Tango.TANGO_INTENT_ACTIVITYCODE);
}
}
@Override
protected void onPause() {
super.onPause();
mGLView.onPause();
// Delete all the non-OpenGl resources.
RTABMapLib.onPause();
mOpenedDatabasePath = "";
RTABMapLib.openDatabase(mTempDatabasePath);
}
@Override
public void onClick(View v) {
// Handle button clicks.
switch (v.getId()) {
case R.id.first_person_button:
RTABMapLib.setCamera(0);
break;
case R.id.third_person_button:
RTABMapLib.setCamera(1);
break;
case R.id.top_down_button:
RTABMapLib.setCamera(2);
break;
default:
return;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// Pass the touch event to the native layer for camera control.
// Single touch to rotate the camera around the device.
// Two fingers to zoom in and out.
int pointCount = event.getPointerCount();
if (pointCount == 1) {
float normalizedX = event.getX(0) / mScreenSize.x;
float normalizedY = event.getY(0) / mScreenSize.y;
RTABMapLib.onTouchEvent(1,
event.getActionMasked(), normalizedX, normalizedY, 0.0f, 0.0f);
}
if (pointCount == 2) {
if (event.getActionMasked() == MotionEvent.ACTION_POINTER_UP) {
int index = event.getActionIndex() == 0 ? 1 : 0;
float normalizedX = event.getX(index) / mScreenSize.x;
float normalizedY = event.getY(index) / mScreenSize.y;
RTABMapLib.onTouchEvent(1,
MotionEvent.ACTION_DOWN, normalizedX, normalizedY, 0.0f, 0.0f);
} else {
float normalizedX0 = event.getX(0) / mScreenSize.x;
float normalizedY0 = event.getY(0) / mScreenSize.y;
float normalizedX1 = event.getX(1) / mScreenSize.x;
float normalizedY1 = event.getY(1) / mScreenSize.y;
RTABMapLib.onTouchEvent(2, event.getActionMasked(),
normalizedX0, normalizedY0, normalizedX1, normalizedY1);
}
}
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
Log.i(TAG, "called onCreateOptionsMenu;");
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.optionmenu, menu);
mItemPause = menu.findItem(R.id.pause);
mItemSave = menu.findItem(R.id.save);
mItemOpen = menu.findItem(R.id.open);
mItemPostProcessing = menu.findItem(R.id.post_processing);
mItemExport = menu.findItem(R.id.export);
mItemLocalizationMode = menu.findItem(R.id.localization_mode);
mItemTrajectoryMode = menu.findItem(R.id.trajectory_mode);
mItemSave.setEnabled(false);
mItemExport.setEnabled(false);
mItemOpen.setEnabled(false);
mItemPostProcessing.setEnabled(false);
return true;
}
private void updateStatsUI(
int nodes,
int words,
int points,
int polygons,
float updateTime,
int loopClosureId,
int databaseMemoryUsed,
int inliers,
int featuresExtracted,
float hypothesis,
int nodesDrawn)
{
if(mItemPause!=null)
{
((TextView)findViewById(R.id.status)).setText(mItemPause.isChecked()?"Paused":mItemLocalizationMode.isChecked()?"Localization":"Mapping");
}
((TextView)findViewById(R.id.points)).setText(String.valueOf(points));
((TextView)findViewById(R.id.polygons)).setText(String.valueOf(polygons));
((TextView)findViewById(R.id.nodes)).setText(String.format("%d (%d shown)", nodes, nodesDrawn));
((TextView)findViewById(R.id.words)).setText(String.valueOf(words));
((TextView)findViewById(R.id.memory)).setText(String.valueOf(Debug.getNativeHeapAllocatedSize()/(1024*1024)));
((TextView)findViewById(R.id.db_size)).setText(String.valueOf(databaseMemoryUsed));
((TextView)findViewById(R.id.inliers)).setText(String.valueOf(inliers));
((TextView)findViewById(R.id.features)).setText(String.valueOf(featuresExtracted));
((TextView)findViewById(R.id.update_time)).setText(String.format("%.3f", updateTime));
((TextView)findViewById(R.id.hypothesis)).setText(String.format("%.3f (%d)", hypothesis, loopClosureId));
if(loopClosureId > 0)
{
++mTotalLoopClosures;
Toast.makeText(this, "Loop closure detected!", Toast.LENGTH_SHORT).show();
}
((TextView)findViewById(R.id.total_loop)).setText(String.valueOf(mTotalLoopClosures));
}
// called from jni
public void updateStatsCallback(
final int nodes,
final int words,
final int points,
final int polygons,
final float updateTime,
final int loopClosureId,
final int databaseMemoryUsed,
final int inliers,
final int features,
final float hypothesis,
final int nodesDrawn)
{
Log.i(TAG, String.format("updateStatsCallback()"));
runOnUiThread(new Runnable() {
public void run() {
updateStatsUI(nodes, words, points, polygons, updateTime, loopClosureId, databaseMemoryUsed, inliers, features, hypothesis, nodesDrawn);
}
});
}
private void rtabmapInitEventUI(
int status,
String msg)
{
Log.i(TAG, String.format("rtabmapInitEventsUI() status=%d msg=%s", status, msg));
((TextView)findViewById(R.id.status)).setText(
status == 1 && msg.isEmpty()?mItemPause!=null&&mItemPause.isChecked()?"Paused":mItemLocalizationMode!=null&&mItemLocalizationMode.isChecked()?"Localization":"Mapping":msg);
/*0=kInitializing,
1=kInitialized,
2=kClosing,
3=kClosed,
4=kInfo,
5=kError*/
if(status == 3)
{
msg = "";
if(!mNewDatabasePath.isEmpty())
{
boolean removed = true;
File outputFile = new File(mNewDatabasePath);
if(outputFile.exists())
{
removed = outputFile.delete();
}
if(removed)
{
File tempFile = new File(mTempDatabasePath);
if(tempFile.renameTo(outputFile))
{
msg = String.format("Database saved to \"%s\".", mNewDatabasePath);
Intent intent = new Intent(this, RTABMapActivity.class);
// use System.currentTimeMillis() to have a unique ID for the pending intent
PendingIntent pIntent = PendingIntent.getActivity(this, (int) System.currentTimeMillis(), intent, 0);
// build notification
// the addAction re-use the same intent to keep the example short
Notification n = new Notification.Builder(this)
.setContentTitle(getString(R.string.app_name))
.setContentText(mNewDatabasePath + " saved!")
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pIntent)
.setAutoCancel(true).build();
NotificationManager notificationManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(0, n);
}
else
{
msg = String.format("Failed to rename temporary database from \"%s\" to \"%s\".",
mTempDatabasePath, mNewDatabasePath);
}
}
else
{
msg = String.format("Failed to overwrite the database \"%s\". The temporary database is still correctly saved at \"%s\".",
mNewDatabasePath, mTempDatabasePath);
}
}
else if(!mOpenedDatabasePath.isEmpty())
{
msg = String.format("Database \"%s\" updated.", mOpenedDatabasePath);
}
if(!msg.isEmpty())
{
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}
mOpenedDatabasePath = "";
mNewDatabasePath = "";
//restart a new scan by default
RTABMapLib.openDatabase(mTempDatabasePath);
((TextView)findViewById(R.id.points)).setText(String.valueOf(0));
((TextView)findViewById(R.id.polygons)).setText(String.valueOf(0));
((TextView)findViewById(R.id.nodes)).setText(String.valueOf(0));
((TextView)findViewById(R.id.words)).setText(String.valueOf(0));
((TextView)findViewById(R.id.memory)).setText(String.valueOf(Debug.getNativeHeapAllocatedSize()/(1024*1024)));
((TextView)findViewById(R.id.db_size)).setText(String.valueOf(0));
((TextView)findViewById(R.id.inliers)).setText(String.valueOf(0));
((TextView)findViewById(R.id.features)).setText(String.valueOf(0));
((TextView)findViewById(R.id.update_time)).setText(String.valueOf(0));
((TextView)findViewById(R.id.hypothesis)).setText(String.valueOf(0));
mTotalLoopClosures = 0;
((TextView)findViewById(R.id.total_loop)).setText(String.valueOf(mTotalLoopClosures));
if(mItemSave!=null)
{
if(mItemPause.isChecked())
{
mItemPause.setChecked(false);
mItemOpen.setEnabled(false);
mItemPostProcessing.setEnabled(false);
mItemSave.setEnabled(false);
mItemExport.setEnabled(false);
RTABMapLib.setPausedMapping(false); // resume mapping
}
}
mProgressDialog.dismiss();
}
}
//called from jni
public void rtabmapInitEventCallback(
final int status,
final String msg)
{
Log.i(TAG, String.format("rtabmapInitEventCallback()"));
runOnUiThread(new Runnable() {
public void run() {
rtabmapInitEventUI(status, msg);
}
});
}
private void tangoEventUI(
int type,
String key,
String value)
{
/**
*
* "TangoServiceException:X" - The service has encountered an exception, and a text description is given in X.
* "FisheyeOverExposed:X" - the fisheye image is over exposed with average pixel value X px.
* "FisheyeUnderExposed:X" - the fisheye image is under exposed with average pixel value X px.
* "ColorOverExposed:X" - the color image is over exposed with average pixel value X px.
* "ColorUnderExposed:X" - the color image is under exposed with average pixel value X px.
* "TooFewFeaturesTracked:X" - too few features were tracked in the fisheye image. The number of features tracked is X.
* "AreaDescriptionSaveProgress:X" - ADF saving is X * 100 percent complete.
* "Unknown"
*/
if(key.equals("TangoServiceException"))
Toast.makeText(this, String.format("Tango service exception: %s", value), Toast.LENGTH_SHORT).show();
else if(key.equals("FisheyeOverExposed"))
;//Toast.makeText(this, String.format("The fisheye image is over exposed with average pixel value %s px.", value), Toast.LENGTH_SHORT).show();
else if(key.equals("FisheyeUnderExposed"))
;//Toast.makeText(this, String.format("The fisheye image is under exposed with average pixel value %s px.", value), Toast.LENGTH_SHORT).show();
else if(key.equals("ColorOverExposed"))
;//Toast.makeText(this, String.format("The color image is over exposed with average pixel value %s px.", value), Toast.LENGTH_SHORT).show();
else if(key.equals("ColorUnderExposed"))
;//Toast.makeText(this, String.format("The color image is under exposed with average pixel value %s px.", value), Toast.LENGTH_SHORT).show();
else if(key.equals("CameraTango"))
Toast.makeText(this, value, Toast.LENGTH_SHORT).show();
else if(key.equals("TooFewFeaturesTracked"))
{
if(!value.equals("0"))
Toast.makeText(this, String.format("Too few features (%s) were tracked in the fisheye image. This may result in poor odometry!", value), Toast.LENGTH_SHORT).show();
}
else
Toast.makeText(this, String.format("Unknown Tango event detected!? (type=%d)", type), Toast.LENGTH_SHORT).show();
}
//called from jni
public void tangoEventCallback(
final int type,
final String key,
final String value)
{
if(mItemPause != null && !mItemPause.isChecked())
{
runOnUiThread(new Runnable() {
public void run() {
tangoEventUI(type, key, value);
}
});
}
}
private boolean CheckTangoCoreVersion(int minVersion) {
int versionNumber = 0;
String packageName = TANGO_PACKAGE_NAME;
try {
PackageInfo pi = getApplicationContext().getPackageManager().getPackageInfo(packageName,
PackageManager.GET_META_DATA);
versionNumber = pi.versionCode;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return (minVersion <= versionNumber);
}
private RTABMapActivity getActivity() {return this;}
private String[] loadFileList(String directory) {
File path = new File(directory);
String fileList[];
try {
path.mkdirs();
}
catch(SecurityException e) {
Log.e(TAG, "unable to write on the sd card " + e.toString());
}
if(path.exists()) {
FilenameFilter filter = new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
File sel = new File(dir, filename);
return filename.endsWith(".db") || sel.isDirectory();
}
};
fileList = path.list(filter);
}
else {
fileList = new String[0];
}
return fileList;
}
public boolean onOptionsItemSelected(MenuItem item) {
Log.i(TAG, "called onOptionsItemSelected; selected item: " + item);
int itemId = item.getItemId();
if (itemId == R.id.pause)
{
item.setChecked(!item.isChecked());
mItemSave.setEnabled(item.isChecked());
mItemExport.setEnabled(item.isChecked());
mItemOpen.setEnabled(item.isChecked());
mItemPostProcessing.setEnabled(item.isChecked());
// mItemSave.setEnabled(item.isChecked() && !mWorkingDirectory.isEmpty());
if(item.isChecked())
{
RTABMapLib.setPausedMapping(true);
((TextView)findViewById(R.id.status)).setText("Paused");
}
else
{
RTABMapLib.setPausedMapping(false);
((TextView)findViewById(R.id.status)).setText(mItemLocalizationMode.isChecked()?"Localization":"Mapping");
}
}
else if (itemId == R.id.detect_more_loop_closures)
{
mProgressDialog.setTitle("Post-Processing");
mProgressDialog.setMessage(String.format("Please wait while detecting more loop closures..."));
mProgressDialog.show();
Thread workingThread = new Thread(new Runnable() {
public void run() {
final int loopDetected = RTABMapLib.postProcessing(2);
runOnUiThread(new Runnable() {
public void run() {
mProgressDialog.dismiss();
if(loopDetected >= 0)
{
Toast.makeText(getActivity(), String.format("Detection done! %d new loop closure(s) added.", loopDetected), Toast.LENGTH_SHORT).show();
}
else if(loopDetected < 0)
{
Toast.makeText(getActivity(), String.format("Detection failed!"), Toast.LENGTH_SHORT).show();
}
}
});
}
});
workingThread.start();
}
else if (itemId == R.id.global_graph_optimization)
{
mProgressDialog.setTitle("Post-Processing");
mProgressDialog.setMessage(String.format("Global graph optimization..."));
mProgressDialog.show();
Thread workingThread = new Thread(new Runnable() {
public void run() {
final int value = RTABMapLib.postProcessing(0);
runOnUiThread(new Runnable() {
public void run() {
mProgressDialog.dismiss();
if(value >= 0)
{
Toast.makeText(getActivity(), String.format("Optimization done!"), Toast.LENGTH_SHORT).show();
}
else if(value < 0)
{
Toast.makeText(getActivity(), String.format("Optimization failed!"), Toast.LENGTH_SHORT).show();
}
}
});
}
});
workingThread.start();
}
else if (itemId == R.id.sba)
{
mProgressDialog.setTitle("Post-Processing");
mProgressDialog.setMessage(String.format("Bundle adjustement..."));
mProgressDialog.show();
Thread workingThread = new Thread(new Runnable() {
public void run() {
final int value = RTABMapLib.postProcessing(1);
runOnUiThread(new Runnable() {
public void run() {
mProgressDialog.dismiss();
if(value >= 0)
{
Toast.makeText(getActivity(), String.format("Optimization done!"), Toast.LENGTH_SHORT).show();
}
else if(value < 0)
{
Toast.makeText(getActivity(), String.format("Optimization failed!"), Toast.LENGTH_SHORT).show();
}
}
});
}
});
workingThread.start();
}
else if(itemId == R.id.debug)
{
item.setChecked(!item.isChecked());
if(!item.isChecked())
{
mLayoutDebug.setVisibility(LinearLayout.GONE);
}
else
{
mLayoutDebug.setVisibility(LinearLayout.VISIBLE);
}
}
else if(itemId == R.id.mesh_rendering)
{
item.setChecked(!item.isChecked());
RTABMapLib.setMeshRendering(item.isChecked());
}
else if(itemId == R.id.map_shown)
{
item.setChecked(!item.isChecked());
RTABMapLib.setMapCloudShown(item.isChecked());
}
else if(itemId == R.id.odom_shown)
{
item.setChecked(!item.isChecked());
RTABMapLib.setOdomCloudShown(item.isChecked());
}
else if(itemId == R.id.localization_mode)
{
item.setChecked(!item.isChecked());
RTABMapLib.setLocalizationMode(item.isChecked());
}
else if(itemId == R.id.trajectory_mode)
{
item.setChecked(!item.isChecked());
RTABMapLib.setTrajectoryMode(item.isChecked());
}
else if(itemId == R.id.graph_optimization)
{
item.setChecked(!item.isChecked());
RTABMapLib.setGraphOptimization(item.isChecked());
}
else if(itemId == R.id.graph_visible)
{
item.setChecked(!item.isChecked());
RTABMapLib.setGraphVisible(item.isChecked());
}
else if(itemId == R.id.auto_exposure)
{
item.setChecked(!item.isChecked());
RTABMapLib.setAutoExposure(item.isChecked());
}
else if(itemId == R.id.resolution)
{
item.setChecked(!item.isChecked());
RTABMapLib.setFullResolution(item.isChecked());
}
else if(itemId == R.id.max_depth)
{
// get double
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Max Depth (m)");
final String[] values = {"1", "2", "3", "4", "5", "No Limit"};
builder.setSingleChoiceItems(values, mMaxDepthIndex, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
if(which >=0 && which < 6)
{
mMaxDepthIndex = which;
RTABMapLib.setMaxCloudDepth(which < 5?Float.parseFloat(values[which]):0);
}
}
});
builder.show();
}
else if(itemId == R.id.mesh_angle_tolerance)
{
// get double
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Mesh Angle Tolerance (deg)");
final String[] values = {"5", "10", "15", "20", "25", "30"};
builder.setSingleChoiceItems(values, mMeshAngleToleranceIndex, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
if(which >=0 && which < 6)
{
mMeshAngleToleranceIndex = which;
RTABMapLib.setMeshAngleTolerance(Float.parseFloat(values[which]));
}
}
});
builder.show();
}
else if(itemId == R.id.mesh_triangle_size)
{
// get double
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Mesh Triangle Size (pixels)");
final String[] values = {"2", "3", "4", "5", "6"};
builder.setSingleChoiceItems(values, mMeshTriangleSizeIndex, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
if(which >=0 && which < 5)
{
mMeshTriangleSizeIndex = which;
RTABMapLib.setMeshTriangleSize(Integer.parseInt(values[which]));
}
}
});
builder.show();
}
else if(itemId == R.id.update_rate)
{
// get double
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Update Rate (Hz)");
final String[] values = {"0.5", "1", "2", "Max"};
builder.setSingleChoiceItems(values, mParamUpdateRateHzIndex, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
if(which >=0 && which < 4)
{
mParamUpdateRateHzIndex = which;
if(RTABMapLib.setMappingParameter("Rtabmap/DetectionRate", values[which]) != 0)
{
Toast.makeText(getActivity(), "Failed to set parameter \"Rtabmap/DetectionRate\"!", Toast.LENGTH_LONG).show();
}
}
}
});
builder.show();
}
else if(itemId == R.id.time_threshold)
{
// get double
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Time Threshold (ms)");
final String[] values = {"400", "700", "1400", "No Limit"};
builder.setSingleChoiceItems(values, mParamTimeThrMsIndex, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
if(which >=0 && which < 4)
{
mParamTimeThrMsIndex = which;
if(RTABMapLib.setMappingParameter("Rtabmap/TimeThr", which==3?"0":values[which]) != 0)
{
Toast.makeText(getActivity(), "Failed to set parameter \"Rtabmap/TimeThr\"!", Toast.LENGTH_LONG).show();
}
}
}
});
builder.show();
}
else if(itemId == R.id.features)
{
// get double
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Max Features");
final String[] values = {"Disabled", "100", "200", "300", "400", "500", "600", "700", "800", "900", "1000", "No Limit"};
builder.setSingleChoiceItems(values, mParamMaxFeaturesIndex, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
if(which >=0 && which < 12)
{
mParamMaxFeaturesIndex = which;
if(RTABMapLib.setMappingParameter("Kp/MaxFeatures", which==0?"-1":which==11?"0":values[which]) != 0)
{
Toast.makeText(getActivity(),"Failed to set parameter \"Kp/MaxFeatures\"!", Toast.LENGTH_LONG).show();
}
}
}
});
builder.show();
}
else if(itemId == R.id.loop_threshold)
{
// get double
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Loop Closure Threshold");
final String[] values = {"Disabled", "0.11", "0.20", "0.30", "0.40", "0.50", "0.60", "0.70", "0.80", "0.90"};
builder.setSingleChoiceItems(values, mParamTimeThrMsIndex, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
if(which >=0 && which < 10)
{
mParamLoopThrMsIndex = which;
if(RTABMapLib.setMappingParameter("Rtabmap/LoopThr", which==0?"1":values[which]) != 0)
{
Toast.makeText(getActivity(), "Failed to set parameter \"Rtabmap/LoopThr\"!", Toast.LENGTH_LONG).show();
}
}
}
});
builder.show();
}
else if (itemId == R.id.save)
{
if(mOpenedDatabasePath.isEmpty())
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("RTAB-Map Database Name (*.db):");
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
builder.setView(input);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which)
{
final String fileName = input.getText().toString();
dialog.dismiss();
if(!fileName.isEmpty())
{
File newFile = new File(mWorkingDirectory + fileName + ".db");
if(newFile.exists())
{
new AlertDialog.Builder(getActivity())
.setTitle("File Already Exists")
.setMessage("Do you want to overwrite the existing file?")
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mNewDatabasePath = mWorkingDirectory + fileName + ".db";
mProgressDialog.setTitle("Saving");
mProgressDialog.setMessage(String.format("Please wait while saving \"%s\"...", mNewDatabasePath));
mProgressDialog.show();
RTABMapLib.save(); // send save event
//disable gui actions
mItemSave.setEnabled(false);
mItemOpen.setEnabled(false);
mItemPostProcessing.setEnabled(false);
mItemExport.setEnabled(false);
}
})
.setNegativeButton("No", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.show();
}
else
{
mNewDatabasePath = mWorkingDirectory + fileName + ".db";
mProgressDialog.setTitle("Saving");
mProgressDialog.setMessage(String.format("Please wait while saving \"%s\"...", mNewDatabasePath));
mProgressDialog.show();
RTABMapLib.save(); // send save event
//disable gui actions
mItemSave.setEnabled(false);
mItemOpen.setEnabled(false);
mItemPostProcessing.setEnabled(false);
mItemExport.setEnabled(false);
}
}
}
});
builder.show();
}
else
{
mProgressDialog.setTitle("Saving");
mProgressDialog.setMessage(String.format("Please wait while updating \"%s\"...", mOpenedDatabasePath));
mProgressDialog.show();
RTABMapLib.save(); // send save event
//disable gui actions
mItemSave.setEnabled(false);
mItemOpen.setEnabled(false);
mItemPostProcessing.setEnabled(false);
mItemExport.setEnabled(false);
}
}
else if(itemId == R.id.reset)
{
((TextView)findViewById(R.id.points)).setText(String.valueOf(0));
((TextView)findViewById(R.id.polygons)).setText(String.valueOf(0));
((TextView)findViewById(R.id.nodes)).setText(String.valueOf(0));
((TextView)findViewById(R.id.words)).setText(String.valueOf(0));
((TextView)findViewById(R.id.memory)).setText(String.valueOf(Debug.getNativeHeapAllocatedSize()/(1024*1024)));
((TextView)findViewById(R.id.db_size)).setText(String.valueOf(0));
((TextView)findViewById(R.id.inliers)).setText(String.valueOf(0));
((TextView)findViewById(R.id.features)).setText(String.valueOf(0));
((TextView)findViewById(R.id.update_time)).setText(String.valueOf(0));
((TextView)findViewById(R.id.hypothesis)).setText(String.valueOf(0));
mTotalLoopClosures = 0;
((TextView)findViewById(R.id.total_loop)).setText(String.valueOf(mTotalLoopClosures));
if(mOpenedDatabasePath.isEmpty())
{
RTABMapLib.resetMapping();
}
else
{
mOpenedDatabasePath = "";
RTABMapLib.openDatabase(mTempDatabasePath);
}
}
else if(itemId == R.id.export_obj || itemId == R.id.export_ply)
{
final String extension = itemId == R.id.export_ply ? ".ply" : ".obj";
final boolean isOBJ = itemId == R.id.export_obj;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(String.format("File Name (*%s):", extension));
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
builder.setView(input);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which)
{
final String fileName = input.getText().toString();
dialog.dismiss();
if(!fileName.isEmpty())
{
File newFile = new File(mWorkingDirectory + fileName + extension);
if(newFile.exists())
{
new AlertDialog.Builder(getActivity())
.setTitle("File Already Exists")
.setMessage("Do you want to overwrite the existing file?")
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
final String path = mWorkingDirectory + fileName + extension;
mItemExport.setEnabled(false);
mProgressDialog.setTitle("Exporting");
mProgressDialog.setMessage(String.format("Please wait while exporting \"%s\"...", fileName+extension));
mProgressDialog.show();
Thread exportThread = new Thread(new Runnable() {
public void run() {
final boolean success = RTABMapLib.exportMesh(path);
runOnUiThread(new Runnable() {
public void run() {
if(success)
{
if(isOBJ)
{
Toast.makeText(getActivity(), String.format("Mesh \"%s\" (with textures \"%s/\" and \"%s\") successfully exported!", path, fileName, fileName + ".mtl"), Toast.LENGTH_LONG).show();
}
else
{
Toast.makeText(getActivity(), String.format("Mesh \"%s\" successfully exported!", path), Toast.LENGTH_LONG).show();
}
}
else
{
Toast.makeText(getActivity(), String.format("Exporting mesh to \"%s\" failed!", path), Toast.LENGTH_LONG).show();
}
mItemExport.setEnabled(true);
mProgressDialog.dismiss();
}
});
}
});
exportThread.start();
}
})
.setNegativeButton("No", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.show();
}
else
{
final String path = mWorkingDirectory + fileName + extension;
mItemExport.setEnabled(false);
mProgressDialog.setTitle("Exporting");
mProgressDialog.setMessage(String.format("Please wait while exporting \"%s\"...", fileName+extension));
mProgressDialog.show();
Thread exportThread = new Thread(new Runnable() {
public void run() {
final boolean success = RTABMapLib.exportMesh(path);
runOnUiThread(new Runnable() {
public void run() {
if(success)
{
if(isOBJ)
{
Toast.makeText(getActivity(), String.format("Mesh \"%s\" (with textures \"%s/\" and \"%s\") successfully exported!", path, fileName, fileName + ".mtl"), Toast.LENGTH_LONG).show();
}
else
{
Toast.makeText(getActivity(), String.format("Mesh \"%s\" successfully exported!", path), Toast.LENGTH_LONG).show();
}
}
else
{
Toast.makeText(getActivity(), String.format("Exporting mesh to \"%s\" failed!", path), Toast.LENGTH_LONG).show();
}
mItemExport.setEnabled(true);
mProgressDialog.dismiss();
}
});
}
});
exportThread.start();
}
}
}
});
builder.show();
}
else if(itemId == R.id.open)
{
final String[] files = loadFileList(mWorkingDirectory);
if(files.length > 0)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Choose your file");
builder.setItems(files, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mOpenedDatabasePath = mWorkingDirectory + files[which];
if(!mItemTrajectoryMode.isChecked())
{
mProgressDialog.setTitle("Loading");
mProgressDialog.setMessage(String.format("Database \"%s\" loaded. Please wait while creating point clouds and meshes...", files[which]));
mProgressDialog.show();
}
RTABMapLib.openDatabase(mOpenedDatabasePath);
RTABMapLib.setCamera(1);
File extStore = new File(mTempDatabasePath);
if(extStore.exists())
{
extStore.delete();
}
}
});
builder.show();
}
}
else if(itemId == R.id.about)
{
AboutDialog about = new AboutDialog(this);
about.setTitle("About RTAB-Map");
about.show();
}
return true;
}
}