mirror of
https://github.com/androidx/media.git
synced 2025-05-21 23:56:32 +08:00
Recenter SphericalSurfaceView view on startup
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214261973
This commit is contained in:
parent
c934ce744d
commit
e86d5efb21
@ -61,7 +61,7 @@ public final class FrameRotationQueue {
|
||||
* timestamp to {@code matrix}. Removes all older rotations and the returned one from the queue.
|
||||
* Does nothing if there is no such rotation.
|
||||
*
|
||||
* @param matrix A float array to hold the rotation matrix.
|
||||
* @param matrix The rotation matrix.
|
||||
* @param timestampUs The time in microseconds to query the rotation.
|
||||
* @return Whether a rotation matrix is copied to {@code matrix}.
|
||||
*/
|
||||
@ -83,8 +83,11 @@ public final class FrameRotationQueue {
|
||||
/**
|
||||
* Computes a recentering matrix from the given angle-axis rotation only accounting for yaw. Roll
|
||||
* and tilt will not be compensated.
|
||||
*
|
||||
* @param recenterMatrix The recenter matrix.
|
||||
* @param rotationMatrix The rotation matrix.
|
||||
*/
|
||||
private static void computeRecenterMatrix(float[] recenterMatrix, float[] rotationMatrix) {
|
||||
public static void computeRecenterMatrix(float[] recenterMatrix, float[] rotationMatrix) {
|
||||
// The re-centering matrix is computed as follows:
|
||||
// recenter.row(2) = temp.col(2).transpose();
|
||||
// recenter.row(0) = recenter.row(1).cross(recenter.row(2)).normalized();
|
||||
|
@ -30,7 +30,7 @@ import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
/** GL utility methods. */
|
||||
/*package*/ final class GlUtil {
|
||||
/* package */ final class GlUtil {
|
||||
private static final String TAG = "Spherical.Utils";
|
||||
|
||||
/** Class only contains static methods. */
|
||||
@ -79,7 +79,9 @@ import java.nio.IntBuffer;
|
||||
|
||||
int program = GLES20.glCreateProgram();
|
||||
GLES20.glAttachShader(program, vertexShader);
|
||||
GLES20.glDeleteShader(vertexShader);
|
||||
GLES20.glAttachShader(program, fragmentShader);
|
||||
GLES20.glDeleteShader(fragmentShader);
|
||||
|
||||
// Link and check for errors.
|
||||
GLES20.glLinkProgram(program);
|
||||
|
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.google.android.exoplayer2.ui.spherical;
|
||||
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.opengl.Matrix;
|
||||
import android.support.annotation.BinderThread;
|
||||
import android.view.Display;
|
||||
import android.view.Surface;
|
||||
import com.google.android.exoplayer2.video.spherical.FrameRotationQueue;
|
||||
|
||||
/**
|
||||
* Listens for orientation sensor events, converts event data to rotation matrix and roll value, and
|
||||
* notifies its own listeners.
|
||||
*/
|
||||
/* package */ final class OrientationListener implements SensorEventListener {
|
||||
/** A listener for orientation changes. */
|
||||
public interface Listener {
|
||||
/**
|
||||
* Called on device orientation change.
|
||||
*
|
||||
* @param deviceOrientationMatrix A 4x4 matrix defining device orientation.
|
||||
* @param deviceRoll Device roll value, in radians. The range of values is -π/2 to π/2.
|
||||
*/
|
||||
void onOrientationChange(float[] deviceOrientationMatrix, float deviceRoll);
|
||||
}
|
||||
|
||||
private final float[] deviceOrientationMatrix4x4 = new float[16];
|
||||
private final float[] tempMatrix4x4 = new float[16];
|
||||
private final float[] recenterMatrix4x4 = new float[16];
|
||||
private final float[] angles = new float[3];
|
||||
private final Display display;
|
||||
private final Listener[] listeners;
|
||||
private boolean recenterMatrixComputed;
|
||||
|
||||
public OrientationListener(Display display, Listener... listeners) {
|
||||
this.display = display;
|
||||
this.listeners = listeners;
|
||||
}
|
||||
|
||||
@Override
|
||||
@BinderThread
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
SensorManager.getRotationMatrixFromVector(deviceOrientationMatrix4x4, event.values);
|
||||
rotateAroundZ(deviceOrientationMatrix4x4, display.getRotation());
|
||||
float roll = extractRoll(deviceOrientationMatrix4x4);
|
||||
// Rotation vector sensor assumes Y is parallel to the ground.
|
||||
rotateYtoSky(deviceOrientationMatrix4x4);
|
||||
recenter(deviceOrientationMatrix4x4);
|
||||
notifyListeners(deviceOrientationMatrix4x4, roll);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
private void notifyListeners(float[] deviceOrientationMatrix, float roll) {
|
||||
for (Listener listener : listeners) {
|
||||
listener.onOrientationChange(deviceOrientationMatrix, roll);
|
||||
}
|
||||
}
|
||||
|
||||
private void recenter(float[] matrix) {
|
||||
if (!recenterMatrixComputed) {
|
||||
FrameRotationQueue.computeRecenterMatrix(recenterMatrix4x4, matrix);
|
||||
recenterMatrixComputed = true;
|
||||
}
|
||||
System.arraycopy(matrix, 0, tempMatrix4x4, 0, tempMatrix4x4.length);
|
||||
Matrix.multiplyMM(matrix, 0, tempMatrix4x4, 0, recenterMatrix4x4, 0);
|
||||
}
|
||||
|
||||
private float extractRoll(float[] matrix) {
|
||||
// Remapping is required since we need the calculated roll of the phone to be independent of the
|
||||
// phone's pitch & yaw.
|
||||
SensorManager.remapCoordinateSystem(
|
||||
matrix, SensorManager.AXIS_X, SensorManager.AXIS_MINUS_Z, tempMatrix4x4);
|
||||
SensorManager.getOrientation(tempMatrix4x4, angles);
|
||||
return angles[2];
|
||||
}
|
||||
|
||||
private void rotateAroundZ(float[] matrix, int rotation) {
|
||||
int xAxis;
|
||||
int yAxis;
|
||||
switch (rotation) {
|
||||
case Surface.ROTATION_270:
|
||||
xAxis = SensorManager.AXIS_MINUS_Y;
|
||||
yAxis = SensorManager.AXIS_X;
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
xAxis = SensorManager.AXIS_MINUS_X;
|
||||
yAxis = SensorManager.AXIS_MINUS_Y;
|
||||
break;
|
||||
case Surface.ROTATION_90:
|
||||
xAxis = SensorManager.AXIS_Y;
|
||||
yAxis = SensorManager.AXIS_MINUS_X;
|
||||
break;
|
||||
case Surface.ROTATION_0:
|
||||
return;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
System.arraycopy(matrix, 0, tempMatrix4x4, 0, tempMatrix4x4.length);
|
||||
SensorManager.remapCoordinateSystem(tempMatrix4x4, xAxis, yAxis, matrix);
|
||||
}
|
||||
|
||||
private static void rotateYtoSky(float[] matrix) {
|
||||
Matrix.rotateM(matrix, 0, 90, 1, 0, 0);
|
||||
}
|
||||
}
|
@ -30,10 +30,10 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
* thread when ready.
|
||||
*/
|
||||
@TargetApi(15)
|
||||
/*package*/ final class ProjectionRenderer {
|
||||
/* package */ final class ProjectionRenderer {
|
||||
|
||||
/** Defines the constants identifying the current eye type. */
|
||||
/*package*/ interface EyeType {
|
||||
/* package */ interface EyeType {
|
||||
/** Single eye in monocular rendering. */
|
||||
int MONOCULAR = 0;
|
||||
|
||||
|
@ -36,7 +36,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/** Renders a GL Scene. */
|
||||
/*package*/ class SceneRenderer implements VideoFrameMetadataListener, CameraMotionListener {
|
||||
/* package */ class SceneRenderer implements VideoFrameMetadataListener, CameraMotionListener {
|
||||
|
||||
private final AtomicBoolean frameAvailable;
|
||||
private final AtomicBoolean resetRotationAtNextFrame;
|
||||
|
@ -20,8 +20,6 @@ import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.opengl.GLES20;
|
||||
import android.opengl.GLSurfaceView;
|
||||
@ -79,11 +77,11 @@ public final class SphericalSurfaceView extends GLSurfaceView {
|
||||
// TODO Calculate this depending on surface size and field of view.
|
||||
private static final float PX_PER_DEGREES = 25;
|
||||
|
||||
/*package*/ static final float UPRIGHT_ROLL = (float) Math.PI;
|
||||
/* package */ static final float UPRIGHT_ROLL = (float) Math.PI;
|
||||
|
||||
private final SensorManager sensorManager;
|
||||
private final @Nullable Sensor orientationSensor;
|
||||
private final PhoneOrientationListener phoneOrientationListener;
|
||||
private final OrientationListener orientationListener;
|
||||
private final Renderer renderer;
|
||||
private final Handler mainHandler;
|
||||
private final TouchTracker touchTracker;
|
||||
@ -117,7 +115,7 @@ public final class SphericalSurfaceView extends GLSurfaceView {
|
||||
touchTracker = new TouchTracker(context, renderer, PX_PER_DEGREES);
|
||||
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
Display display = Assertions.checkNotNull(windowManager).getDefaultDisplay();
|
||||
phoneOrientationListener = new PhoneOrientationListener(display, touchTracker, renderer);
|
||||
orientationListener = new OrientationListener(display, touchTracker, renderer);
|
||||
|
||||
setEGLContextClientVersion(2);
|
||||
setRenderer(renderer);
|
||||
@ -173,14 +171,14 @@ public final class SphericalSurfaceView extends GLSurfaceView {
|
||||
super.onResume();
|
||||
if (orientationSensor != null) {
|
||||
sensorManager.registerListener(
|
||||
phoneOrientationListener, orientationSensor, SensorManager.SENSOR_DELAY_FASTEST);
|
||||
orientationListener, orientationSensor, SensorManager.SENSOR_DELAY_FASTEST);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
if (orientationSensor != null) {
|
||||
sensorManager.unregisterListener(phoneOrientationListener);
|
||||
sensorManager.unregisterListener(orientationListener);
|
||||
}
|
||||
super.onPause();
|
||||
}
|
||||
@ -229,82 +227,13 @@ public final class SphericalSurfaceView extends GLSurfaceView {
|
||||
}
|
||||
}
|
||||
|
||||
/** Detects sensor events and saves them as a matrix. */
|
||||
private static class PhoneOrientationListener implements SensorEventListener {
|
||||
private final float[] phoneInWorldSpaceMatrix = new float[16];
|
||||
private final float[] remappedPhoneMatrix = new float[16];
|
||||
private final float[] angles = new float[3];
|
||||
private final Display display;
|
||||
private final TouchTracker touchTracker;
|
||||
private final Renderer renderer;
|
||||
|
||||
public PhoneOrientationListener(Display display, TouchTracker touchTracker, Renderer renderer) {
|
||||
this.display = display;
|
||||
this.touchTracker = touchTracker;
|
||||
this.renderer = renderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
@BinderThread
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
SensorManager.getRotationMatrixFromVector(remappedPhoneMatrix, event.values);
|
||||
|
||||
// If we're not in upright portrait mode, remap the axes of the coordinate system according to
|
||||
// the display rotation.
|
||||
int xAxis;
|
||||
int yAxis;
|
||||
switch (display.getRotation()) {
|
||||
case Surface.ROTATION_270:
|
||||
xAxis = SensorManager.AXIS_MINUS_Y;
|
||||
yAxis = SensorManager.AXIS_X;
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
xAxis = SensorManager.AXIS_MINUS_X;
|
||||
yAxis = SensorManager.AXIS_MINUS_Y;
|
||||
break;
|
||||
case Surface.ROTATION_90:
|
||||
xAxis = SensorManager.AXIS_Y;
|
||||
yAxis = SensorManager.AXIS_MINUS_X;
|
||||
break;
|
||||
case Surface.ROTATION_0:
|
||||
default:
|
||||
xAxis = SensorManager.AXIS_X;
|
||||
yAxis = SensorManager.AXIS_Y;
|
||||
break;
|
||||
}
|
||||
SensorManager.remapCoordinateSystem(
|
||||
remappedPhoneMatrix, xAxis, yAxis, phoneInWorldSpaceMatrix);
|
||||
|
||||
// Extract the phone's roll and pass it on to touchTracker & renderer. Remapping is required
|
||||
// since we need the calculated roll of the phone to be independent of the phone's pitch &
|
||||
// yaw. Any operation that decomposes rotation to Euler angles needs to be performed
|
||||
// carefully.
|
||||
SensorManager.remapCoordinateSystem(
|
||||
phoneInWorldSpaceMatrix,
|
||||
SensorManager.AXIS_X,
|
||||
SensorManager.AXIS_MINUS_Z,
|
||||
remappedPhoneMatrix);
|
||||
SensorManager.getOrientation(remappedPhoneMatrix, angles);
|
||||
float roll = angles[2];
|
||||
touchTracker.setRoll(roll);
|
||||
|
||||
// Rotate from Android coordinates to OpenGL coordinates. Android's coordinate system
|
||||
// assumes Y points North and Z points to the sky. OpenGL has Y pointing up and Z pointing
|
||||
// toward the user.
|
||||
Matrix.rotateM(phoneInWorldSpaceMatrix, 0, 90, 1, 0, 0);
|
||||
renderer.setDeviceOrientation(phoneInWorldSpaceMatrix, roll);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard GL Renderer implementation. The notable code is the matrix multiplication in
|
||||
* onDrawFrame and updatePitchMatrix.
|
||||
*/
|
||||
// @VisibleForTesting
|
||||
/*package*/ class Renderer implements GLSurfaceView.Renderer, TouchTracker.Listener {
|
||||
/* package */ class Renderer
|
||||
implements GLSurfaceView.Renderer, TouchTracker.Listener, OrientationListener.Listener {
|
||||
private final SceneRenderer scene;
|
||||
private final float[] projectionMatrix = new float[16];
|
||||
|
||||
@ -362,8 +291,9 @@ public final class SphericalSurfaceView extends GLSurfaceView {
|
||||
}
|
||||
|
||||
/** Adjusts the GL camera's rotation based on device rotation. Runs on the sensor thread. */
|
||||
@Override
|
||||
@BinderThread
|
||||
public synchronized void setDeviceOrientation(float[] matrix, float deviceRoll) {
|
||||
public synchronized void onOrientationChange(float[] matrix, float deviceRoll) {
|
||||
System.arraycopy(matrix, 0, deviceOrientationMatrix, 0, deviceOrientationMatrix.length);
|
||||
this.deviceRoll = -deviceRoll;
|
||||
updatePitchMatrix();
|
||||
|
@ -45,16 +45,16 @@ import android.view.View;
|
||||
* Mesh as the user moves their finger. However, that requires quaternion interpolation.
|
||||
*/
|
||||
// @VisibleForTesting
|
||||
/*package*/ class TouchTracker extends GestureDetector.SimpleOnGestureListener
|
||||
implements View.OnTouchListener {
|
||||
/* package */ class TouchTracker extends GestureDetector.SimpleOnGestureListener
|
||||
implements View.OnTouchListener, OrientationListener.Listener {
|
||||
|
||||
/*package*/ interface Listener {
|
||||
/* package */ interface Listener {
|
||||
void onScrollChange(PointF scrollOffsetDegrees);
|
||||
}
|
||||
|
||||
// Touch input won't change the pitch beyond +/- 45 degrees. This reduces awkward situations
|
||||
// where the touch-based pitch and gyro-based pitch interact badly near the poles.
|
||||
/*package*/ static final float MAX_PITCH_DEGREES = 45;
|
||||
/* package */ static final float MAX_PITCH_DEGREES = 45;
|
||||
|
||||
// With every touch event, update the accumulated degrees offset by the new pixel amount.
|
||||
private final PointF previousTouchPointPx = new PointF();
|
||||
@ -132,8 +132,9 @@ import android.view.View;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@BinderThread
|
||||
public void setRoll(float roll) {
|
||||
public void onOrientationChange(float[] deviceOrientationMatrix, float roll) {
|
||||
// We compensate for roll by rotating in the opposite direction.
|
||||
this.roll = -roll;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user