Merge "Adding better navigation to model viewer (pinch to zoom, rotations) Adding ability to load a3d files from disk."

This commit is contained in:
Alex Sakhartchouk
2011-02-17 09:14:03 -08:00
committed by Android (Google) Code Review
8 changed files with 462 additions and 102 deletions

View File

@@ -3,14 +3,19 @@
package="com.android.modelviewer">
<application android:label="ModelViewer">
<activity android:name="SimpleModel"
android:label="SimpleModel"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.Black.NoTitleBar">
android:label="SimpleModel">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="A3DSelector"
android:label="A3DSelector"
android:hardwareAccelerated="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity android:name="SceneGraph"
android:label="SceneGraph"
android:theme="@android:style/Theme.Black.NoTitleBar">

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/load_model"
android:title="@string/load_model" />
<item android:id="@+id/display_options"
android:title="@string/display_options" />
</menu>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<skip />
<string name="load_model">Load Model</string>
<string name="display_options">Display Options</string>
</resources>

View File

@@ -0,0 +1,110 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.modelviewer;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.List;
import android.app.ListActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
/**
* A list view where the last item the user clicked is placed in
* the "activated" state, causing its background to highlight.
*/
public class A3DSelector extends ListActivity {
File[] mCurrentSubList;
File mCurrentFile;
class A3DFilter implements FileFilter {
public boolean accept(File file) {
if (file.isDirectory()) {
return true;
}
return file.getName().endsWith(".a3d");
}
}
private void populateList(File file) {
mCurrentFile = file;
setTitle(mCurrentFile.getAbsolutePath() + "/*.a3d");
List<String> names = new ArrayList<String>();
names.add("..");
mCurrentSubList = mCurrentFile.listFiles(new A3DFilter());
if (mCurrentSubList != null) {
for (int i = 0; i < mCurrentSubList.length; i ++) {
String fileName = mCurrentSubList[i].getName();
if (mCurrentSubList[i].isDirectory()) {
fileName = "/" + fileName;
}
names.add(fileName);
}
}
// Use the built-in layout for showing a list item with a single
// line of text whose background is changes when activated.
setListAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_activated_1, names));
getListView().setTextFilterEnabled(true);
// Tell the list view to show one checked/activated item at a time.
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
populateList(new File("/sdcard/"));
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
if (position == 0) {
File parent = mCurrentFile.getParentFile();
if (parent == null) {
return;
}
populateList(parent);
return;
}
// the first thing in list is parent directory
File selectedFile = mCurrentSubList[position - 1];
if (selectedFile.isDirectory()) {
populateList(selectedFile);
return;
}
Intent resultIntent = new Intent();
resultIntent.setData(Uri.fromFile(selectedFile));
setResult(RESULT_OK, resultIntent);
finish();
}
}

View File

