From 7e7dde386d42a1cef0e991d95b34a759cc9a0c38 Mon Sep 17 00:00:00 2001 From: Simon Leistikow Date: Wed, 13 Sep 2017 15:05:38 +0200 Subject: [PATCH] 360 degree images now will be displayed somehow (at least they are) --- .../java/de/trac/spherical/MainActivity.java | 23 ++-- .../de/trac/spherical/rendering/Renderer.java | 71 +++++++++++- .../rendering/SphereSurfaceView.java | 103 ++++++++++++++++++ 3 files changed, 181 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/de/trac/spherical/rendering/SphereSurfaceView.java diff --git a/app/src/main/java/de/trac/spherical/MainActivity.java b/app/src/main/java/de/trac/spherical/MainActivity.java index 4d8c832..1bb24f3 100644 --- a/app/src/main/java/de/trac/spherical/MainActivity.java +++ b/app/src/main/java/de/trac/spherical/MainActivity.java @@ -1,6 +1,9 @@ package de.trac.spherical; +import android.content.Context; import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.net.Uri; import android.opengl.GLSurfaceView; import android.support.v7.app.AppCompatActivity; @@ -17,6 +20,7 @@ import de.trac.spherical.parser.PhotoSphereMetadata; import de.trac.spherical.parser.SphereParser; import de.trac.spherical.rendering.Renderer; +import de.trac.spherical.rendering.SphereSurfaceView; public class MainActivity extends AppCompatActivity { @@ -25,18 +29,17 @@ public class MainActivity extends AppCompatActivity { public static final String MIME_PHOTO_SPHERE = "application/vnd.google.panorama360+jpg"; public static final String MIME_IMAGE = "image/*"; - private TextView text; - private GLSurfaceView surfaceView; + private SphereSurfaceView surfaceView; + private Renderer renderer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - //TODO: Remove later - text = (TextView) findViewById(R.id.hello_world); - surfaceView = (GLSurfaceView) findViewById(R.id.surface_view); - surfaceView.setEGLContextClientVersion(2); - surfaceView.setRenderer(new Renderer()); + + // Initialize renderer and setup surface view. + surfaceView = new SphereSurfaceView(this); + renderer = new Renderer(surfaceView); + setContentView(surfaceView); Intent intent = getIntent(); switch (intent.getAction()) { @@ -129,7 +132,7 @@ public class MainActivity extends AppCompatActivity { } private void displayPhotoSphere(InputStream inputStream, PhotoSphereMetadata metadata) { - //Please fill me! + renderer.setBitmap(BitmapFactory.decodeStream(inputStream)); } /** @@ -137,6 +140,6 @@ public class MainActivity extends AppCompatActivity { * @param inputStream */ private void displayFlatImage(InputStream inputStream) { - + Toast.makeText(this, "Not yet implemented", Toast.LENGTH_SHORT).show(); } } diff --git a/app/src/main/java/de/trac/spherical/rendering/Renderer.java b/app/src/main/java/de/trac/spherical/rendering/Renderer.java index 8c7afa0..b3ed01c 100644 --- a/app/src/main/java/de/trac/spherical/rendering/Renderer.java +++ b/app/src/main/java/de/trac/spherical/rendering/Renderer.java @@ -1,8 +1,9 @@ package de.trac.spherical.rendering; -import android.database.MatrixCursor; +import android.graphics.Bitmap; import android.opengl.GLSurfaceView; +import android.opengl.GLUtils; import android.opengl.Matrix; import android.util.Log; @@ -10,6 +11,7 @@ import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import static android.opengl.GLES20.*; +import static android.opengl.GLUtils.getEGLErrorString; public class Renderer implements GLSurfaceView.Renderer { @@ -65,6 +67,29 @@ public class Renderer implements GLSurfaceView.Renderer { private int positionLocation; private int textureCoordinatesLocation; private int mvpLocation; + private int texLocation; + + // Store texture. + private final int textureID [] = new int[1]; + + // Store bitmap for lazy loading. + private Bitmap bitmap = null; + + // Store input handler instance to determine transformation. + private SphereSurfaceView surfaceView; + + /** + * Constructor. Will be set as renderer of the specified surface view. + * @param surfaceView SurfaceView which will own the renderer + */ + public Renderer(SphereSurfaceView surfaceView) { + if(surfaceView == null) + throw new NullPointerException("SurfaceView must not be null"); + + this.surfaceView = surfaceView; + this.surfaceView.setEGLContextClientVersion(2); + this.surfaceView.setRenderer(this); + } /** * Draws the frame. @@ -72,8 +97,19 @@ public class Renderer implements GLSurfaceView.Renderer { */ public void onDrawFrame(GL10 unused) { + // Upload texture, if necessary. + if(bitmap != null) { + glBindTexture(GL_TEXTURE_2D, textureID[0]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0); + + // Release bitmap for garbage collection. + bitmap = null; + } + // Update transformation matrix. - Matrix.multiplyMM(mvpMatrix, 0, viewMatrix, 0, modlMatrix, 0); + Matrix.multiplyMM(mvpMatrix, 0, surfaceView.getRotationMatrix(), 0, modlMatrix, 0); Matrix.multiplyMM(mvpMatrix, 0, projMatrix, 0, mvpMatrix, 0); // Draw the frame. @@ -87,12 +123,17 @@ public class Renderer implements GLSurfaceView.Renderer { glVertexAttribPointer(textureCoordinatesLocation, 2, GL_FLOAT, false, 2*4, sphere.getTextureCoordinatesBuffer()); glUniformMatrix4fv(mvpLocation, 1, false, mvpMatrix, 0); + glUniform1i(texLocation, 0); glDrawElements(GL_TRIANGLES, sphere.getIndexBuffer().capacity(), GL_UNSIGNED_SHORT, sphere.getIndexBuffer()); glDisableVertexAttribArray(textureCoordinatesLocation); glDisableVertexAttribArray(positionLocation); glUseProgram(0); + + int error = glGetError(); + if(error != GL_NO_ERROR) + Log.e("Renderer", "Error: " + getEGLErrorString(error)); } /** @@ -119,7 +160,7 @@ public class Renderer implements GLSurfaceView.Renderer { //TODO: (re)move tmp code Matrix.setIdentityM(modlMatrix, 0); - Matrix.translateM(modlMatrix, 0, 0, 0, 4.0f); + //Matrix.translateM(modlMatrix, 0, 0, 0, 4.0f); Matrix.setLookAtM(viewMatrix, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f); } @@ -129,16 +170,21 @@ public class Renderer implements GLSurfaceView.Renderer { public void initialize() { // Initialize sphere. - sphere = new Sphere(1.0f, 32, 32); // TODO: choose useful parameters. + sphere = new Sphere(10.0f, 32, 32); // TODO: choose useful parameters. // Set OpenGL state. - glClearColor(1.0f, 0.0f, 0.0f, 1.0f); + glClearColor(0.0f, 0.0f, 1.0f, 1.0f); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); + glCullFace(GL_FRONT); + glEnable(GL_TEXTURE_2D); + glActiveTexture(GL_TEXTURE0); // Build shader program. programID = buildProgram(DEFAULT_VERTEX_SHADER, DEFAULT_FRAGMENT_SHADER); + + // Generate texture. + glGenTextures(1, textureID, 0); } /** @@ -146,9 +192,18 @@ public class Renderer implements GLSurfaceView.Renderer { */ public void deinitialize() { sphere = null; + glDeleteTextures(1, textureID, 0); glDeleteProgram(programID); } + /** + * Uploads the image data of the given bitmap into the internal texture. + * @param bitmap Bitmap to be set as texture + */ + public void setBitmap(Bitmap bitmap) { + this.bitmap = bitmap; + } + /** * Builds a shader program given vertex and fragment shader soruce. * @param vertexSource The vertex shader source @@ -194,6 +249,10 @@ public class Renderer implements GLSurfaceView.Renderer { if (mvpLocation == -1) { throw new RuntimeException("Could not get uniform location for 'mvpMatrix'"); } + texLocation = glGetUniformLocation(program, "tex"); + if(texLocation == -1) { + throw new RuntimeException("Could not get uniform location for 'tex'"); + } return program; } diff --git a/app/src/main/java/de/trac/spherical/rendering/SphereSurfaceView.java b/app/src/main/java/de/trac/spherical/rendering/SphereSurfaceView.java new file mode 100644 index 0000000..b0be570 --- /dev/null +++ b/app/src/main/java/de/trac/spherical/rendering/SphereSurfaceView.java @@ -0,0 +1,103 @@ +package de.trac.spherical.rendering; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.opengl.GLSurfaceView; +import android.opengl.Matrix; +import android.view.MotionEvent; + +/** + * This SurfaceView implementation is the glue between the Renderer and any input event. + */ +public class SphereSurfaceView extends GLSurfaceView implements SensorEventListener { + + public static boolean USE_TOUCH = true; // TODO: determine dynamically + + private final float TOUCH_SCALE_FACTOR = 180.0f / 1080; + private float previousX; + private float previousY; + + // The actual rotation matrix determined by user input. + private final float rotationMatrix [] = new float[16]; + + // The following fields are used as cache. + private final float tmpMatrix[] = new float[9]; + private final float tmpVector[] = new float[3]; + + public SphereSurfaceView(Context context) { + super(context); + + Matrix.setIdentityM(rotationMatrix, 0); + + SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + Sensor accelerometer = manager.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0); + manager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + + if(!USE_TOUCH) + return true; + + float x = event.getX(); + float y = event.getY(); + + switch (event.getAction()) { + case MotionEvent.ACTION_MOVE: + + float dx = x - previousX; + float dy = y - previousY; + + if (y > getHeight() / 2) + dx = dx * -1 ; + + if (x < getWidth() / 2) + dy = dy * -1 ; + + Matrix.rotateM(rotationMatrix, 0, dy * TOUCH_SCALE_FACTOR, 1.0f, 0.0f, 0.0f); + Matrix.rotateM(rotationMatrix, 0, dx * TOUCH_SCALE_FACTOR, 0.0f, 1.0f, 0.0f); + } + + previousX = x; + previousY = y; + return true; + + } + + @Override + public void onAccuracyChanged(Sensor s, int arg1) { + // unused + } + + @Override + public void onSensorChanged(SensorEvent event) { + + if(USE_TOUCH) + return; + + SensorManager.getRotationMatrixFromVector(tmpMatrix, event.values); + SensorManager.getOrientation(tmpMatrix, tmpVector); + + for (int i = 0; i < tmpVector.length; i++) + tmpVector[i] = Math.round(Math.toDegrees(tmpVector[i])); + + synchronized (rotationMatrix) { + Matrix.setRotateEulerM(rotationMatrix, 0, -tmpVector[0], -tmpVector[1], -tmpVector[2]); + } + } + + /** + * Returns a matrix representing the devices rotation. + * This function is thread safe. + * @return rotation matrix according to device rotation + */ + public float [] getRotationMatrix() { + synchronized (rotationMatrix) { + return rotationMatrix; + } + } +}