Begin work on rotation via touch, rework file structure

This commit is contained in:
Simon Leistikow 2017-09-18 02:13:27 +02:00
parent 8d8b4dad4b
commit 3fbf5b67ec
5 changed files with 191 additions and 130 deletions

View File

@ -31,8 +31,7 @@ import java.io.InputStream;
import de.trac.spherical.parser.PhotoSphereMetadata; import de.trac.spherical.parser.PhotoSphereMetadata;
import de.trac.spherical.parser.PhotoSphereParser; import de.trac.spherical.parser.PhotoSphereParser;
import de.trac.spherical.rendering.Renderer; import de.trac.spherical.rendering.PhotoSphereSurfaceView;
import de.trac.spherical.rendering.SphereSurfaceView;
public class MainActivity extends AppCompatActivity { 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 static final int PERMISSION_REQUEST_READ_EXTERNAL_STORAGE = 387;
private SphereSurfaceView surfaceView; private PhotoSphereSurfaceView surfaceView;
private Renderer renderer;
private FloatingActionButton fab; private FloatingActionButton fab;
private Toolbar toolbar; private Toolbar toolbar;
@ -66,7 +64,7 @@ public class MainActivity extends AppCompatActivity {
fab.setOnClickListener(new View.OnClickListener() { fab.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
SphereSurfaceView.USE_TOUCH = !SphereSurfaceView.USE_TOUCH; PhotoSphereSurfaceView.USE_TOUCH = !PhotoSphereSurfaceView.USE_TOUCH;
displayUI(false); displayUI(false);
} }
}); });
@ -78,9 +76,8 @@ public class MainActivity extends AppCompatActivity {
// Initialize renderer and setup surface view. // Initialize renderer and setup surface view.
LinearLayout container = (LinearLayout) findViewById(R.id.container); LinearLayout container = (LinearLayout) findViewById(R.id.container);
surfaceView = new SphereSurfaceView(this); surfaceView = new PhotoSphereSurfaceView(this);
container.addView(surfaceView); container.addView(surfaceView);
renderer = new Renderer(surfaceView);
// Detect gestures like single taps. // Detect gestures like single taps.
final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { 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) { private void displayPhotoSphere(InputStream inputStream, PhotoSphereMetadata metadata) {
renderer.setBitmap(BitmapFactory.decodeStream(inputStream)); surfaceView.setBitmap(BitmapFactory.decodeStream(inputStream));
Log.d(TAG, "Display Photo Sphere!"); Log.d(TAG, "Display Photo PhotoSphere!");
} }
/** /**

View File

@ -9,14 +9,20 @@ import java.nio.ShortBuffer;
* This class is used to create native buffers holding vertices and * This class is used to create native buffers holding vertices and
* texture coordinates of a sphere with a given radius. * texture coordinates of a sphere with a given radius.
*/ */
public class Sphere { public class PhotoSphereGeometry {
// The following attributes make up our sphere. // The following attributes make up our sphere.
private FloatBuffer vertexBuffer; private FloatBuffer vertexBuffer;
private FloatBuffer textureCoordinatesBuffer; private FloatBuffer textureCoordinatesBuffer;
private ShortBuffer indexBuffer; 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; final int polyCountXPitch = polyCountX + 1;

View File

@ -3,6 +3,7 @@ package de.trac.spherical.rendering;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.opengl.GLSurfaceView; import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.opengl.GLUtils; import android.opengl.GLUtils;
import android.opengl.Matrix; import android.opengl.Matrix;
import android.util.Log; 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.glVertexAttribPointer;
import static android.opengl.GLES20.glViewport; import static android.opengl.GLES20.glViewport;
public class Renderer implements GLSurfaceView.Renderer { public class PhotoSphereRenderer implements GLSurfaceView.Renderer {
/** /**
* Default vertex shader. * Default vertex shader.
@ -92,14 +93,19 @@ public class Renderer implements GLSurfaceView.Renderer {
" gl_FragColor = texture2D(tex, uv);\n" + " gl_FragColor = texture2D(tex, uv);\n" +
"}\n"; "}\n";
// Store a sphere geometry as framework for the photo texture. // Sphere configuration.
private Sphere sphere = null; 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. // Store projection matrix.
private float projMatrix [] = new float [16]; private float projectionMatrix[] = new float [16];
// Store modelview matrix. // Store modelview matrix.
private float modlMatrix [] = new float [16]; private float modelMatrix[] = new float [16];
// Store view matrix. // Store view matrix.
private float viewMatrix [] = new float [16]; private float viewMatrix [] = new float [16];
@ -107,6 +113,9 @@ public class Renderer implements GLSurfaceView.Renderer {
// Store the model view projection matrix. // Store the model view projection matrix.
private float mvpMatrix [] = new float [16]; private float mvpMatrix [] = new float [16];
// This array contains the current view matrix {x, y, width, height).
private int view [] = null;
// Store shader name. // Store shader name.
private int programID; private int programID;
@ -123,13 +132,13 @@ public class Renderer implements GLSurfaceView.Renderer {
private Bitmap bitmap = null; private Bitmap bitmap = null;
// Store input handler instance to determine transformation. // Store input handler instance to determine transformation.
private SphereSurfaceView surfaceView; private PhotoSphereSurfaceView surfaceView;
/** /**
* Constructor. Will be set as renderer of the specified surface view. * Constructor. Will be set as renderer of the specified surface view.
* @param surfaceView SurfaceView which will own the renderer * @param surfaceView SurfaceView which will own the renderer
*/ */
public Renderer(SphereSurfaceView surfaceView) { public PhotoSphereRenderer(PhotoSphereSurfaceView surfaceView) {
if(surfaceView == null) if(surfaceView == null)
throw new NullPointerException("SurfaceView must not be null"); throw new NullPointerException("SurfaceView must not be null");
@ -157,23 +166,23 @@ public class Renderer implements GLSurfaceView.Renderer {
} }
// Update transformation matrix. // Update transformation matrix.
Matrix.multiplyMM(mvpMatrix, 0, surfaceView.getRotationMatrix(), 0, modlMatrix, 0); Matrix.multiplyMM(mvpMatrix, 0, surfaceView.getRotationMatrix(), 0, modelMatrix, 0);
Matrix.multiplyMM(mvpMatrix, 0, projMatrix, 0, mvpMatrix, 0); Matrix.multiplyMM(mvpMatrix, 0, projectionMatrix, 0, mvpMatrix, 0);
// Draw the frame. // Draw the frame.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(programID); glUseProgram(programID);
glEnableVertexAttribArray(positionLocation); glEnableVertexAttribArray(positionLocation);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, false, 3*4, sphere.getVertexBuffer()); glVertexAttribPointer(positionLocation, 3, GL_FLOAT, false, 3*4, photoSphereGeometry.getVertexBuffer());
glEnableVertexAttribArray(textureCoordinatesLocation); 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); glUniformMatrix4fv(mvpLocation, 1, false, mvpMatrix, 0);
glUniform1i(texLocation, 0); glUniform1i(texLocation, 0);
glBindTexture(GL_TEXTURE_2D, textureID[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); glBindTexture(GL_TEXTURE_2D, 0);
glDisableVertexAttribArray(textureCoordinatesLocation); glDisableVertexAttribArray(textureCoordinatesLocation);
@ -190,9 +199,10 @@ public class Renderer implements GLSurfaceView.Renderer {
* @param height new height of the surface * @param height new height of the surface
*/ */
public void onSurfaceChanged(GL10 unused, int width, int height) { public void onSurfaceChanged(GL10 unused, int width, int height) {
view = new int[]{0, 0, width, height};
glViewport(0, 0, width, height); glViewport(0, 0, width, height);
float ratio = (float) 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) { public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// Initialize sphere. // Initialize photoSphereGeometry.
sphere = new Sphere(10.0f, 32, 32); // TODO: choose useful parameters. photoSphereGeometry = new PhotoSphereGeometry(SPHERE_RADIUS, SPHERE_POLY_COUNT_X, SPHERE_POLY_COUNT_Y);
// Set OpenGL state. // Set OpenGL state.
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
@ -220,18 +230,31 @@ public class Renderer implements GLSurfaceView.Renderer {
glGenTextures(1, textureID, 0); glGenTextures(1, textureID, 0);
// Initialize matrices. // 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); 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 * @param bitmap Bitmap to be set as texture
*/ */
public void setBitmap(Bitmap bitmap) { public void requestBitmapUpload(Bitmap bitmap) {
this.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. * Builds a shader program given vertex and fragment shader soruce.
* @param vertexSource The vertex shader source * @param vertexSource The vertex shader source

View File

@ -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);
}
}

View File

@ -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;
}
}
}