@@ -21,6 +21,7 @@ import android.renderscript.RenderScript;
import android.app.Activity;
import android.content.res.Configuration;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -31,9 +32,11 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.MenuInflater;
import android.view.Window;
import android.widget.Button;
import android.widget.ListView;
import android.net.Uri;
import java.lang.Runtime;
@@ -67,5 +70,45 @@ public class SimpleModel extends Activity {
mView.pause();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.loader_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.load_model:
loadModel();
return true;
case R.id.display_options:
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private static final int FIND_A3D_MODEL = 10;
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
if (requestCode == FIND_A3D_MODEL) {
Uri selectedImageUri = data.getData();
Log.e("Selected Path: ", selectedImageUri.getPath());
mView.loadA3DFile(selectedImageUri.getPath());
}
}
}
public void loadModel() {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_PICK);
intent.setClassName("com.android.modelviewer",
"com.android.modelviewer.A3DSelector");
startActivityForResult(intent, FIND_A3D_MODEL);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008 The Android Open Source Project
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,24 +26,20 @@ import android.util.Log;
public class SimpleModelRS {
private final int STATE_LAST_FOCUS = 1;
int mWidth;
int mHeight;
int mRotation;
public SimpleModelRS() {
}
public void init(RenderScriptGL rs, Resources res, int width, int height) {
public void init(RenderScriptGL rs, Resources res) {
mRS = rs;
mRes = res;
mWidth = width;
mHeight = height;
mRotation = 0;
initRS();
}
public void surfaceChanged() {
mRS.getWidth();
mRS.getHeight();
}
private Resources mRes;
private RenderScriptGL mRS;
private Sampler mSampler;
@@ -55,34 +51,23 @@ public class SimpleModelRS {
private Allocation mGridImage;
private Allocation mAllocPV;
private Mesh mMesh;
private Font mItalic;
private Allocation mTextAlloc;
private ScriptField_MeshInfo mMeshes;
private ScriptC_simplemodel mScript;
int mLastX;
int mLastY;
public void touchEvent(int x, int y) {
int dx = mLastX - x;
if (Math.abs(dx) > 50 || Math.abs(dx) < 3) {
dx = 0;
}
public void onActionDown(float x, float y) {
mScript.invoke_onActionDown(x, y);
}
mRotation -= dx;
if (mRotation > 360) {
mRotation -= 360;
}
if (mRotation < 0) {
mRotation += 360;
}
public void onActionScale(float scale) {
mScript.invoke_onActionScale(scale);
}
mScript.set_gRotate((float)mRotation);
mLastX = x;
mLastY = y;
public void onActionMove(float x, float y) {
mScript.invoke_onActionMove(x, y);
}
private void initPFS() {
@@ -130,12 +115,48 @@ public class SimpleModelRS {
mScript.set_gTGrid(mGridImage);
}
private void initTextAllocation() {
String allocString = "Displaying file: R.raw.robot";
private void initTextAllocation(String fileName) {
String allocString = "Displaying file: " + fileName;
mTextAlloc = Allocation.createFromString(mRS, allocString, Allocation.USAGE_SCRIPT);
mScript.set_gTextAlloc(mTextAlloc);
}
private void initMeshes(FileA3D model) {
int numEntries = model.getIndexEntryCount();
int numMeshes = 0;
for (int i = 0; i < numEntries; i ++) {
FileA3D.IndexEntry entry = model.getIndexEntry(i);
if (entry != null && entry.getEntryType() == FileA3D.EntryType.MESH) {
numMeshes ++;
}
}
if (numMeshes > 0) {
mMeshes = new ScriptField_MeshInfo(mRS, numMeshes);
for (int i = 0; i < numEntries; i ++) {
FileA3D.IndexEntry entry = model.getIndexEntry(i);
if (entry != null && entry.getEntryType() == FileA3D.EntryType.MESH) {
Mesh mesh = entry.getMesh();
mMeshes.set_mMesh(i, mesh, false);
mMeshes.set_mNumIndexSets(i, mesh.getPrimitiveCount(), false);
}
}
mMeshes.copyAll();
} else {
throw new RSRuntimeException("No valid meshes in file");
}
mScript.bind_gMeshes(mMeshes);
mScript.invoke_updateMeshInfo();
}
public void loadA3DFile(String path) {
FileA3D model = FileA3D.createFromFile(mRS, path);
initMeshes(model);
initTextAllocation(path);
}
private void initRS() {
mScript = new ScriptC_simplemodel(mRS, mRes, R.raw.simplemodel);
@@ -147,18 +168,12 @@ public class SimpleModelRS {
loadImage();
FileA3D model = FileA3D.createFromResource(mRS, mRes, R.raw.robot);
FileA3D.IndexEntry entry = model.getIndexEntry(0);
if (entry == null || entry.getEntryType() != FileA3D.EntryType.MESH) {
Log.e("rs", "could not load model");
} else {
mMesh = (Mesh)entry.getObject();
mScript.set_gTestMesh(mMesh);
}
initMeshes(model);
mItalic = Font.create(mRS, mRes, "serif", Font.Style.ITALIC, 8);
mScript.set_gItalic(mItalic);
initTextAllocation();
initTextAllocation("R.raw.robot");
mRS.bindRootScript(mScript);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008 The Android Open Source Project
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,81 +16,129 @@
package com.android.modelviewer;
import java.io.Writer;
import java.util.ArrayList;
import java.util.concurrent.Semaphore;
import android.renderscript.RSSurfaceView;
import android.renderscript.RenderScript;
import android.renderscript.RenderScriptGL;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.ScaleGestureDetector;
import android.util.Log;
public class SimpleModelView extends RSSurfaceView {
public SimpleModelView(Context context) {
super(context);
//setFocusable(true);
}
private RenderScriptGL mRS;
private SimpleModelRS mRender;
private ScaleGestureDetector mScaleDetector;
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
super.surfaceChanged(holder, format, w, h);
private static final int INVALID_POINTER_ID = -1;
private int mActivePointerId = INVALID_POINTER_ID;
public SimpleModelView(Context context) {
super(context);
ensureRenderScript();
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
private void ensureRenderScript() {
if (mRS == null) {
RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
sc.setDepth(16, 24);
mRS = createRenderScriptGL(sc);
mRS.setSurface(holder, w, h);
mRender = new SimpleModelRS();
mRender.init(mRS, getResources(), w, h);
mRender.init(mRS, getResources());
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
ensureRenderScript();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
super.surfaceChanged(holder, format, w, h);
mRender.surfaceChanged();
}
@Override
protected void onDetachedFromWindow() {
mRender = null;
if (mRS != null) {
mRS = null;
destroyRenderScriptGL();
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
// break point at here
// this method doesn't work when 'extends View' include 'extends ScrollView'.
return super.onKeyDown(keyCode, event);
public void loadA3DFile(String path) {
mRender.loadA3DFile(path);
}
@Override
public boolean onTouchEvent(MotionEvent ev)
{
boolean ret = true;
int act = ev.getAction();
if (act == ev.ACTION_UP) {
ret = false;
public boolean onTouchEvent(MotionEvent ev) {
mScaleDetector.onTouchEvent(ev);
boolean ret = false;
float x = ev.getX();
float y = ev.getY();
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
mRender.onActionDown(x, y);
mActivePointerId = ev.getPointerId(0);
ret = true;
break;
}
case MotionEvent.ACTION_MOVE: {
if (!mScaleDetector.isInProgress()) {
mRender.onActionMove(x, y);
}
mRender.onActionDown(x, y);
ret = true;
break;
}
case MotionEvent.ACTION_UP: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
x = ev.getX(newPointerIndex);
y = ev.getY(newPointerIndex);
mRender.onActionDown(x, y);
mActivePointerId = ev.getPointerId(newPointerIndex);
}
break;
}
}
mRender.touchEvent((int)ev.getX(), (int)ev.getY());
return ret;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
mRender.onActionScale(detector.getScaleFactor());
return true;
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2009 The Android Open Source Project
// Copyright (C) 2011 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -22,17 +22,112 @@ rs_program_vertex gPVBackground;
rs_program_fragment gPFBackground;
rs_allocation gTGrid;
rs_mesh gTestMesh;
rs_program_store gPFSBackground;
float gRotate;
rs_font gItalic;
rs_allocation gTextAlloc;
typedef struct MeshInfo {
rs_mesh mMesh;
int mNumIndexSets;
float3 bBoxMin;
float3 bBoxMax;
} MeshInfo_t;
MeshInfo_t *gMeshes;
static float3 gLookAt;
static float gRotateX;
static float gRotateY;
static float gZoom;
static float gLastX;
static float gLastY;
void onActionDown(float x, float y) {
gLastX = x;
gLastY = y;
}
void onActionScale(float scale) {
gZoom *= 1.0f / scale;
gZoom = max(0.1f, min(gZoom, 500.0f));
}
void onActionMove(float x, float y) {
float dx = gLastX - x;
float dy = gLastY - y;
if (fabs(dy) <= 2.0f) {
dy = 0.0f;
}
if (fabs(dx) <= 2.0f) {
dx = 0.0f;
}
gRotateY -= dx;
if (gRotateY > 360) {
gRotateY -= 360;
}
if (gRotateY < 0) {
gRotateY += 360;
}
gRotateX -= dy;
gRotateX = min(gRotateX, 80.0f);
gRotateX = max(gRotateX, -80.0f);
gLastX = x;
gLastY = y;
}
void init() {
gRotate = 0.0f;
gRotateX = 0.0f;
gRotateY = 0.0f;
gZoom = 50.0f;
gLookAt = 0.0f;
}
void updateMeshInfo() {
rs_allocation allMeshes = rsGetAllocation(gMeshes);
int size = rsAllocationGetDimX(allMeshes);
gLookAt = 0.0f;
float minX, minY, minZ, maxX, maxY, maxZ;
for (int i = 0; i < size; i++) {
MeshInfo_t *info = (MeshInfo_t*)rsGetElementAt(allMeshes, i);
rsgMeshComputeBoundingBox(info->mMesh,
&minX, &minY, &minZ,
&maxX, &maxY, &maxZ);
info->bBoxMin = (minX, minY, minZ);
info->bBoxMax = (maxX, maxY, maxZ);
gLookAt += (info->bBoxMin + info->bBoxMax)*0.5f;
}
gLookAt = gLookAt / (float)size;
}
static void renderAllMeshes() {
rs_allocation allMeshes = rsGetAllocation(gMeshes);
int size = rsAllocationGetDimX(allMeshes);
gLookAt = 0.0f;
float minX, minY, minZ, maxX, maxY, maxZ;
for (int i = 0; i < size; i++) {
MeshInfo_t *info = (MeshInfo_t*)rsGetElementAt(allMeshes, i);
rsgDrawMesh(info->mMesh);
}
}
void drawDescription() {
uint width = rsgGetWidth();
uint height = rsgGetHeight();
int left = 0, right = 0, top = 0, bottom = 0;
rsgBindFont(gItalic);
rsgMeasureText(gTextAlloc, &left, &right, &top, &bottom);
rsgDrawText(gTextAlloc, 2 -left, height - 2 + bottom);
}
int root(int launchID) {
@@ -43,7 +138,7 @@ int root(int launchID) {
rsgBindProgramVertex(gPVBackground);
rs_matrix4x4 proj;
float aspect = (float)rsgGetWidth() / (float)rsgGetHeight();
rsMatrixLoadPerspective(&proj, 30.0f, aspect, 0.1f, 100.0f);
rsMatrixLoadPerspective(&proj, 30.0f, aspect, 1.0f, 100.0f);
rsgProgramVertexLoadProjectionMatrix(&proj);
rsgBindProgramFragment(gPFBackground);
@@ -52,20 +147,15 @@ int root(int launchID) {
rs_matrix4x4 matrix;
rsMatrixLoadIdentity(&matrix);
// Position our model on the screen
rsMatrixTranslate(&matrix, 0.0f, -0.3f, -10.0f);
rsMatrixScale(&matrix, 0.2f, 0.2f, 0.2f);
rsMatrixRotate(&matrix, 25.0f, 1.0f, 0.0f, 0.0f);
rsMatrixRotate(&matrix, gRotate, 0.0f, 1.0f, 0.0f);
// Position our models on the screen
rsMatrixTranslate(&matrix, gLookAt.x, gLookAt.y, gLookAt.z - gZoom);
rsMatrixRotate(&matrix, gRotateX, 1.0f, 0.0f, 0.0f);
rsMatrixRotate(&matrix, gRotateY, 0.0f, 1.0f, 0.0f);
rsgProgramVertexLoadModelMatrix(&matrix);
rsgDrawMesh(gTestMesh);
renderAllMeshes();
rsgFontColor(0.3f, 0.3f, 0.3f, 1.0f);
rsgDrawText("Renderscript model test", 30, 695);
rsgBindFont(gItalic);
rsgDrawText(gTextAlloc, 30, 730);
drawDescription();
return 10;
}