mirror of
https://github.com/introlab/rtabmap.git
synced 2026-10-04 17:17:47 +08:00
Tango: 0.12.0 (performance optimization, time and memory)
This commit is contained in:
@@ -36,6 +36,10 @@ import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Typeface;
|
||||
import android.hardware.Camera;
|
||||
import android.graphics.Point;
|
||||
import android.hardware.display.DisplayManager;
|
||||
@@ -52,6 +56,7 @@ import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextPaint;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.Menu;
|
||||
@@ -93,6 +98,7 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
public static final String RTABMAP_TMP_DB = "rtabmap.tmp.db";
|
||||
public static final String RTABMAP_TMP_DIR = "tmp/";
|
||||
public static final String RTABMAP_TMP_FILENAME = "map";
|
||||
public static final String RTABMAP_SDCARD_PATH = "/sdcard/";
|
||||
|
||||
public static final String RTABMAP_AUTH_TOKEN_KEY = "com.introlab.rtabmap.AUTH_TOKEN";
|
||||
public static final String RTABMAP_FILENAME_KEY = "com.introlab.rtabmap.FILENAME";
|
||||
@@ -136,6 +142,8 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
private MenuItem mItemRenderingMesh;
|
||||
private MenuItem mItemRenderingTextureMesh;
|
||||
private MenuItem mItemDataRecorderMode;
|
||||
private MenuItem mItemStatusVisibility;
|
||||
private MenuItem mItemDebugVisibility;
|
||||
|
||||
private ToggleButton mButtonFirst;
|
||||
private ToggleButton mButtonThird;
|
||||
@@ -149,6 +157,7 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
|
||||
private String mOpenedDatabasePath = "";
|
||||
private String mWorkingDirectory = "";
|
||||
private String mWorkingDirectoryHuman = "";
|
||||
|
||||
private String mUpdateRate;
|
||||
private String mTimeThr;
|
||||
@@ -157,15 +166,16 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
private String mMinInliers;
|
||||
private String mMaxOptimizationError;
|
||||
|
||||
private LinearLayout mLayoutDebug;
|
||||
|
||||
private int mTotalLoopClosures = 0;
|
||||
private boolean mMapIsEmpty = false;
|
||||
private boolean mExportedOBJ = false;
|
||||
|
||||
private Toast mToast = null;
|
||||
|
||||
private AlertDialog mMemoryWarningDialog = null;
|
||||
|
||||
private Thread mMemStatusUpdateThread = null;
|
||||
private String[] mStatusTexts = new String[16];
|
||||
|
||||
//Tango Service connection.
|
||||
ServiceConnection mTangoServiceConnection = new ServiceConnection() {
|
||||
@@ -254,12 +264,10 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
mRenderer = new Renderer(this);
|
||||
mGLView.setRenderer(mRenderer);
|
||||
|
||||
mLayoutDebug = (LinearLayout) findViewById(R.id.debug_layout);
|
||||
mLayoutDebug.setVisibility(LinearLayout.GONE);
|
||||
|
||||
mProgressDialog = new ProgressDialog(this);
|
||||
mProgressDialog.setCanceledOnTouchOutside(false);
|
||||
mRenderer.setProgressDialog(mProgressDialog);
|
||||
mRenderer.setToast(mToast);
|
||||
|
||||
// Check if the Tango Core is out dated.
|
||||
if (!CheckTangoCoreVersion(MIN_TANGO_CORE_VERSION)) {
|
||||
@@ -270,6 +278,7 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
|
||||
mOpenedDatabasePath = "";
|
||||
mWorkingDirectory = "";
|
||||
mWorkingDirectoryHuman = "";
|
||||
mTotalLoopClosures = 0;
|
||||
|
||||
if(Environment.getExternalStorageState().compareTo(Environment.MEDIA_MOUNTED)==0)
|
||||
@@ -278,6 +287,7 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
mWorkingDirectory = extStore.getAbsolutePath() + "/" + getString(R.string.app_name) + "/";
|
||||
extStore = new File(mWorkingDirectory);
|
||||
extStore.mkdirs();
|
||||
mWorkingDirectoryHuman = RTABMAP_SDCARD_PATH + getString(R.string.app_name) + "/";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -407,6 +417,7 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
RTABMapLib.setAutoExposure(sharedPref.getBoolean(getString(R.string.pref_key_auto_exposure), Boolean.parseBoolean(getString(R.string.pref_default_auto_exposure))));
|
||||
RTABMapLib.setRawScanSaved(sharedPref.getBoolean(getString(R.string.pref_key_raw_scan_saved), Boolean.parseBoolean(getString(R.string.pref_default_raw_scan_saved))));
|
||||
RTABMapLib.setFullResolution(sharedPref.getBoolean(getString(R.string.pref_key_resolution), Boolean.parseBoolean(getString(R.string.pref_default_resolution))));
|
||||
RTABMapLib.setSmoothing(sharedPref.getBoolean(getString(R.string.pref_key_smoothing), Boolean.parseBoolean(getString(R.string.pref_default_smoothing))));
|
||||
RTABMapLib.setAppendMode(sharedPref.getBoolean(getString(R.string.pref_key_append), Boolean.parseBoolean(getString(R.string.pref_default_append))));
|
||||
RTABMapLib.setMappingParameter("Rtabmap/DetectionRate", mUpdateRate);
|
||||
RTABMapLib.setMappingParameter("Rtabmap/TimeThr", mTimeThr);
|
||||
@@ -480,6 +491,12 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
|
||||
private void setCamera(int type)
|
||||
{
|
||||
// for convenience, for a refresh of the memory used
|
||||
stopUpdateStatusThread();
|
||||
mStatusTexts[1] = getString(R.string.memory)+String.valueOf(Debug.getNativeHeapAllocatedSize()/(1024*1024));
|
||||
mStatusTexts[2] = getString(R.string.free_memory)+String.valueOf(getFreeMemory());
|
||||
updateStatusTexts();
|
||||
|
||||
RTABMapLib.setCamera(type);
|
||||
mButtonFirst.setChecked(type==0);
|
||||
mButtonThird.setChecked(type==1);
|
||||
@@ -584,6 +601,8 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
mItemRenderingMesh = menu.findItem(R.id.mesh);
|
||||
mItemRenderingTextureMesh = menu.findItem(R.id.texture_mesh);
|
||||
mItemDataRecorderMode = menu.findItem(R.id.data_recorder);
|
||||
mItemStatusVisibility = menu.findItem(R.id.status);
|
||||
mItemDebugVisibility = menu.findItem(R.id.debug);
|
||||
mItemSave.setEnabled(false);
|
||||
mItemExport.setEnabled(false);
|
||||
mItemOpen.setEnabled(false);
|
||||
@@ -633,58 +652,107 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
activityManager.getMemoryInfo(mi);
|
||||
return mi.availMem / 0x100000L; // MB
|
||||
}
|
||||
|
||||
private void updateStatsUI(
|
||||
int nodes,
|
||||
int words,
|
||||
int points,
|
||||
int polygons,
|
||||
float updateTime,
|
||||
int loopClosureId,
|
||||
int highestHypId,
|
||||
int databaseMemoryUsed,
|
||||
int inliers,
|
||||
int matches,
|
||||
int featuresExtracted,
|
||||
float hypothesis,
|
||||
int nodesDrawn,
|
||||
float fps,
|
||||
int rejected,
|
||||
float rehearsalValue,
|
||||
float optimizationMaxError)
|
||||
|
||||
private void updateStatusTexts()
|
||||
{
|
||||
if(mButtonPause!=null)
|
||||
if(mItemStatusVisibility != null && mItemDebugVisibility != null)
|
||||
{
|
||||
if(mButtonPause.isChecked())
|
||||
if(mItemStatusVisibility.isChecked() && mItemDebugVisibility.isChecked())
|
||||
{
|
||||
((TextView)findViewById(R.id.status)).setText("Paused");
|
||||
mRenderer.updateTexts(mStatusTexts);
|
||||
}
|
||||
else if(mItemStatusVisibility.isChecked())
|
||||
{
|
||||
mRenderer.updateTexts(Arrays.copyOfRange(mStatusTexts, 0, 3));
|
||||
}
|
||||
else if(mItemDebugVisibility.isChecked())
|
||||
{
|
||||
mRenderer.updateTexts(Arrays.copyOfRange(mStatusTexts, 4, mStatusTexts.length));
|
||||
}
|
||||
else
|
||||
{
|
||||
String updateValue = mUpdateRate.compareTo("0")==0?"Max":mUpdateRate;
|
||||
((TextView)findViewById(R.id.status)).setText(mItemLocalizationMode.isChecked()?String.format("Localization (%s Hz)", updateValue):mItemDataRecorderMode.isChecked()?String.format("Recording (%s Hz)", updateValue):String.format("Mapping (%s Hz)", updateValue));
|
||||
mRenderer.updateTexts(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStatsUI(
|
||||
int processMemoryUsed,
|
||||
int loopClosureId,
|
||||
int inliers,
|
||||
int matches,
|
||||
int rejected,
|
||||
float optimizationMaxError,
|
||||
String[] statusTexts)
|
||||
{
|
||||
mStatusTexts = statusTexts;
|
||||
updateStatusTexts();
|
||||
|
||||
if(mButtonPause!=null)
|
||||
{
|
||||
if(!mButtonPause.isChecked())
|
||||
{
|
||||
//check if we are low in memory
|
||||
long memoryUsed = processMemoryUsed;
|
||||
long memoryFree = getFreeMemory();
|
||||
|
||||
if(memoryFree < 100)
|
||||
{
|
||||
mButtonPause.setChecked(true);
|
||||
pauseMapping();
|
||||
|
||||
if(mMemoryWarningDialog!=null)
|
||||
{
|
||||
mMemoryWarningDialog.dismiss();
|
||||
mMemoryWarningDialog = null;
|
||||
}
|
||||
|
||||
mMemoryWarningDialog = new AlertDialog.Builder(getActivity())
|
||||
.setTitle("Memory is full!")
|
||||
.setCancelable(false)
|
||||
.setMessage(String.format("Scanning has been paused because free memory is too "
|
||||
+ "low (%d MB). You should be able to save the database but some post-processing and exporting options may fail. "
|
||||
+ "\n\nNote that for large environments, you can save multiple databases and "
|
||||
+ "merge them with RTAB-Map Desktop version.", memoryUsed))
|
||||
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mMemoryWarningDialog = null;
|
||||
}
|
||||
})
|
||||
.create();
|
||||
mMemoryWarningDialog.show();
|
||||
}
|
||||
else if(mMemoryWarningDialog == null && memoryUsed*3 > memoryFree && (mItemDataRecorderMode == null || !mItemDataRecorderMode.isChecked()))
|
||||
{
|
||||
mMemoryWarningDialog = new AlertDialog.Builder(getActivity())
|
||||
.setTitle("Warning: Memory is almost full!")
|
||||
.setCancelable(false)
|
||||
.setMessage(String.format("Free memory (%d MB) should be at least 3 times the "
|
||||
+ "memory used (%d MB) so that some post-processing and exporting options "
|
||||
+ "have enough memory to work correctly. If you just want to save the database "
|
||||
+ "after scanning, you can continue until the next warning.\n\n"
|
||||
+ "Note that showing only point clouds reduces memory needed for rendering.", memoryFree, memoryUsed))
|
||||
.setPositiveButton("Pause", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mButtonPause.setChecked(true);
|
||||
pauseMapping();
|
||||
}
|
||||
})
|
||||
.setNeutralButton("Continue", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
})
|
||||
.create();
|
||||
mMemoryWarningDialog.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
((TextView)findViewById(R.id.memory)).setText(String.valueOf(Debug.getNativeHeapAllocatedSize()/(1024*1024)));
|
||||
((TextView)findViewById(R.id.free_memory)).setText(String.valueOf(getFreeMemory()));
|
||||
((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.database_size)).setText(String.valueOf(databaseMemoryUsed));
|
||||
((TextView)findViewById(R.id.inliers)).setText(String.valueOf(inliers));
|
||||
((TextView)findViewById(R.id.features)).setText(String.format("%d / %s", featuresExtracted, mMaxFeatures.compareTo("0")==0?"No Limit":mMaxFeatures.compareTo("-1")==0?"Disabled":mMaxFeatures));
|
||||
((TextView)findViewById(R.id.rehearsal)).setText(String.format("%.3f", rehearsalValue));
|
||||
((TextView)findViewById(R.id.update_time)).setText(String.format("%.3f / %s", updateTime, mTimeThr.compareTo("0")==0?"No Limit":mTimeThr));
|
||||
((TextView)findViewById(R.id.hypothesis)).setText(String.format("%.3f / %s (%d)", hypothesis, mLoopThr, loopClosureId>0?loopClosureId:highestHypId));
|
||||
((TextView)findViewById(R.id.fps)).setText(String.format("%.3f Hz", fps));
|
||||
|
||||
if(mButtonPause!=null && !mButtonPause.isChecked())
|
||||
{
|
||||
if(loopClosureId > 0)
|
||||
{
|
||||
++mTotalLoopClosures;
|
||||
|
||||
mToast.setText(String.format("Loop closure detected! (%d/%d inliers)", inliers, matches));
|
||||
mToast.show();
|
||||
}
|
||||
@@ -701,7 +769,6 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
mToast.show();
|
||||
}
|
||||
}
|
||||
((TextView)findViewById(R.id.total_loop)).setText(String.valueOf(mTotalLoopClosures));
|
||||
}
|
||||
|
||||
// called from jni
|
||||
@@ -713,10 +780,11 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
final float updateTime,
|
||||
final int loopClosureId,
|
||||
final int highestHypId,
|
||||
final int processMemoryUsed,
|
||||
final int databaseMemoryUsed,
|
||||
final int inliers,
|
||||
final int matches,
|
||||
final int features,
|
||||
final int featuresExtracted,
|
||||
final float hypothesis,
|
||||
final int nodesDrawn,
|
||||
final float fps,
|
||||
@@ -726,10 +794,57 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
{
|
||||
Log.i(TAG, String.format("updateStatsCallback()"));
|
||||
|
||||
final String[] statusTexts = new String[16];
|
||||
if(mButtonPause!=null)
|
||||
{
|
||||
if(mButtonPause.isChecked())
|
||||
{
|
||||
statusTexts[0] = getString(R.string.status)+"Paused";
|
||||
}
|
||||
else
|
||||
{
|
||||
String updateValue = mUpdateRate.compareTo("0")==0?"Max":mUpdateRate;
|
||||
statusTexts[0] = getString(R.string.status)+(mItemLocalizationMode.isChecked()?String.format("Localization (%s Hz)", updateValue):mItemDataRecorderMode.isChecked()?String.format("Recording (%s Hz)", updateValue):String.format("Mapping (%s Hz)", updateValue));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
statusTexts[0] = getString(R.string.status);
|
||||
}
|
||||
|
||||
stopUpdateStatusThread();
|
||||
|
||||
// getNativeHeapAllocatedSize() is too slow, so we need to use the estimate.
|
||||
// Multiply by 3/2 to match getNativeHeapAllocatedSize()
|
||||
final int adjustedMemoryUsed = (processMemoryUsed*3)/2;
|
||||
|
||||
statusTexts[1] = getString(R.string.memory)+adjustedMemoryUsed;
|
||||
statusTexts[2] = getString(R.string.free_memory)+getFreeMemory();
|
||||
|
||||
|
||||
if(loopClosureId > 0)
|
||||
{
|
||||
++mTotalLoopClosures;
|
||||
}
|
||||
|
||||
int index = 4;
|
||||
statusTexts[index++] = getString(R.string.nodes)+nodes+" (" + nodesDrawn + " shown)";
|
||||
statusTexts[index++] = getString(R.string.words)+words;
|
||||
statusTexts[index++] = getString(R.string.database_size)+databaseMemoryUsed;
|
||||
statusTexts[index++] = getString(R.string.points)+points;
|
||||
statusTexts[index++] = getString(R.string.polygons)+polygons;
|
||||
statusTexts[index++] = getString(R.string.update_time)+(int)(updateTime) + " / " + (mTimeThr.compareTo("0")==0?"No Limit":mTimeThr);
|
||||
statusTexts[index++] = getString(R.string.features)+featuresExtracted +" / " + (mMaxFeatures.compareTo("0")==0?"No Limit":mMaxFeatures.compareTo("-1")==0?"Disabled":mMaxFeatures);
|
||||
statusTexts[index++] = getString(R.string.rehearsal)+(int)(rehearsalValue*100.0f);
|
||||
statusTexts[index++] = getString(R.string.total_loop)+mTotalLoopClosures;
|
||||
statusTexts[index++] = getString(R.string.inliers)+inliers;
|
||||
statusTexts[index++] = getString(R.string.hypothesis)+(int)(hypothesis*100.0f) +" / " + (int)(Float.parseFloat(mLoopThr)*100.0f) + " (" + (loopClosureId>0?loopClosureId:highestHypId)+")";
|
||||
statusTexts[index++] = getString(R.string.fps)+(int)fps+" Hz";
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
updateStatsUI(nodes, words, points, polygons, updateTime, loopClosureId, highestHypId, databaseMemoryUsed, inliers, matches, features, hypothesis, nodesDrawn, fps, rejected, rehearsalValue, optimizationMaxError);
|
||||
}
|
||||
public void run() {
|
||||
updateStatsUI(adjustedMemoryUsed, loopClosureId, inliers, matches, rejected, optimizationMaxError, statusTexts);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -743,14 +858,16 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
{
|
||||
if(mButtonPause.isChecked())
|
||||
{
|
||||
((TextView)findViewById(R.id.status)).setText(
|
||||
status == 1 && msg.isEmpty()?"Paused":msg);
|
||||
mStatusTexts[0] = getString(R.string.status)+(status == 1 && msg.isEmpty()?"Paused":msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
((TextView)findViewById(R.id.status)).setText(
|
||||
status == 1 && msg.isEmpty()?(mItemLocalizationMode!=null&&mItemLocalizationMode.isChecked()?"Localization":mItemDataRecorderMode!=null&&mItemDataRecorderMode.isChecked()?"Recording":"Mapping"):msg);
|
||||
mStatusTexts[0] = getString(R.string.status)+(status == 1 && msg.isEmpty()?(mItemLocalizationMode!=null&&mItemLocalizationMode.isChecked()?"Localization":mItemDataRecorderMode!=null&&mItemDataRecorderMode.isChecked()?"Recording":"Mapping"):msg);
|
||||
}
|
||||
|
||||
mStatusTexts[1] = getString(R.string.memory)+String.valueOf(Debug.getNativeHeapAllocatedSize()/(1024*1024));
|
||||
mStatusTexts[2] = getString(R.string.free_memory)+String.valueOf(getFreeMemory());
|
||||
updateStatusTexts();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -901,6 +1018,47 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
});
|
||||
workingThread.start();
|
||||
}
|
||||
|
||||
public void startUpdateStatusThread()
|
||||
{
|
||||
if(mMemStatusUpdateThread == null)
|
||||
{
|
||||
mMemStatusUpdateThread = new Thread() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (!isInterrupted()) {
|
||||
Thread.sleep(1000);
|
||||
if(mState != RTABMapActivity.State.STATE_VISUALIZING)
|
||||
{
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mStatusTexts[1] = getString(R.string.memory)+String.valueOf(Debug.getNativeHeapAllocatedSize()/(1024*1024));
|
||||
mStatusTexts[2] = getString(R.string.free_memory)+String.valueOf(getFreeMemory());
|
||||
updateStatusTexts();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
};
|
||||
mMemStatusUpdateThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
public void stopUpdateStatusThread()
|
||||
{
|
||||
if(mMemStatusUpdateThread != null)
|
||||
{
|
||||
Thread tmp = mMemStatusUpdateThread;
|
||||
mMemStatusUpdateThread = null;
|
||||
tmp.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateState(State state)
|
||||
{
|
||||
@@ -920,32 +1078,7 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
mItemReset.setEnabled(false);
|
||||
mItemModes.setEnabled(false);
|
||||
mButtonPause.setVisibility(View.INVISIBLE);
|
||||
if(mMemStatusUpdateThread == null)
|
||||
{
|
||||
mMemStatusUpdateThread = new Thread() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (!isInterrupted()) {
|
||||
Thread.sleep(1000);
|
||||
if(mState == RTABMapActivity.State.STATE_PROCESSING)
|
||||
{
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
((TextView)findViewById(R.id.memory)).setText(String.valueOf(Debug.getNativeHeapAllocatedSize()/(1024*1024)));
|
||||
((TextView)findViewById(R.id.free_memory)).setText(String.valueOf(getFreeMemory()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
};
|
||||
mMemStatusUpdateThread.start();
|
||||
}
|
||||
startUpdateStatusThread();
|
||||
break;
|
||||
case STATE_VISUALIZING:
|
||||
mButtonLighting.setVisibility(View.VISIBLE);
|
||||
@@ -961,12 +1094,7 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
mItemModes.setEnabled(true);
|
||||
mButtonPause.setVisibility(View.INVISIBLE);
|
||||
mItemDataRecorderMode.setEnabled(mButtonPause.isChecked());
|
||||
if(mMemStatusUpdateThread != null)
|
||||
{
|
||||
Thread tmp = mMemStatusUpdateThread;
|
||||
mMemStatusUpdateThread = null;
|
||||
tmp.interrupt();
|
||||
}
|
||||
stopUpdateStatusThread();
|
||||
break;
|
||||
default:
|
||||
mButtonLighting.setVisibility(View.INVISIBLE);
|
||||
@@ -983,12 +1111,7 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
mButtonPause.setVisibility(View.VISIBLE);
|
||||
mItemDataRecorderMode.setEnabled(mButtonPause.isChecked());
|
||||
RTABMapLib.postExportation(false);
|
||||
if(mMemStatusUpdateThread != null)
|
||||
{
|
||||
Thread tmp = mMemStatusUpdateThread;
|
||||
mMemStatusUpdateThread = null;
|
||||
tmp.interrupt();
|
||||
}
|
||||
stopUpdateStatusThread();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1000,11 +1123,17 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
if(mButtonPause.isChecked())
|
||||
{
|
||||
RTABMapLib.setPausedMapping(true);
|
||||
((TextView)findViewById(R.id.status)).setText("Paused");
|
||||
|
||||
mStatusTexts[0] = getString(R.string.status)+"Paused";
|
||||
mStatusTexts[1] = getString(R.string.memory)+String.valueOf(Debug.getNativeHeapAllocatedSize()/(1024*1024));
|
||||
mStatusTexts[2] = getString(R.string.free_memory)+String.valueOf(getFreeMemory());
|
||||
updateStatusTexts();
|
||||
|
||||
mMapIsEmpty = false;
|
||||
mDateOnPause = new Date();
|
||||
|
||||
if(!mOnPause && !mItemLocalizationMode.isChecked() && !mItemDataRecorderMode.isChecked())
|
||||
long memoryFree = getFreeMemory();
|
||||
if(!mOnPause && !mItemLocalizationMode.isChecked() && !mItemDataRecorderMode.isChecked() && memoryFree >= 100)
|
||||
{
|
||||
// Do standard post processing?
|
||||
new AlertDialog.Builder(getActivity())
|
||||
@@ -1025,8 +1154,13 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
}
|
||||
else
|
||||
{
|
||||
if(mMemoryWarningDialog != null)
|
||||
{
|
||||
mMemoryWarningDialog.dismiss();
|
||||
mMemoryWarningDialog=null;
|
||||
}
|
||||
RTABMapLib.setPausedMapping(false);
|
||||
((TextView)findViewById(R.id.status)).setText(mItemLocalizationMode.isChecked()?"Localization":mItemDataRecorderMode.isChecked()?"Recording":"Mapping");
|
||||
|
||||
if(mItemDataRecorderMode.isChecked())
|
||||
{
|
||||
mToast.makeText(getActivity(), String.format("Data Recorder Mode: no map is created, only raw data is recorded."), mToast.LENGTH_LONG).show();
|
||||
@@ -1154,17 +1288,15 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
});
|
||||
workingThread.start();
|
||||
}
|
||||
else if(itemId == R.id.status)
|
||||
{
|
||||
item.setChecked(!item.isChecked());
|
||||
updateStatusTexts();
|
||||
}
|
||||
else if(itemId == R.id.debug)
|
||||
{
|
||||
item.setChecked(!item.isChecked());
|
||||
if(!item.isChecked())
|
||||
{
|
||||
mLayoutDebug.setVisibility(LinearLayout.GONE);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLayoutDebug.setVisibility(LinearLayout.VISIBLE);
|
||||
}
|
||||
updateStatusTexts();
|
||||
}
|
||||
else if(itemId == R.id.mesh || itemId == R.id.texture_mesh || itemId == R.id.point_cloud)
|
||||
{
|
||||
@@ -1256,62 +1388,7 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
.setMessage("Do you want to overwrite the existing file?")
|
||||
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
final String newDatabasePath = mWorkingDirectory + fileName + ".db";
|
||||
mProgressDialog.setTitle("Saving");
|
||||
if(mOpenedDatabasePath.equals(newDatabasePath))
|
||||
{
|
||||
mProgressDialog.setMessage(String.format("Please wait while updating \"%s\"...", newDatabasePath));
|
||||
}
|
||||
else
|
||||
{
|
||||
mProgressDialog.setMessage(String.format("Please wait while saving \"%s\"...", newDatabasePath));
|
||||
}
|
||||
mProgressDialog.show();
|
||||
updateState(State.STATE_PROCESSING);
|
||||
Thread saveThread = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
RTABMapLib.save(newDatabasePath); // save
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if(mOpenedDatabasePath.equals(newDatabasePath))
|
||||
{
|
||||
mToast.makeText(getActivity(), String.format("Database \"%s\" updated.", newDatabasePath), mToast.LENGTH_LONG).show();
|
||||
}
|
||||
else
|
||||
{
|
||||
mToast.makeText(getActivity(), String.format("Database saved to \"%s\".", newDatabasePath), mToast.LENGTH_LONG).show();
|
||||
|
||||
Intent intent = new Intent(getActivity(), RTABMapActivity.class);
|
||||
// use System.currentTimeMillis() to have a unique ID for the pending intent
|
||||
PendingIntent pIntent = PendingIntent.getActivity(getActivity(), (int) System.currentTimeMillis(), intent, 0);
|
||||
|
||||
// build notification
|
||||
// the addAction re-use the same intent to keep the example short
|
||||
Notification n = new Notification.Builder(getActivity())
|
||||
.setContentTitle(getString(R.string.app_name))
|
||||
.setContentText(newDatabasePath + " saved!")
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setContentIntent(pIntent)
|
||||
.setAutoCancel(true).build();
|
||||
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
|
||||
notificationManager.notify(0, n);
|
||||
}
|
||||
if(!mItemDataRecorderMode.isChecked())
|
||||
{
|
||||
mOpenedDatabasePath = newDatabasePath;
|
||||
}
|
||||
mProgressDialog.dismiss();
|
||||
updateState(State.STATE_IDLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
saveThread.start();
|
||||
saveDatabase(fileName);
|
||||
}
|
||||
})
|
||||
.setNegativeButton("No", new DialogInterface.OnClickListener() {
|
||||
@@ -1323,61 +1400,7 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
}
|
||||
else
|
||||
{
|
||||
final String newDatabasePath = mWorkingDirectory + fileName + ".db";
|
||||
mProgressDialog.setTitle("Saving");
|
||||
if(mOpenedDatabasePath.equals(newDatabasePath))
|
||||
{
|
||||
mProgressDialog.setMessage(String.format("Please wait while updating \"%s\"...", mOpenedDatabasePath));
|
||||
}
|
||||
else
|
||||
{
|
||||
mProgressDialog.setMessage(String.format("Please wait while saving \"%s\"...", newDatabasePath));
|
||||
}
|
||||
mProgressDialog.show();
|
||||
updateState(State.STATE_PROCESSING);
|
||||
Thread saveThread = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
RTABMapLib.save(newDatabasePath); // save
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if(mOpenedDatabasePath.equals(newDatabasePath))
|
||||
{
|
||||
mToast.makeText(getActivity(), String.format("Database \"%s\" updated.", newDatabasePath), mToast.LENGTH_LONG).show();
|
||||
}
|
||||
else
|
||||
{
|
||||
mToast.makeText(getActivity(), String.format("Database saved to \"%s\".", newDatabasePath), mToast.LENGTH_LONG).show();
|
||||
|
||||
Intent intent = new Intent(getActivity(), RTABMapActivity.class);
|
||||
// use System.currentTimeMillis() to have a unique ID for the pending intent
|
||||
PendingIntent pIntent = PendingIntent.getActivity(getActivity(), (int) System.currentTimeMillis(), intent, 0);
|
||||
|
||||
// build notification
|
||||
// the addAction re-use the same intent to keep the example short
|
||||
Notification n = new Notification.Builder(getActivity())
|
||||
.setContentTitle(getString(R.string.app_name))
|
||||
.setContentText(newDatabasePath + " saved!")
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setContentIntent(pIntent)
|
||||
.setAutoCancel(true).build();
|
||||
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
|
||||
notificationManager.notify(0, n);
|
||||
}
|
||||
if(!mItemDataRecorderMode.isChecked())
|
||||
{
|
||||
mOpenedDatabasePath = newDatabasePath;
|
||||
}
|
||||
mProgressDialog.dismiss();
|
||||
updateState(State.STATE_IDLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
saveThread.start();
|
||||
saveDatabase(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1388,17 +1411,22 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
}
|
||||
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.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));
|
||||
((TextView)findViewById(R.id.fps)).setText(String.valueOf(0));
|
||||
mTotalLoopClosures = 0;
|
||||
((TextView)findViewById(R.id.total_loop)).setText(String.valueOf(mTotalLoopClosures));
|
||||
|
||||
int index = 4;
|
||||
mStatusTexts[index++] = getString(R.string.nodes)+0;
|
||||
mStatusTexts[index++] = getString(R.string.words)+0;
|
||||
mStatusTexts[index++] = getString(R.string.database_size)+0;
|
||||
mStatusTexts[index++] = getString(R.string.points)+0;
|
||||
mStatusTexts[index++] = getString(R.string.polygons)+0;
|
||||
mStatusTexts[index++] = getString(R.string.update_time)+0;
|
||||
mStatusTexts[index++] = getString(R.string.features)+0;
|
||||
mStatusTexts[index++] = getString(R.string.rehearsal)+0;
|
||||
mStatusTexts[index++] = getString(R.string.total_loop)+0;
|
||||
mStatusTexts[index++] = getString(R.string.inliers)+0;
|
||||
mStatusTexts[index++] = getString(R.string.hypothesis)+0;
|
||||
mStatusTexts[index++] = getString(R.string.fps)+0;
|
||||
updateStatusTexts();
|
||||
|
||||
mOpenedDatabasePath = "";
|
||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
@@ -1422,17 +1450,21 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// 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.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));
|
||||
((TextView)findViewById(R.id.fps)).setText(String.valueOf(0));
|
||||
mTotalLoopClosures = 0;
|
||||
((TextView)findViewById(R.id.total_loop)).setText(String.valueOf(mTotalLoopClosures));
|
||||
int index = 4;
|
||||
mStatusTexts[index++] = getString(R.string.nodes)+0;
|
||||
mStatusTexts[index++] = getString(R.string.words)+0;
|
||||
mStatusTexts[index++] = getString(R.string.database_size)+0;
|
||||
mStatusTexts[index++] = getString(R.string.points)+0;
|
||||
mStatusTexts[index++] = getString(R.string.polygons)+0;
|
||||
mStatusTexts[index++] = getString(R.string.update_time)+0;
|
||||
mStatusTexts[index++] = getString(R.string.features)+0;
|
||||
mStatusTexts[index++] = getString(R.string.rehearsal)+0;
|
||||
mStatusTexts[index++] = getString(R.string.total_loop)+0;
|
||||
mStatusTexts[index++] = getString(R.string.inliers)+0;
|
||||
mStatusTexts[index++] = getString(R.string.hypothesis)+0;
|
||||
mStatusTexts[index++] = getString(R.string.fps)+0;
|
||||
updateStatusTexts();
|
||||
|
||||
mItemDataRecorderMode.setChecked(!dataRecorderOldState);
|
||||
RTABMapLib.setDataRecorderMode(mItemDataRecorderMode.isChecked());
|
||||
@@ -1467,7 +1499,8 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
})
|
||||
.show();
|
||||
}
|
||||
else if(itemId == R.id.export_point_cloud ||
|
||||
else if(itemId == R.id.export_point_cloud ||
|
||||
itemId == R.id.export_point_cloud_highrez ||
|
||||
itemId == R.id.export_mesh ||
|
||||
itemId == R.id.export_mesh_texture ||
|
||||
itemId == R.id.export_optimized_mesh ||
|
||||
@@ -1476,14 +1509,14 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
final boolean isOBJ = itemId == R.id.export_mesh_texture || itemId == R.id.export_optimized_mesh_texture;
|
||||
final String extension = isOBJ? ".obj" : ".ply";
|
||||
|
||||
final int polygons = Integer.parseInt(((TextView)findViewById(R.id.polygons)).getText().toString());
|
||||
|
||||
final boolean meshing = itemId != R.id.export_point_cloud;
|
||||
final boolean meshing = itemId != R.id.export_point_cloud && itemId != R.id.export_point_cloud_highrez;
|
||||
final boolean regenerateCloud = itemId == R.id.export_point_cloud_highrez;
|
||||
final boolean optimized = itemId == R.id.export_optimized_mesh || itemId == R.id.export_optimized_mesh_texture;
|
||||
|
||||
// get Export settings
|
||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
final float cloudVoxelSize = Float.parseFloat(sharedPref.getString(getString(R.string.pref_key_cloud_voxel), getString(R.string.pref_default_cloud_voxel)));
|
||||
final String cloudVoxelSizeStr = sharedPref.getString(getString(R.string.pref_key_cloud_voxel), getString(R.string.pref_default_cloud_voxel));
|
||||
final float cloudVoxelSize = Float.parseFloat(cloudVoxelSizeStr);
|
||||
final int textureSize = isOBJ?Integer.parseInt(sharedPref.getString(getString(R.string.pref_key_texture_size), getString(R.string.pref_default_texture_size))):0;
|
||||
final int normalK = Integer.parseInt(sharedPref.getString(getString(R.string.pref_key_normal_k), getString(R.string.pref_default_normal_k)));
|
||||
final float maxTextureDistance = Float.parseFloat(sharedPref.getString(getString(R.string.pref_key_max_texture_distance), getString(R.string.pref_default_max_texture_distance)));
|
||||
@@ -1511,6 +1544,7 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
final boolean success = RTABMapLib.exportMesh(
|
||||
tmpPath,
|
||||
cloudVoxelSize,
|
||||
regenerateCloud,
|
||||
meshing,
|
||||
textureSize,
|
||||
normalK,
|
||||
@@ -1527,6 +1561,11 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
public void run() {
|
||||
if(success)
|
||||
{
|
||||
if(!meshing && cloudVoxelSize>0.0f)
|
||||
{
|
||||
mToast.makeText(getActivity(), String.format("Cloud assembled and voxelized at %s m.", cloudVoxelSizeStr), mToast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
// Visualize the result?
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setCancelable(false)
|
||||
@@ -1563,6 +1602,10 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
saveOnDevice();
|
||||
}
|
||||
})
|
||||
.setNeutralButton("Cancel", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
})
|
||||
@@ -1596,11 +1639,11 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
builder.setTitle("Choose Your File (*.db)");
|
||||
builder.setItems(filesWithSize, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, final int which) {
|
||||
|
||||
// Smooth and adjust color now?
|
||||
|
||||
// Adjust color now?
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle("Opening database...")
|
||||
.setMessage("Do you want to smooth and adjust colors now?\nThis can be done later under Optimize menu.")
|
||||
.setMessage("Do you want to adjust colors now?\nThis can be done later under Optimize menu.")
|
||||
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int whichIn) {
|
||||
openDatabase(files[which], true);
|
||||
@@ -1633,6 +1676,67 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void saveDatabase(String fileName)
|
||||
{
|
||||
final String newDatabasePath = mWorkingDirectory + fileName + ".db";
|
||||
final String newDatabasePathHuman = mWorkingDirectoryHuman + fileName + ".db";
|
||||
mProgressDialog.setTitle("Saving");
|
||||
if(mOpenedDatabasePath.equals(newDatabasePath))
|
||||
{
|
||||
mProgressDialog.setMessage(String.format("Please wait while updating \"%s\"...", newDatabasePathHuman));
|
||||
}
|
||||
else
|
||||
{
|
||||
mProgressDialog.setMessage(String.format("Please wait while saving \"%s\"...", newDatabasePathHuman));
|
||||
}
|
||||
mProgressDialog.show();
|
||||
updateState(State.STATE_PROCESSING);
|
||||
startUpdateStatusThread();
|
||||
Thread saveThread = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
RTABMapLib.save(newDatabasePath); // save
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if(mOpenedDatabasePath.equals(newDatabasePath))
|
||||
{
|
||||
mToast.makeText(getActivity(), String.format("Database \"%s\" updated.", newDatabasePathHuman), mToast.LENGTH_LONG).show();
|
||||
}
|
||||
else
|
||||
{
|
||||
mToast.makeText(getActivity(), String.format("Database saved to \"%s\".", newDatabasePathHuman), mToast.LENGTH_LONG).show();
|
||||
|
||||
Intent intent = new Intent(getActivity(), RTABMapActivity.class);
|
||||
// use System.currentTimeMillis() to have a unique ID for the pending intent
|
||||
PendingIntent pIntent = PendingIntent.getActivity(getActivity(), (int) System.currentTimeMillis(), intent, 0);
|
||||
|
||||
// build notification
|
||||
// the addAction re-use the same intent to keep the example short
|
||||
Notification n = new Notification.Builder(getActivity())
|
||||
.setContentTitle(getString(R.string.app_name))
|
||||
.setContentText(newDatabasePathHuman + " saved!")
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setContentIntent(pIntent)
|
||||
.setAutoCancel(true).build();
|
||||
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
|
||||
notificationManager.notify(0, n);
|
||||
}
|
||||
if(!mItemDataRecorderMode.isChecked())
|
||||
{
|
||||
mOpenedDatabasePath = newDatabasePath;
|
||||
}
|
||||
mProgressDialog.dismiss();
|
||||
updateState(State.STATE_IDLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
saveThread.start();
|
||||
}
|
||||
|
||||
private void saveOnDevice()
|
||||
{
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
@@ -1705,6 +1809,7 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
{
|
||||
final String extension = mExportedOBJ?".obj":".ply";
|
||||
final String path = mWorkingDirectory + fileName + extension;
|
||||
final String pathHuman = mWorkingDirectoryHuman + fileName + extension;
|
||||
|
||||
boolean success = true;
|
||||
if(mExportedOBJ)
|
||||
@@ -1726,28 +1831,28 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
copy(fromOBJFile,toOBJFile);
|
||||
copy(fromMTLFile,toMTLFile);
|
||||
copy(fromJPGFile,toJPGFile);
|
||||
mToast.makeText(getActivity(), String.format("Mesh \"%s\" (with texture \"%s\" and \"%s\") successfully exported!", path, fileName + ".jpg", fileName + ".mtl"), mToast.LENGTH_LONG).show();
|
||||
mToast.makeText(getActivity(), String.format("Mesh \"%s\" (with texture \"%s\" and \"%s\") successfully exported!", pathHuman, fileName + ".jpg", fileName + ".mtl"), mToast.LENGTH_LONG).show();
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
mToast.makeText(getActivity(), String.format("Exporting mesh \"%s\" (with texture \"%s\" and \"%s\") failed! Error=%s", path, fileName + ".jpg", fileName + ".mtl", e.getMessage()), mToast.LENGTH_LONG).show();
|
||||
mToast.makeText(getActivity(), String.format("Exporting mesh \"%s\" (with texture \"%s\" and \"%s\") failed! Error=%s", pathHuman, fileName + ".jpg", fileName + ".mtl", e.getMessage()), mToast.LENGTH_LONG).show();
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
File toPLYFile = new File(mWorkingDirectory + fileName + extension);
|
||||
File toPLYFile = new File(path);
|
||||
toPLYFile.delete();
|
||||
File fromPLYFile = new File(mWorkingDirectory + RTABMAP_TMP_DIR + RTABMAP_TMP_FILENAME + extension);
|
||||
|
||||
try
|
||||
{
|
||||
copy(fromPLYFile,toPLYFile);
|
||||
mToast.makeText(getActivity(), String.format("Mesh/point cloud \"%s\" successfully exported!", path), mToast.LENGTH_LONG).show();
|
||||
mToast.makeText(getActivity(), String.format("Mesh/point cloud \"%s\" successfully exported!", pathHuman), mToast.LENGTH_LONG).show();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mToast.makeText(getActivity(), String.format("Exporting mesh/point cloud \"%s\" failed! Error=%s", path, e.getMessage()), mToast.LENGTH_LONG).show();
|
||||
mToast.makeText(getActivity(), String.format("Exporting mesh/point cloud \"%s\" failed! Error=%s", pathHuman, e.getMessage()), mToast.LENGTH_LONG).show();
|
||||
success=false;
|
||||
}
|
||||
}
|
||||
@@ -1762,7 +1867,7 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
// the addAction re-use the same intent to keep the example short
|
||||
Notification n = new Notification.Builder(getActivity())
|
||||
.setContentTitle(getString(R.string.app_name))
|
||||
.setContentText(path + " exported!")
|
||||
.setContentText(pathHuman + " exported!")
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setContentIntent(pIntent)
|
||||
.setAutoCancel(true).build();
|
||||
@@ -1775,19 +1880,11 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void openDatabase(String fileName, boolean optimize)
|
||||
private void openDatabase(final String fileName, final boolean optimize)
|
||||
{
|
||||
mOpenedDatabasePath = mWorkingDirectory + fileName;
|
||||
|
||||
if(!mItemTrajectoryMode.isChecked())
|
||||
{
|
||||
mProgressDialog.setTitle("Loading");
|
||||
mProgressDialog.setMessage(String.format("Database \"%s\" loaded. Please wait while creating point clouds and meshes...", fileName));
|
||||
mProgressDialog.show();
|
||||
updateState(State.STATE_PROCESSING);
|
||||
}
|
||||
|
||||
String tmpDatabase = mWorkingDirectory+RTABMAP_TMP_DB;
|
||||
final String tmpDatabase = mWorkingDirectory+RTABMAP_TMP_DB;
|
||||
(new File(tmpDatabase)).delete();
|
||||
try{
|
||||
copy(new File(mOpenedDatabasePath), new File(tmpDatabase));
|
||||
@@ -1798,32 +1895,62 @@ public class RTABMapActivity extends Activity implements OnClickListener {
|
||||
}
|
||||
|
||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
boolean databaseInMemory = sharedPref.getBoolean(getString(R.string.pref_key_db_in_memory), Boolean.parseBoolean(getString(R.string.pref_default_db_in_memory)));
|
||||
int status = RTABMapLib.openDatabase(tmpDatabase, databaseInMemory, optimize);
|
||||
setCamera(1);
|
||||
updateState(State.STATE_IDLE);
|
||||
final boolean databaseInMemory = sharedPref.getBoolean(getString(R.string.pref_key_db_in_memory), Boolean.parseBoolean(getString(R.string.pref_default_db_in_memory)));
|
||||
|
||||
if(status == -1)
|
||||
{
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setCancelable(false)
|
||||
.setTitle("Error")
|
||||
.setMessage("The map is loaded but optimization of the map's graph has "
|
||||
+ "failed, so the map cannot be shown. Change the Graph Optimizer approach used"
|
||||
+ " or enable/disable if the graph is optimized from graph "
|
||||
+ "end in \"Settings -> Mapping...\" and try opening again.")
|
||||
.setPositiveButton("Open Settings", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = new Intent(getActivity(), SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Close", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
mProgressDialog.setTitle("Loading");
|
||||
mProgressDialog.setMessage(String.format("Opening database \"%s\"...", fileName));
|
||||
mProgressDialog.show();
|
||||
updateState(State.STATE_PROCESSING);
|
||||
|
||||
Thread openThread = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
|
||||
final int status = RTABMapLib.openDatabase(tmpDatabase, databaseInMemory, optimize);
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
setCamera(1);
|
||||
updateState(State.STATE_IDLE);
|
||||
mProgressDialog.dismiss();
|
||||
if(status == -1)
|
||||
{
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setCancelable(false)
|
||||
.setTitle("Error")
|
||||
.setMessage("The map is loaded but optimization of the map's graph has "
|
||||
+ "failed, so the map cannot be shown. Change the Graph Optimizer approach used"
|
||||
+ " or enable/disable if the graph is optimized from graph "
|
||||
+ "end in \"Settings -> Mapping...\" and try opening again.")
|
||||
.setPositiveButton("Open Settings", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = new Intent(getActivity(), SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Close", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
else
|
||||
{
|
||||
// creating meshes...
|
||||
startUpdateStatusThread();
|
||||
|
||||
if(!mItemTrajectoryMode.isChecked())
|
||||
{
|
||||
mProgressDialog.setTitle("Loading");
|
||||
mProgressDialog.setMessage(String.format("Database \"%s\" loaded. Please wait while creating point clouds and meshes...", fileName));
|
||||
mProgressDialog.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
openThread.start();
|
||||
}
|
||||
|
||||
public void copy(File src, File dst) throws IOException {
|
||||
|
||||
@@ -70,6 +70,7 @@ public class RTABMapLib
|
||||
public static native void setAutoExposure(boolean enabled);
|
||||
public static native void setRawScanSaved(boolean enabled);
|
||||
public static native void setFullResolution(boolean enabled);
|
||||
public static native void setSmoothing(boolean enabled);
|
||||
public static native void setAppendMode(boolean enabled);
|
||||
public static native void setDataRecorderMode(boolean enabled);
|
||||
public static native void setMaxCloudDepth(float value);
|
||||
@@ -88,6 +89,7 @@ public class RTABMapLib
|
||||
public static native boolean exportMesh(
|
||||
String filePath,
|
||||
float cloudVoxelSize,
|
||||
boolean regenerateCloud,
|
||||
boolean meshing,
|
||||
int textureSize,
|
||||
int normalK,
|
||||
|
||||
@@ -16,10 +16,14 @@
|
||||
|
||||
package com.introlab.rtabmap;
|
||||
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.opengl.GLES20;
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.opengl.Matrix;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -30,56 +34,161 @@ import javax.microedition.khronos.opengles.GL10;
|
||||
// ground grid, camera frustum, camera axis, and trajectory based on the Tango
|
||||
// device's pose.
|
||||
public class Renderer implements GLSurfaceView.Renderer {
|
||||
|
||||
private static Activity mActivity;
|
||||
public Renderer(Activity c) {
|
||||
mActivity = c;
|
||||
}
|
||||
|
||||
private ProgressDialog mProgressDialog;
|
||||
private final float[] mtrxProjection = new float[16];
|
||||
private final float[] mtrxView = new float[16];
|
||||
private final float[] mtrxProjectionAndView = new float[16];
|
||||
|
||||
private TextManager mTextManager = null;
|
||||
private float mSurfaceHeight = 0.0f;
|
||||
|
||||
private Vector<TextObject> mTexts;
|
||||
|
||||
private static RTABMapActivity mActivity;
|
||||
public Renderer(RTABMapActivity c) {
|
||||
mActivity = c;
|
||||
}
|
||||
|
||||
private ProgressDialog mProgressDialog = null;
|
||||
private Toast mToast = null;
|
||||
|
||||
private boolean mTextChanged = false;
|
||||
private ReentrantLock mTextLock = new ReentrantLock();
|
||||
|
||||
public void setProgressDialog(ProgressDialog progressDialog)
|
||||
{
|
||||
mProgressDialog = progressDialog;
|
||||
}
|
||||
|
||||
|
||||
public void setToast(Toast toast)
|
||||
{
|
||||
mToast = toast;
|
||||
}
|
||||
|
||||
// Render loop of the Gl context.
|
||||
public void onDrawFrame(GL10 gl) {
|
||||
public void onDrawFrame(GL10 useGLES20instead) {
|
||||
|
||||
try
|
||||
{
|
||||
final int value = RTABMapLib.render();
|
||||
|
||||
if(mTextManager!=null)
|
||||
{
|
||||
if(mTextChanged)
|
||||
{
|
||||
mTextChanged = false;
|
||||
Vector<TextObject> txtcollection = new Vector<TextObject>();
|
||||
|
||||
mTextLock.lock();
|
||||
try {
|
||||
if(mTexts.size() > 0)
|
||||
{
|
||||
txtcollection.addAll(mTexts);
|
||||
}
|
||||
} finally {
|
||||
mTextLock.unlock();
|
||||
}
|
||||
|
||||
// Prepare the text for rendering
|
||||
mTextManager.PrepareDraw(txtcollection);
|
||||
}
|
||||
|
||||
mTextManager.Draw(mtrxProjectionAndView);
|
||||
}
|
||||
|
||||
mActivity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if(value != 0 && mProgressDialog != null && mProgressDialog.isShowing())
|
||||
{
|
||||
Log.i("RTABMapActivity", "Renderer: dismiss dialog, value received=" + String.valueOf(value));
|
||||
mProgressDialog.dismiss();
|
||||
mActivity.stopUpdateStatusThread();
|
||||
}
|
||||
if(value==-1)
|
||||
if(value==-1 && mToast!=null)
|
||||
{
|
||||
Toast.makeText(mActivity, String.format("Out of Memory!"), Toast.LENGTH_LONG).show();
|
||||
mToast.makeText(mActivity, String.format("Out of Memory!"), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(final Exception e)
|
||||
{
|
||||
mActivity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
Toast.makeText(mActivity, String.format("Rendering error! %s", e.getMessage()), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
if(mToast!=null)
|
||||
{
|
||||
mActivity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
mToast.makeText(mActivity, String.format("Rendering error! %s", e.getMessage()), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called when the surface size changes.
|
||||
public void onSurfaceChanged(GL10 gl, int width, int height) {
|
||||
RTABMapLib.setupGraphic(width, height);
|
||||
}
|
||||
// Called when the surface size changes.
|
||||
public void onSurfaceChanged(GL10 useGLES20instead, int width, int height) {
|
||||
|
||||
RTABMapLib.setupGraphic(width, height);
|
||||
|
||||
mSurfaceHeight = (float)height;
|
||||
|
||||
// Called when the surface is created or recreated.
|
||||
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
|
||||
RTABMapLib.initGlContent();
|
||||
}
|
||||
// Clear our matrices
|
||||
for(int i=0;i<16;i++)
|
||||
{
|
||||
mtrxProjection[i] = 0.0f;
|
||||
mtrxView[i] = 0.0f;
|
||||
mtrxProjectionAndView[i] = 0.0f;
|
||||
}
|
||||
|
||||
// Setup our screen width and height for normal sprite translation.
|
||||
Matrix.orthoM(mtrxProjection, 0, 0f, width, 0.0f, height, 0, 50);
|
||||
|
||||
// Set the camera position (View matrix)
|
||||
Matrix.setLookAtM(mtrxView, 0, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
|
||||
|
||||
// Calculate the projection and view transformation
|
||||
Matrix.multiplyMM(mtrxProjectionAndView, 0, mtrxProjection, 0, mtrxView, 0);
|
||||
}
|
||||
|
||||
// Called when the surface is created or recreated.
|
||||
public void onSurfaceCreated(GL10 useGLES20instead, EGLConfig config) {
|
||||
|
||||
RTABMapLib.initGlContent();
|
||||
|
||||
// Create our text manager
|
||||
mTextManager = new TextManager(mActivity);
|
||||
|
||||
GLES20.glEnable(GLES20.GL_BLEND);
|
||||
GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
|
||||
public void updateTexts(String[] texts)
|
||||
{
|
||||
if(mTextManager != null && mSurfaceHeight > 0.0f)
|
||||
{
|
||||
Vector<TextObject> textObjects = new Vector<TextObject>();
|
||||
float offset = mSurfaceHeight-mTextManager.getMaxTextHeight();
|
||||
if(texts != null)
|
||||
{
|
||||
for(int i=0;i<texts.length; ++i)
|
||||
{
|
||||
if(texts[i]!=null && texts[i].length()>0)
|
||||
{
|
||||
TextObject txt = new TextObject(texts[i], 0, offset);
|
||||
textObjects.add(txt);
|
||||
}
|
||||
offset-=mTextManager.getMaxTextHeight();
|
||||
}
|
||||
}
|
||||
|
||||
mTextLock.lock();
|
||||
try {
|
||||
mTexts = textObjects;
|
||||
} finally {
|
||||
mTextLock.unlock();
|
||||
}
|
||||
|
||||
mTextChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,8 +303,7 @@ public class SketchfabActivity extends Activity implements OnClickListener {
|
||||
{
|
||||
Log.i(RTABMapActivity.TAG, String.format("Zipped files = %d MB", fileSizeMB));
|
||||
builder.setMessage(String.format("Total size to upload = %d MB. %sDo you want to continue?\n\n"
|
||||
+ "Tip: To reduce the model size, try \"Optimized Mesh\" export. "
|
||||
+ "You can also look at the Settings->Exporting options to reduce the output size.", fileSizeMB,
|
||||
+ "Tip: To reduce the model size, you can also look at the Settings->Exporting options to reduce the output size.", fileSizeMB,
|
||||
fileSizeMB>=50?"Note that for size over 50 MB, a Sketchfab PRO account is required, otherwise the upload may fail. ":""));
|
||||
}
|
||||
|
||||
@@ -448,7 +447,7 @@ public class SketchfabActivity extends Activity implements OnClickListener {
|
||||
multipart.addFormField("description", mDescription.getText().toString());
|
||||
multipart.addFormField("tags", mTags.getText().toString());
|
||||
multipart.addFormField("source", "RTAB-Map");
|
||||
multipart.addFormField("isPublished", mDraft.isChecked()?"true":"false");
|
||||
multipart.addFormField("isPublished", mDraft.isChecked()?"false":"true");
|
||||
multipart.addFilePart("modelFile", uploadFile);
|
||||
|
||||
Log.i(RTABMapActivity.TAG, "Starting multipart request");
|
||||
|
||||
@@ -0,0 +1,443 @@
|
||||
//package ri.blog.opengl008;
|
||||
package com.introlab.rtabmap;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.Iterator;
|
||||
import java.util.Vector;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.opengl.GLES20;
|
||||
import android.opengl.GLUtils;
|
||||
import android.text.TextPaint;
|
||||
|
||||
public class TextManager {
|
||||
|
||||
/* SHADER Text
|
||||
*
|
||||
* This shader is for rendering 2D text textures straight from a texture
|
||||
* Color and alpha blended.
|
||||
*
|
||||
*/
|
||||
public static final String vs_Text =
|
||||
"uniform mat4 uMVPMatrix;" +
|
||||
"attribute vec4 vPosition;" +
|
||||
"attribute vec4 a_Color;" +
|
||||
"attribute vec2 a_texCoord;" +
|
||||
"varying vec4 v_Color;" +
|
||||
"varying vec2 v_texCoord;" +
|
||||
"void main() {" +
|
||||
" gl_Position = uMVPMatrix * vPosition;" +
|
||||
" v_texCoord = a_texCoord;" +
|
||||
" v_Color = a_Color;" +
|
||||
"}";
|
||||
public static final String fs_Text =
|
||||
"precision mediump float;" +
|
||||
"varying vec4 v_Color;" +
|
||||
"varying vec2 v_texCoord;" +
|
||||
"uniform sampler2D s_texture;" +
|
||||
"void main() {" +
|
||||
" gl_FragColor = texture2D( s_texture, v_texCoord ) * v_Color;" +
|
||||
" gl_FragColor.rgb *= v_Color.a;" +
|
||||
"}";
|
||||
|
||||
public static int sp_Text;
|
||||
|
||||
public static final int RI_TEXT_TEXTURE_SIZE = 512; // 512
|
||||
public static final float RI_TEXT_HEIGHT_BASE = 32.0f;
|
||||
public static final char RI_TEXT_START = ' ';
|
||||
public static final char RI_TEXT_STOP = '~'+1;
|
||||
|
||||
public float getMaxTextHeight() {return mTextHeight;}
|
||||
|
||||
private float mUVWidth;
|
||||
private float mUVHeight;
|
||||
private float mTextHeight;
|
||||
|
||||
private FloatBuffer vertexBuffer;
|
||||
private FloatBuffer textureBuffer;
|
||||
private FloatBuffer colorBuffer;
|
||||
private ShortBuffer drawListBuffer;
|
||||
|
||||
private float[] vecs;
|
||||
private float[] uvs;
|
||||
private short[] indices;
|
||||
private float[] colors;
|
||||
|
||||
private int index_vecs;
|
||||
private int index_indices;
|
||||
private int index_uvs;
|
||||
private int index_colors;
|
||||
|
||||
private int texturenr;
|
||||
private int[] mTextures;
|
||||
|
||||
private float uniformscale = 1.0f;
|
||||
|
||||
float[] mCharacterWidth;
|
||||
|
||||
public TextManager(Context context)
|
||||
{
|
||||
// Create the arrays
|
||||
vecs = new float[3 * 10];
|
||||
colors = new float[4 * 10];
|
||||
uvs = new float[2 * 10];
|
||||
indices = new short[10];
|
||||
|
||||
// init as 0 as default
|
||||
texturenr = 0;
|
||||
|
||||
// Text shader
|
||||
int vshadert = TextManager.loadShader(GLES20.GL_VERTEX_SHADER, TextManager.vs_Text);
|
||||
int fshadert = TextManager.loadShader(GLES20.GL_FRAGMENT_SHADER, TextManager.fs_Text);
|
||||
|
||||
TextManager.sp_Text = GLES20.glCreateProgram();
|
||||
GLES20.glAttachShader(TextManager.sp_Text, vshadert);
|
||||
GLES20.glAttachShader(TextManager.sp_Text, fshadert); // add the fragment shader to program
|
||||
GLES20.glLinkProgram(TextManager.sp_Text); // creates OpenGL ES program executables
|
||||
|
||||
// Generate Textures, if more needed, alter these numbers.
|
||||
mTextures = new int[1];
|
||||
GLES20.glGenTextures(1, mTextures, 0);
|
||||
|
||||
// Create an empty, mutable bitmap
|
||||
Bitmap bitmap = Bitmap.createBitmap(RI_TEXT_TEXTURE_SIZE, RI_TEXT_TEXTURE_SIZE, Bitmap.Config.ARGB_4444);
|
||||
// get a canvas to paint over the bitmap
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
bitmap.eraseColor(0);
|
||||
|
||||
// Draw the text
|
||||
TextPaint textPaint = new TextPaint();
|
||||
textPaint.setTextSize(RI_TEXT_HEIGHT_BASE);
|
||||
textPaint.setColor(Color.WHITE);
|
||||
textPaint.setAntiAlias(true);
|
||||
textPaint.setTypeface(Typeface.create("Arial", Typeface.BOLD));
|
||||
|
||||
// compute real maximum height
|
||||
mTextHeight=0.0f;
|
||||
for(char c=RI_TEXT_START; c<RI_TEXT_STOP; ++c)
|
||||
{
|
||||
Rect textBounds = new Rect();
|
||||
textPaint.getTextBounds(String.valueOf(c), 0, 1, textBounds);
|
||||
if(textBounds.height() + textBounds.bottom > mTextHeight)
|
||||
{
|
||||
mTextHeight = textBounds.height() + textBounds.bottom;
|
||||
}
|
||||
}
|
||||
mUVWidth = (float)RI_TEXT_HEIGHT_BASE/(float)RI_TEXT_TEXTURE_SIZE;
|
||||
mUVHeight = mTextHeight/(float)RI_TEXT_TEXTURE_SIZE;
|
||||
|
||||
int colCount = RI_TEXT_TEXTURE_SIZE/(int)RI_TEXT_HEIGHT_BASE;
|
||||
mCharacterWidth = new float[RI_TEXT_STOP-RI_TEXT_START];
|
||||
int i=0;
|
||||
for(char c=RI_TEXT_START; c<RI_TEXT_STOP; ++c)
|
||||
{
|
||||
Rect textBounds = new Rect();
|
||||
textPaint.getTextBounds(String.valueOf(c), 0, 1, textBounds);
|
||||
canvas.drawText(String.valueOf(c), (i%colCount)*RI_TEXT_HEIGHT_BASE, (i/colCount) * mTextHeight + RI_TEXT_HEIGHT_BASE, textPaint);
|
||||
mCharacterWidth[i] = textPaint.measureText(String.valueOf(c));
|
||||
++i;
|
||||
}
|
||||
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]);
|
||||
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
|
||||
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
|
||||
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
|
||||
|
||||
//Clean up
|
||||
bitmap.recycle();
|
||||
}
|
||||
|
||||
public void setTextureID(int val)
|
||||
{
|
||||
texturenr = val;
|
||||
}
|
||||
|
||||
public static int loadShader(int type, String shaderCode){
|
||||
|
||||
// create a vertex shader type (GLES20.GL_VERTEX_SHADER)
|
||||
// or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
|
||||
int shader = GLES20.glCreateShader(type);
|
||||
|
||||
// add the source code to the shader and compile it
|
||||
GLES20.glShaderSource(shader, shaderCode);
|
||||
GLES20.glCompileShader(shader);
|
||||
|
||||
// return the shader
|
||||
return shader;
|
||||
}
|
||||
|
||||
|
||||
public void AddCharRenderInformation(float[] vec, float[] cs, float[] uv, short[] indi)
|
||||
{
|
||||
// We need a base value because the object has indices related to
|
||||
// that object and not to this collection so basicly we need to
|
||||
// translate the indices to align with the vertexlocation in ou
|
||||
// vecs array of vectors.
|
||||
short base = (short) (index_vecs / 3);
|
||||
|
||||
// We should add the vec, translating the indices to our saved vector
|
||||
for(int i=0;i<vec.length;i++)
|
||||
{
|
||||
vecs[index_vecs] = vec[i];
|
||||
index_vecs++;
|
||||
}
|
||||
|
||||
// We should add the colors, so we can use the same texture for multiple effects.
|
||||
for(int i=0;i<cs.length;i++)
|
||||
{
|
||||
colors[index_colors] = cs[i];
|
||||
index_colors++;
|
||||
}
|
||||
|
||||
// We should add the uvs
|
||||
for(int i=0;i<uv.length;i++)
|
||||
{
|
||||
uvs[index_uvs] = uv[i];
|
||||
index_uvs++;
|
||||
}
|
||||
|
||||
// We handle the indices
|
||||
for(int j=0;j<indi.length;j++)
|
||||
{
|
||||
indices[index_indices] = (short) (base + indi[j]);
|
||||
index_indices++;
|
||||
}
|
||||
}
|
||||
|
||||
public void PrepareDrawInfo(Vector<TextObject> txtcollection)
|
||||
{
|
||||
// Reset the indices.
|
||||
index_vecs = 0;
|
||||
index_indices = 0;
|
||||
index_uvs = 0;
|
||||
index_colors = 0;
|
||||
|
||||
// Get the total amount of characters
|
||||
int charcount = 0;
|
||||
for (TextObject txt : txtcollection) {
|
||||
if(txt!=null)
|
||||
{
|
||||
if(!(txt.text==null))
|
||||
{
|
||||
charcount += txt.text.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the arrays we need with the correct size.
|
||||
vecs = null;
|
||||
colors = null;
|
||||
uvs = null;
|
||||
indices = null;
|
||||
|
||||
vecs = new float[charcount * 12];
|
||||
colors = new float[charcount * 16];
|
||||
uvs = new float[charcount * 8];
|
||||
indices = new short[charcount * 6];
|
||||
|
||||
}
|
||||
|
||||
public void PrepareDraw(Vector<TextObject> txtcollection)
|
||||
{
|
||||
// Setup all the arrays
|
||||
PrepareDrawInfo(txtcollection);
|
||||
|
||||
// Using the iterator protects for problems with concurrency
|
||||
for( Iterator< TextObject > it = txtcollection.iterator(); it.hasNext() ; )
|
||||
{
|
||||
TextObject txt = it.next();
|
||||
if(txt!=null)
|
||||
{
|
||||
if(!(txt.text==null))
|
||||
{
|
||||
convertTextToTriangleInfo(txt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw(float[] m)
|
||||
{
|
||||
if(vecs.length > 0)
|
||||
{
|
||||
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
|
||||
|
||||
// Set the correct shader for our grid object.
|
||||
GLES20.glUseProgram(sp_Text);
|
||||
|
||||
// The vertex buffer.
|
||||
ByteBuffer bb = ByteBuffer.allocateDirect(vecs.length * 4);
|
||||
bb.order(ByteOrder.nativeOrder());
|
||||
vertexBuffer = bb.asFloatBuffer();
|
||||
vertexBuffer.put(vecs);
|
||||
vertexBuffer.position(0);
|
||||
|
||||
// The vertex buffer.
|
||||
ByteBuffer bb3 = ByteBuffer.allocateDirect(colors.length * 4);
|
||||
bb3.order(ByteOrder.nativeOrder());
|
||||
colorBuffer = bb3.asFloatBuffer();
|
||||
colorBuffer.put(colors);
|
||||
colorBuffer.position(0);
|
||||
|
||||
// The texture buffer
|
||||
ByteBuffer bb2 = ByteBuffer.allocateDirect(uvs.length * 4);
|
||||
bb2.order(ByteOrder.nativeOrder());
|
||||
textureBuffer = bb2.asFloatBuffer();
|
||||
textureBuffer.put(uvs);
|
||||
textureBuffer.position(0);
|
||||
|
||||
// initialize byte buffer for the draw list
|
||||
ByteBuffer dlb = ByteBuffer.allocateDirect(indices.length * 2);
|
||||
dlb.order(ByteOrder.nativeOrder());
|
||||
drawListBuffer = dlb.asShortBuffer();
|
||||
drawListBuffer.put(indices);
|
||||
drawListBuffer.position(0);
|
||||
|
||||
// get handle to vertex shader's vPosition member
|
||||
int mPositionHandle = GLES20.glGetAttribLocation(sp_Text, "vPosition");
|
||||
|
||||
// Enable a handle to the triangle vertices
|
||||
GLES20.glEnableVertexAttribArray(mPositionHandle);
|
||||
|
||||
// Prepare the background coordinate data
|
||||
GLES20.glVertexAttribPointer(mPositionHandle, 3,
|
||||
GLES20.GL_FLOAT, false,
|
||||
0, vertexBuffer);
|
||||
|
||||
int mTexCoordLoc = GLES20.glGetAttribLocation(sp_Text, "a_texCoord" );
|
||||
|
||||
// Prepare the texturecoordinates
|
||||
GLES20.glVertexAttribPointer ( mTexCoordLoc, 2, GLES20.GL_FLOAT,
|
||||
false,
|
||||
0, textureBuffer);
|
||||
|
||||
GLES20.glEnableVertexAttribArray ( mPositionHandle );
|
||||
GLES20.glEnableVertexAttribArray ( mTexCoordLoc );
|
||||
|
||||
int mColorHandle = GLES20.glGetAttribLocation(sp_Text, "a_Color");
|
||||
|
||||
// Enable a handle to the triangle vertices
|
||||
GLES20.glEnableVertexAttribArray(mColorHandle);
|
||||
|
||||
// Prepare the background coordinate data
|
||||
GLES20.glVertexAttribPointer(mColorHandle, 4,
|
||||
GLES20.GL_FLOAT, false,
|
||||
0, colorBuffer);
|
||||
|
||||
// get handle to shape's transformation matrix
|
||||
int mtrxhandle = GLES20.glGetUniformLocation(sp_Text, "uMVPMatrix");
|
||||
|
||||
// Apply the projection and view transformation
|
||||
GLES20.glUniformMatrix4fv(mtrxhandle, 1, false, m, 0);
|
||||
|
||||
int mSamplerLoc = GLES20.glGetUniformLocation (sp_Text, "s_texture" );
|
||||
|
||||
// Texture activate unit 0
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
|
||||
// Bind the texture to this unit.
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]);
|
||||
// Set the sampler texture unit to our selected id
|
||||
GLES20.glUniform1i ( mSamplerLoc, texturenr);
|
||||
|
||||
// Draw the triangle
|
||||
GLES20.glDrawElements(GLES20.GL_TRIANGLES, indices.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
|
||||
|
||||
// Disable vertex array
|
||||
GLES20.glDisableVertexAttribArray(mPositionHandle);
|
||||
GLES20.glDisableVertexAttribArray(mTexCoordLoc);
|
||||
GLES20.glDisableVertexAttribArray(mColorHandle);
|
||||
}
|
||||
}
|
||||
|
||||
private void convertTextToTriangleInfo(TextObject val)
|
||||
{
|
||||
// Get attributes from text object
|
||||
float x = val.x;
|
||||
float y = val.y;
|
||||
String text = val.text;
|
||||
|
||||
// Create
|
||||
for(int j=0; j<text.length(); j++)
|
||||
{
|
||||
// get ascii value
|
||||
char c = text.charAt(j);
|
||||
int c_val = (int)c;
|
||||
|
||||
int indx = c_val-RI_TEXT_START;
|
||||
|
||||
if(indx<0 || indx>=mCharacterWidth.length) {
|
||||
// unknown character, we will add a space for it to be save.
|
||||
indx = 0;
|
||||
}
|
||||
|
||||
int colCount = RI_TEXT_TEXTURE_SIZE/(int)RI_TEXT_HEIGHT_BASE;
|
||||
|
||||
// Calculate the uv parts
|
||||
int row = indx / colCount;
|
||||
int col = indx % colCount;
|
||||
|
||||
float v = row * mUVHeight;
|
||||
float v2 = v + mUVHeight;
|
||||
float u = col * mUVWidth;
|
||||
float u2 = u + mCharacterWidth[indx]/(float)RI_TEXT_TEXTURE_SIZE;
|
||||
|
||||
// Creating the triangle information
|
||||
float[] vec = new float[12];
|
||||
float[] uv = new float[8];
|
||||
float[] colors = new float[16];
|
||||
|
||||
vec[0] = x;
|
||||
vec[1] = y + (mTextHeight * uniformscale);
|
||||
vec[2] = 0.99f;
|
||||
vec[3] = x;
|
||||
vec[4] = y;
|
||||
vec[5] = 0.99f;
|
||||
vec[6] = x + (mCharacterWidth[indx] * uniformscale);
|
||||
vec[7] = y;
|
||||
vec[8] = 0.99f;
|
||||
vec[9] = x + (mCharacterWidth[indx] * uniformscale);
|
||||
vec[10] = y + (mTextHeight * uniformscale);
|
||||
vec[11] = 0.99f;
|
||||
|
||||
colors = new float[]
|
||||
{val.color[0], val.color[1], val.color[2], val.color[3],
|
||||
val.color[0], val.color[1], val.color[2], val.color[3],
|
||||
val.color[0], val.color[1], val.color[2], val.color[3],
|
||||
val.color[0], val.color[1], val.color[2], val.color[3]
|
||||
};
|
||||
// 0.001f = texture bleeding hack/fix
|
||||
uv[0] = u+0.001f;
|
||||
uv[1] = v+0.001f;
|
||||
uv[2] = u+0.001f;
|
||||
uv[3] = v2-0.001f;
|
||||
uv[4] = u2-0.001f;
|
||||
uv[5] = v2-0.001f;
|
||||
uv[6] = u2-0.001f;
|
||||
uv[7] = v+0.001f;
|
||||
|
||||
short[] inds = {0, 1, 2, 0, 2, 3};
|
||||
|
||||
// Add our triangle information to our collection for 1 render call.
|
||||
AddCharRenderInformation(vec, colors, uv, inds);
|
||||
|
||||
// Calculate the new position
|
||||
x += (mCharacterWidth[indx] * uniformscale);
|
||||
}
|
||||
}
|
||||
|
||||
public float getUniformscale() {
|
||||
return uniformscale;
|
||||
}
|
||||
|
||||
public void setUniformscale(float uniformscale) {
|
||||
this.uniformscale = uniformscale;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//package ri.blog.opengl008;
|
||||
package com.introlab.rtabmap;
|
||||
|
||||
public class TextObject {
|
||||
|
||||
public String text;
|
||||
public float x;
|
||||
public float y;
|
||||
public float[] color;
|
||||
|
||||
public TextObject()
|
||||
{
|
||||
text = "default";
|
||||
x = 0f;
|
||||
y = 0f;
|
||||
color = new float[] {1f, 1f, 1f, 1.0f};
|
||||
}
|
||||
|
||||
public TextObject(String txt, float xcoord, float ycoord)
|
||||
{
|
||||
text = txt;
|
||||
x = xcoord;
|
||||
y = ycoord;
|
||||
color = new float[] {1f, 1f, 1f, 1.0f};
|
||||
}
|
||||
|
||||
/*public boolean validate()
|
||||
{
|
||||
if(text.compareTo("")==0) return false;
|
||||
|
||||
return true;
|
||||
}*/
|
||||
}
|
||||
Reference in New Issue
Block a user