From 3fbf5b67ece809b254ae87b4e49cf26acc6a8fb1 Mon Sep 17 00:00:00 2001 From: Simon Leistikow Date: Mon, 18 Sep 2017 02:13:27 +0200 Subject: [PATCH] Begin work on rotation via touch, rework file structure --- .../java/de/trac/spherical/MainActivity.java | 15 +- .../{Sphere.java => PhotoSphereGeometry.java} | 10 +- ...Renderer.java => PhotoSphereRenderer.java} | 59 +++++--- .../rendering/PhotoSphereSurfaceView.java | 136 ++++++++++++++++++ .../rendering/SphereSurfaceView.java | 101 ------------- 5 files changed, 191 insertions(+), 130 deletions(-) rename app/src/main/java/de/trac/spherical/rendering/{Sphere.java => PhotoSphereGeometry.java} (93%) rename app/src/main/java/de/trac/spherical/rendering/{Renderer.java => PhotoSphereRenderer.java} (82%) create mode 100644 app/src/main/java/de/trac/spherical/rendering/PhotoSphereSurfaceView.java delete 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 a30c53e..189081b 100644 --- a/app/src/main/java/de/trac/spherical/MainActivity.java +++ b/app/src/main/java/de/trac/spherical/MainActivity.java @@ -31,8 +31,7 @@ import java.io.InputStream; import de.trac.spherical.parser.PhotoSphereMetadata; import de.trac.spherical.parser.PhotoSphereParser; -import de.trac.spherical.rendering.Renderer; -import de.trac.spherical.rendering.SphereSurfaceView; +import de.trac.spherical.rendering.PhotoSphereSurfaceView; public class MainActivity extends AppCompatActivity { @@ -43,8 +42,7 @@ public class MainActivity extends AppCompatActivity { private static final int PERMISSION_REQUEST_READ_EXTERNAL_STORAGE = 387; - private SphereSurfaceView surfaceView; - private Renderer renderer; + private PhotoSphereSurfaceView surfaceView; private FloatingActionButton fab; private Toolbar toolbar; @@ -66,7 +64,7 @@ public class MainActivity extends AppCompatActivity { fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - SphereSurfaceView.USE_TOUCH = !SphereSurfaceView.USE_TOUCH; + PhotoSphereSurfaceView.USE_TOUCH = !PhotoSphereSurfaceView.USE_TOUCH; displayUI(false); } }); @@ -78,9 +76,8 @@ public class MainActivity extends AppCompatActivity { // Initialize renderer and setup surface view. LinearLayout container = (LinearLayout) findViewById(R.id.container); - surfaceView = new SphereSurfaceView(this); + surfaceView = new PhotoSphereSurfaceView(this); container.addView(surfaceView); - renderer = new Renderer(surfaceView); // Detect gestures like single taps. final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { @@ -261,8 +258,8 @@ public class MainActivity extends AppCompatActivity { } private void displayPhotoSphere(InputStream inputStream, PhotoSphereMetadata metadata) { - renderer.setBitmap(BitmapFactory.decodeStream(inputStream)); - Log.d(TAG, "Display Photo Sphere!"); + surfaceView.setBitmap(BitmapFactory.decodeStream(inputStream)); + Log.d(TAG, "Display Photo PhotoSphere!"); } /** diff --git a/app/src/main/java/de/trac/spherical/rendering/Sphere.java b/app/src/main/java/de/trac/spherical/rendering/PhotoSphereGeometry.java similarity index 93% rename from app/src/main/java/de/trac/spherical/rendering/Sphere.java rename to app/src/main/java/de/trac/spherical/rendering/PhotoSphereGeometry.java index df6b477..dea92eb 100644 --- a/app/src/main/java/de/trac/spherical/rendering/Sphere.java +++ b/app/src/main/java/de/trac/spherical/rendering/PhotoSphereGeometry.java @@ -9,14 +9,20 @@ import java.nio.ShortBuffer; * This class is used to create native buffers holding vertices and * texture coordinates of a sphere with a given radius. */ -public class Sphere { +public class PhotoSphereGeometry { // The following attributes make up our sphere. private FloatBuffer vertexBuffer; private FloatBuffer textureCoordinatesBuffer; private ShortBuffer indexBuffer; - public Sphere(float radius, int polyCountX, int polyCountY) { + /** + * Initializes the native buffers with a sphere based on the specified parameters. + * @param radius the sphere's radius + * @param polyCountX the number of polygons around the sphere in x direction + * @param polyCountY the number of polygons around the sphere in y direction + */ + public PhotoSphereGeometry(float radius, int polyCountX, int polyCountY) { final int polyCountXPitch = polyCountX + 1; diff --git a/app/src/main/java/de/trac/spherical/rendering/Renderer.java b/app/src/main/java/de/trac/spherical/rendering/PhotoSphereRenderer.java similarity index 82% rename from app/src/main/java/de/trac/spherical/rendering/Renderer.java rename to app/src/main/java/de/trac/spherical/rendering/PhotoSphereRenderer.java index 23eaccb..ec5bb09 100644 --- a/app/src/main/java/de/trac/spherical/rendering/Renderer.java +++ b/app/src/main/java/de/trac/spherical/rendering/PhotoSphereRenderer.java @@ -3,6 +3,7 @@ package de.trac.spherical.rendering; import android.graphics.Bitmap; import android.opengl.GLSurfaceView; +import android.opengl.GLU; import android.opengl.GLUtils; import android.opengl.Matrix; import android.util.Log; @@ -60,7 +61,7 @@ import static android.opengl.GLES20.glUseProgram; import static android.opengl.GLES20.glVertexAttribPointer; import static android.opengl.GLES20.glViewport; -public class Renderer implements GLSurfaceView.Renderer { +public class PhotoSphereRenderer implements GLSurfaceView.Renderer { /** * Default vertex shader. @@ -92,14 +93,19 @@ public class Renderer implements GLSurfaceView.Renderer { " gl_FragColor = texture2D(tex, uv);\n" + "}\n"; - // Store a sphere geometry as framework for the photo texture. - private Sphere sphere = null; + // Sphere configuration. + public static final int SPHERE_POLY_COUNT_X = 32; + public static final int SPHERE_POLY_COUNT_Y = 32; + public static final float SPHERE_RADIUS = 10.0f; + + // Store a photoSphereGeometry geometry as framework for the photo texture. + private PhotoSphereGeometry photoSphereGeometry = null; // Store projection matrix. - private float projMatrix [] = new float [16]; + private float projectionMatrix[] = new float [16]; // Store modelview matrix. - private float modlMatrix [] = new float [16]; + private float modelMatrix[] = new float [16]; // Store view matrix. private float viewMatrix [] = new float [16]; @@ -107,6 +113,9 @@ public class Renderer implements GLSurfaceView.Renderer { // Store the model view projection matrix. private float mvpMatrix [] = new float [16]; + // This array contains the current view matrix {x, y, width, height). + private int view [] = null; + // Store shader name. private int programID; @@ -123,13 +132,13 @@ public class Renderer implements GLSurfaceView.Renderer { private Bitmap bitmap = null; // Store input handler instance to determine transformation. - private SphereSurfaceView surfaceView; + private PhotoSphereSurfaceView surfaceView; /** * Constructor. Will be set as renderer of the specified surface view. * @param surfaceView SurfaceView which will own the renderer */ - public Renderer(SphereSurfaceView surfaceView) { + public PhotoSphereRenderer(PhotoSphereSurfaceView surfaceView) { if(surfaceView == null) throw new NullPointerException("SurfaceView must not be null"); @@ -157,23 +166,23 @@ public class Renderer implements GLSurfaceView.Renderer { } // Update transformation matrix. - Matrix.multiplyMM(mvpMatrix, 0, surfaceView.getRotationMatrix(), 0, modlMatrix, 0); - Matrix.multiplyMM(mvpMatrix, 0, projMatrix, 0, mvpMatrix, 0); + Matrix.multiplyMM(mvpMatrix, 0, surfaceView.getRotationMatrix(), 0, modelMatrix, 0); + Matrix.multiplyMM(mvpMatrix, 0, projectionMatrix, 0, mvpMatrix, 0); // Draw the frame. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glUseProgram(programID); glEnableVertexAttribArray(positionLocation); - glVertexAttribPointer(positionLocation, 3, GL_FLOAT, false, 3*4, sphere.getVertexBuffer()); + glVertexAttribPointer(positionLocation, 3, GL_FLOAT, false, 3*4, photoSphereGeometry.getVertexBuffer()); glEnableVertexAttribArray(textureCoordinatesLocation); - glVertexAttribPointer(textureCoordinatesLocation, 2, GL_FLOAT, false, 2*4, sphere.getTextureCoordinatesBuffer()); + glVertexAttribPointer(textureCoordinatesLocation, 2, GL_FLOAT, false, 2*4, photoSphereGeometry.getTextureCoordinatesBuffer()); glUniformMatrix4fv(mvpLocation, 1, false, mvpMatrix, 0); glUniform1i(texLocation, 0); glBindTexture(GL_TEXTURE_2D, textureID[0]); - glDrawElements(GL_TRIANGLES, sphere.getIndexBuffer().capacity(), GL_UNSIGNED_SHORT, sphere.getIndexBuffer()); + glDrawElements(GL_TRIANGLES, photoSphereGeometry.getIndexBuffer().capacity(), GL_UNSIGNED_SHORT, photoSphereGeometry.getIndexBuffer()); glBindTexture(GL_TEXTURE_2D, 0); glDisableVertexAttribArray(textureCoordinatesLocation); @@ -190,9 +199,10 @@ public class Renderer implements GLSurfaceView.Renderer { * @param height new height of the surface */ public void onSurfaceChanged(GL10 unused, int width, int height) { + view = new int[]{0, 0, width, height}; glViewport(0, 0, width, height); float ratio = (float) width / height; - Matrix.perspectiveM(projMatrix, 0, 45.0f, ratio, 0.25f, 128.0f); + Matrix.perspectiveM(projectionMatrix, 0, 45.0f, ratio, 0.25f, 128.0f); } /** @@ -203,8 +213,8 @@ public class Renderer implements GLSurfaceView.Renderer { */ public void onSurfaceCreated(GL10 unused, EGLConfig config) { - // Initialize sphere. - sphere = new Sphere(10.0f, 32, 32); // TODO: choose useful parameters. + // Initialize photoSphereGeometry. + photoSphereGeometry = new PhotoSphereGeometry(SPHERE_RADIUS, SPHERE_POLY_COUNT_X, SPHERE_POLY_COUNT_Y); // Set OpenGL state. glClearColor(0.0f, 0.0f, 0.0f, 1.0f); @@ -220,18 +230,31 @@ public class Renderer implements GLSurfaceView.Renderer { glGenTextures(1, textureID, 0); // Initialize matrices. - Matrix.setRotateM(modlMatrix, 0, 90, 1.0f, 0.0f, 0.0f); + Matrix.setRotateM(modelMatrix, 0, 90, 1.0f, 0.0f, 0.0f); Matrix.setLookAtM(viewMatrix, 0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f); } /** - * Uploads the image data of the given bitmap into the internal texture. + * Requests the renderer to uploads the image data of the given bitmap as texture. + * May not be done immediately. * @param bitmap Bitmap to be set as texture */ - public void setBitmap(Bitmap bitmap) { + public void requestBitmapUpload(Bitmap bitmap) { this.bitmap = bitmap; } + /** + * Takes screen coordinates and transforms them into world space + * @param x x coordinate + * @param y y coordinate + * @param outRayStart will be filled by the start position of the ray + * @param outRayDirection will be filled by the direction of the ray + */ + public void getRay(float x, float y, float [] outRayStart, float [] outRayDirection) { + GLU.gluUnProject(x, y, 0.0f, modelMatrix, 0, projectionMatrix, 0, view, 0, outRayStart, 0); + GLU.gluUnProject(x, y, 1.0f, modelMatrix, 0, projectionMatrix, 0, view, 0, outRayDirection, 0); + } + /** * Builds a shader program given vertex and fragment shader soruce. * @param vertexSource The vertex shader source diff --git a/app/src/main/java/de/trac/spherical/rendering/PhotoSphereSurfaceView.java b/app/src/main/java/de/trac/spherical/rendering/PhotoSphereSurfaceView.java new file mode 100644 index 0000000..0020601 --- /dev/null +++ b/app/src/main/java/de/trac/spherical/rendering/PhotoSphereSurfaceView.java @@ -0,0 +1,136 @@ +package de.trac.spherical.rendering; + +import android.content.Context; +import android.graphics.Bitmap; +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.os.Build; +import android.view.MotionEvent; + +/** + * This SurfaceView implementation is the glue between the PhotoSphereRenderer and any input event. + */ +public class PhotoSphereSurfaceView extends GLSurfaceView implements SensorEventListener { + + public static boolean USE_TOUCH = false; // TODO: determine dynamically + + // The actual rotation matrix determined by user input. + private final float rotationMatrix [] = new float[16]; + + // These vectors Are used for ray determination. + private final float rayStart [] = new float[4]; + private final float rayDirection [] = new float[4]; + + // The renderer used by this view. + private PhotoSphereRenderer renderer; + + /** + * Constructor. Initializes Renderer. + * @param context application context + */ + public PhotoSphereSurfaceView(Context context) { + super(context); + + // Initialize transformation matrix. + Matrix.setIdentityM(rotationMatrix, 0); + + // Initialize sensors. + SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + Sensor sensor; + if (Build.VERSION.SDK_INT >= 18) { + sensor = manager.getSensorList(Sensor.TYPE_GAME_ROTATION_VECTOR).get(0); + } else { + sensor = manager.getSensorList(Sensor.TYPE_ROTATION_VECTOR).get(0); + } + manager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME); + + // Initialize renderer. + renderer = new PhotoSphereRenderer(this); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + + if(!USE_TOUCH) + return true; + + switch (event.getAction()) { + case MotionEvent.ACTION_MOVE: + + // Retrieve ray in world space. + renderer.getRay(event.getX(), event.getY(), rayStart, rayDirection); + + // Solve quadric equation. + float a = 0.0f, b = 0.0f, c = 0.0f; + for(int i=0; i<3; i++) { + a += rayDirection[i] * rayDirection[i]; + b += rayDirection[i] * 2.0f * (rayStart[i]); // Sphere center at origin. + c += rayStart[i]*rayStart[i]; + } + c -= PhotoSphereRenderer.SPHERE_RADIUS*PhotoSphereRenderer.SPHERE_RADIUS; + float D = b*b-4.0f*a*c; + + // Since the conditions are + if(D < 0) { + throw new RuntimeException("Ray must intersect with sphere, check camera position"); + } + + D = (float) Math.sqrt(D); + + // Calculate intersection point p. + float t = -0.5f*(b+D)/a; + float px = rayStart[0] + t*rayDirection[0]; + float py = rayStart[1] + t*rayDirection[1]; + float pz = rayStart[2] + t*rayDirection[2]; + + // Calculate angles. + //float angleX = (float) Math.toDegrees(Math.atan2(py, px)); + //float angleY = (float) Math.toDegrees(Math.acos(pz/Matrix.length(px, py, pz))); + + synchronized (rotationMatrix) { + Matrix.setLookAtM(rotationMatrix, 0, 0.0f, 0.0f, 0.0f, px, py, pz, 1.0f, 0.0f, 0.0f); + } + } + + return true; + } + + @Override + public void onAccuracyChanged(Sensor s, int arg1) { + // unused + } + + @Override + public void onSensorChanged(SensorEvent event) { + + if(USE_TOUCH) + return; + + synchronized (rotationMatrix) { + SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values); + } + } + + /** + * 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; + } + } + + /** + * Sets the bitmap to be rendered by the internal renderer. + * @param bitmap bitmap to be rendered + */ + public void setBitmap(Bitmap bitmap) { + renderer.requestBitmapUpload(bitmap); + } +} diff --git a/app/src/main/java/de/trac/spherical/rendering/SphereSurfaceView.java b/app/src/main/java/de/trac/spherical/rendering/SphereSurfaceView.java deleted file mode 100644 index 2ed8c28..0000000 --- a/app/src/main/java/de/trac/spherical/rendering/SphereSurfaceView.java +++ /dev/null @@ -1,101 +0,0 @@ -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.os.Build; -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 = false; // 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]; - - public SphereSurfaceView(Context context) { - super(context); - - Matrix.setIdentityM(rotationMatrix, 0); - - SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - Sensor sensor; - if (Build.VERSION.SDK_INT >= 18) { - sensor = manager.getSensorList(Sensor.TYPE_GAME_ROTATION_VECTOR).get(0); - } else { - sensor = manager.getSensorList(Sensor.TYPE_ROTATION_VECTOR).get(0); - } - manager.registerListener(this, sensor, 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 ; - - synchronized (rotationMatrix) { - 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; - - synchronized (rotationMatrix) { - SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values); - } - } - - /** - * 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; - } - } -}