mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Fix TouchTrackerTest
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=206304655
This commit is contained in:
parent
4eee474555
commit
30fecb71d2
4 changed files with 123 additions and 416 deletions
|
|
@ -34,9 +34,7 @@ import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.View;
|
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import com.google.android.exoplayer2.ui.spherical.Mesh.EyeType;
|
import com.google.android.exoplayer2.ui.spherical.Mesh.EyeType;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
@ -87,14 +85,10 @@ public final class SphericalSurfaceView extends GLSurfaceView {
|
||||||
private static final float Z_NEAR = .1f;
|
private static final float Z_NEAR = .1f;
|
||||||
private static final float Z_FAR = 100;
|
private static final float Z_FAR = 100;
|
||||||
|
|
||||||
// Arbitrary touch speed number. This should be tweaked so the scene smoothly follows the
|
// TODO Calculate this depending on surface size and field of view.
|
||||||
// finger or derived from DisplayMetrics.
|
|
||||||
private static final float PX_PER_DEGREES = 25;
|
private static final float PX_PER_DEGREES = 25;
|
||||||
// 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.
|
|
||||||
private static final float MAX_PITCH_DEGREES = 45;
|
|
||||||
|
|
||||||
private static final float UPRIGHT_ROLL = (float) Math.PI;
|
/*package*/ static final float UPRIGHT_ROLL = (float) Math.PI;
|
||||||
|
|
||||||
private final SensorManager sensorManager;
|
private final SensorManager sensorManager;
|
||||||
private final @Nullable Sensor orientationSensor;
|
private final @Nullable Sensor orientationSensor;
|
||||||
|
|
@ -126,7 +120,7 @@ public final class SphericalSurfaceView extends GLSurfaceView {
|
||||||
|
|
||||||
renderer = new Renderer();
|
renderer = new Renderer();
|
||||||
|
|
||||||
TouchTracker touchTracker = new TouchTracker(renderer);
|
TouchTracker touchTracker = new TouchTracker(renderer, PX_PER_DEGREES);
|
||||||
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||||
Display display = Assertions.checkNotNull(windowManager).getDefaultDisplay();
|
Display display = Assertions.checkNotNull(windowManager).getDefaultDisplay();
|
||||||
phoneOrientationListener = new PhoneOrientationListener(display, touchTracker, renderer);
|
phoneOrientationListener = new PhoneOrientationListener(display, touchTracker, renderer);
|
||||||
|
|
@ -291,97 +285,12 @@ public final class SphericalSurfaceView extends GLSurfaceView {
|
||||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
|
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic touch input system.
|
|
||||||
*
|
|
||||||
* <p>Mixing touch input and gyro input results in a complicated UI so this should be used
|
|
||||||
* carefully. This touch system implements a basic (X, Y) -> (yaw, pitch) transform. This works
|
|
||||||
* for basic UI but fails in edge cases where the user tries to drag scene up or down. There is no
|
|
||||||
* good UX solution for this. The least bad solution is to disable pitch manipulation and only let
|
|
||||||
* the user adjust yaw. This example tries to limit the awkwardness by restricting pitch
|
|
||||||
* manipulation to +/- 45 degrees.
|
|
||||||
*
|
|
||||||
* <p>It is also important to get the order of operations correct. To match what users expect,
|
|
||||||
* touch interaction manipulates the scene by rotating the world by the yaw offset and tilting the
|
|
||||||
* camera by the pitch offset. If the order of operations is incorrect, the sensors & touch
|
|
||||||
* rotations will have strange interactions. The roll of the phone is also tracked so that the x &
|
|
||||||
* y are correctly mapped to yaw & pitch no matter how the user holds their phone.
|
|
||||||
*
|
|
||||||
* <p>This class doesn't handle any scrolling inertia but Android's
|
|
||||||
* com.google.vr.sdk.widgets.common.TouchTracker.FlingGestureListener can be used with this code
|
|
||||||
* for a nicer UI. An even more advanced UI would reproject the user's touch point into 3D and
|
|
||||||
* drag the Mesh as the user moves their finger. However, that requires quaternion interpolation
|
|
||||||
* and is beyond the scope of this sample.
|
|
||||||
*/
|
|
||||||
// @VisibleForTesting
|
|
||||||
/*package*/ static class TouchTracker implements OnTouchListener {
|
|
||||||
// With every touch event, update the accumulated degrees offset by the new pixel amount.
|
|
||||||
private final PointF previousTouchPointPx = new PointF();
|
|
||||||
private final PointF accumulatedTouchOffsetDegrees = new PointF();
|
|
||||||
// The conversion from touch to yaw & pitch requires compensating for device roll. This is set
|
|
||||||
// on the sensor thread and read on the UI thread.
|
|
||||||
private volatile float roll;
|
|
||||||
|
|
||||||
private final Renderer renderer;
|
|
||||||
|
|
||||||
public TouchTracker(Renderer renderer) {
|
|
||||||
this.renderer = renderer;
|
|
||||||
roll = UPRIGHT_ROLL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts ACTION_MOVE events to pitch & yaw events while compensating for device roll.
|
|
||||||
*
|
|
||||||
* @return true if we handled the event
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean onTouch(View v, MotionEvent event) {
|
|
||||||
switch (event.getAction()) {
|
|
||||||
case MotionEvent.ACTION_DOWN:
|
|
||||||
// Initialize drag gesture.
|
|
||||||
previousTouchPointPx.set(event.getX(), event.getY());
|
|
||||||
return true;
|
|
||||||
case MotionEvent.ACTION_MOVE:
|
|
||||||
// Calculate the touch delta in screen space.
|
|
||||||
float touchX = (event.getX() - previousTouchPointPx.x) / PX_PER_DEGREES;
|
|
||||||
float touchY = (event.getY() - previousTouchPointPx.y) / PX_PER_DEGREES;
|
|
||||||
previousTouchPointPx.set(event.getX(), event.getY());
|
|
||||||
|
|
||||||
float r = roll; // Copy volatile state.
|
|
||||||
float cr = (float) Math.cos(r);
|
|
||||||
float sr = (float) Math.sin(r);
|
|
||||||
// To convert from screen space to the 3D space, we need to adjust the drag vector based
|
|
||||||
// on the roll of the phone. This is standard rotationMatrix(roll) * vector math but has
|
|
||||||
// an inverted y-axis due to the screen-space coordinates vs GL coordinates.
|
|
||||||
// Handle yaw.
|
|
||||||
accumulatedTouchOffsetDegrees.x -= cr * touchX - sr * touchY;
|
|
||||||
// Handle pitch and limit it to 45 degrees.
|
|
||||||
accumulatedTouchOffsetDegrees.y += sr * touchX + cr * touchY;
|
|
||||||
accumulatedTouchOffsetDegrees.y =
|
|
||||||
Math.max(
|
|
||||||
-MAX_PITCH_DEGREES, Math.min(MAX_PITCH_DEGREES, accumulatedTouchOffsetDegrees.y));
|
|
||||||
|
|
||||||
renderer.setPitchOffset(accumulatedTouchOffsetDegrees.y);
|
|
||||||
renderer.setYawOffset(accumulatedTouchOffsetDegrees.x);
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@BinderThread
|
|
||||||
public void setRoll(float roll) {
|
|
||||||
// We compensate for roll by rotating in the opposite direction.
|
|
||||||
this.roll = -roll;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standard GL Renderer implementation. The notable code is the matrix multiplication in
|
* Standard GL Renderer implementation. The notable code is the matrix multiplication in
|
||||||
* onDrawFrame and updatePitchMatrix.
|
* onDrawFrame and updatePitchMatrix.
|
||||||
*/
|
*/
|
||||||
// @VisibleForTesting
|
// @VisibleForTesting
|
||||||
/*package*/ class Renderer implements GLSurfaceView.Renderer {
|
/*package*/ class Renderer implements GLSurfaceView.Renderer, TouchTracker.Listener {
|
||||||
private final SceneRenderer scene;
|
private final SceneRenderer scene;
|
||||||
private final float[] projectionMatrix = new float[16];
|
private final float[] projectionMatrix = new float[16];
|
||||||
|
|
||||||
|
|
@ -464,17 +373,12 @@ public final class SphericalSurfaceView extends GLSurfaceView {
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set the pitch offset matrix. */
|
@Override
|
||||||
@UiThread
|
@UiThread
|
||||||
public synchronized void setPitchOffset(float pitchDegrees) {
|
public synchronized void onScrollChange(PointF scrollOffsetDegrees) {
|
||||||
touchPitch = pitchDegrees;
|
touchPitch = scrollOffsetDegrees.y;
|
||||||
updatePitchMatrix();
|
updatePitchMatrix();
|
||||||
}
|
Matrix.setRotateM(touchYawMatrix, 0, -scrollOffsetDegrees.x, 0, 1, 0);
|
||||||
|
|
||||||
/** Set the yaw offset matrix. */
|
|
||||||
@UiThread
|
|
||||||
public synchronized void setYawOffset(float yawDegrees) {
|
|
||||||
Matrix.setRotateM(touchYawMatrix, 0, -yawDegrees, 0, 1, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private float calculateFieldOfViewInYDirection(float aspect) {
|
private float calculateFieldOfViewInYDirection(float aspect) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* 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.graphics.PointF;
|
||||||
|
import android.support.annotation.BinderThread;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic touch input system.
|
||||||
|
*
|
||||||
|
* <p>Mixing touch input and gyro input results in a complicated UI so this should be used
|
||||||
|
* carefully. This touch system implements a basic (X, Y) -> (yaw, pitch) transform. This works for
|
||||||
|
* basic UI but fails in edge cases where the user tries to drag scene up or down. There is no good
|
||||||
|
* UX solution for this. The least bad solution is to disable pitch manipulation and only let the
|
||||||
|
* user adjust yaw. This example tries to limit the awkwardness by restricting pitch manipulation to
|
||||||
|
* +/- 45 degrees.
|
||||||
|
*
|
||||||
|
* <p>It is also important to get the order of operations correct. To match what users expect, touch
|
||||||
|
* interaction manipulates the scene by rotating the world by the yaw offset and tilting the camera
|
||||||
|
* by the pitch offset. If the order of operations is incorrect, the sensors & touch rotations will
|
||||||
|
* have strange interactions. The roll of the phone is also tracked so that the x & y are correctly
|
||||||
|
* mapped to yaw & pitch no matter how the user holds their phone.
|
||||||
|
*
|
||||||
|
* <p>This class doesn't handle any scrolling inertia but Android's
|
||||||
|
* com.google.vr.sdk.widgets.common.TouchTracker.FlingGestureListener can be used with this code for
|
||||||
|
* a nicer UI. An even more advanced UI would reproject the user's touch point into 3D and drag the
|
||||||
|
* Mesh as the user moves their finger. However, that requires quaternion interpolation.
|
||||||
|
*/
|
||||||
|
// @VisibleForTesting
|
||||||
|
/*package*/ class TouchTracker implements View.OnTouchListener {
|
||||||
|
|
||||||
|
/*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;
|
||||||
|
|
||||||
|
// With every touch event, update the accumulated degrees offset by the new pixel amount.
|
||||||
|
private final PointF previousTouchPointPx = new PointF();
|
||||||
|
private final PointF accumulatedTouchOffsetDegrees = new PointF();
|
||||||
|
|
||||||
|
private final Listener listener;
|
||||||
|
private final float pxPerDegrees;
|
||||||
|
// The conversion from touch to yaw & pitch requires compensating for device roll. This is set
|
||||||
|
// on the sensor thread and read on the UI thread.
|
||||||
|
private volatile float roll;
|
||||||
|
|
||||||
|
public TouchTracker(Listener listener, float pxPerDegrees) {
|
||||||
|
this.listener = listener;
|
||||||
|
this.pxPerDegrees = pxPerDegrees;
|
||||||
|
roll = SphericalSurfaceView.UPRIGHT_ROLL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts ACTION_MOVE events to pitch & yaw events while compensating for device roll.
|
||||||
|
*
|
||||||
|
* @return true if we handled the event
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
// Initialize drag gesture.
|
||||||
|
previousTouchPointPx.set(event.getX(), event.getY());
|
||||||
|
return true;
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
// Calculate the touch delta in screen space.
|
||||||
|
float touchX = (event.getX() - previousTouchPointPx.x) / pxPerDegrees;
|
||||||
|
float touchY = (event.getY() - previousTouchPointPx.y) / pxPerDegrees;
|
||||||
|
previousTouchPointPx.set(event.getX(), event.getY());
|
||||||
|
|
||||||
|
float r = roll; // Copy volatile state.
|
||||||
|
float cr = (float) Math.cos(r);
|
||||||
|
float sr = (float) Math.sin(r);
|
||||||
|
// To convert from screen space to the 3D space, we need to adjust the drag vector based
|
||||||
|
// on the roll of the phone. This is standard rotationMatrix(roll) * vector math but has
|
||||||
|
// an inverted y-axis due to the screen-space coordinates vs GL coordinates.
|
||||||
|
// Handle yaw.
|
||||||
|
accumulatedTouchOffsetDegrees.x -= cr * touchX - sr * touchY;
|
||||||
|
// Handle pitch and limit it to 45 degrees.
|
||||||
|
accumulatedTouchOffsetDegrees.y += sr * touchX + cr * touchY;
|
||||||
|
accumulatedTouchOffsetDegrees.y =
|
||||||
|
Math.max(
|
||||||
|
-MAX_PITCH_DEGREES, Math.min(MAX_PITCH_DEGREES, accumulatedTouchOffsetDegrees.y));
|
||||||
|
|
||||||
|
listener.onScrollChange(accumulatedTouchOffsetDegrees);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@BinderThread
|
||||||
|
public void setRoll(float roll) {
|
||||||
|
// We compensate for roll by rotating in the opposite direction.
|
||||||
|
this.roll = -roll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.robolectric.RobolectricTestRunner;
|
|
||||||
|
|
||||||
/** Tests for {@link Mesh}. */
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
|
||||||
public class MeshTest {
|
|
||||||
private static final float EPSILON = .00001f;
|
|
||||||
// This is a copy of Mesh.COORDS_PER_VERTEX which is private.
|
|
||||||
private static final int COORDS_PER_VERTEX = 7;
|
|
||||||
|
|
||||||
// Default 360 sphere.
|
|
||||||
private static final float RADIUS = 1;
|
|
||||||
private static final int LATITUDES = 12;
|
|
||||||
private static final int LONGITUDES = 24;
|
|
||||||
private static final float VERTICAL_FOV_DEGREES = 180;
|
|
||||||
private static final float HORIZONTAL_FOV_DEGREES = 360;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSphericalMesh() throws Exception {
|
|
||||||
// Only the first param is important in this test.
|
|
||||||
float[] data =
|
|
||||||
Mesh.createUvSphereVertexData(
|
|
||||||
RADIUS,
|
|
||||||
LATITUDES,
|
|
||||||
LONGITUDES,
|
|
||||||
VERTICAL_FOV_DEGREES,
|
|
||||||
HORIZONTAL_FOV_DEGREES,
|
|
||||||
Mesh.MEDIA_STEREO_TOP_BOTTOM);
|
|
||||||
|
|
||||||
assertThat(data.length).isGreaterThan(LATITUDES * LONGITUDES * COORDS_PER_VERTEX);
|
|
||||||
assertEquals(0, data.length % COORDS_PER_VERTEX);
|
|
||||||
|
|
||||||
for (int i = 0; i < data.length / COORDS_PER_VERTEX; ++i) {
|
|
||||||
float x = data[i * COORDS_PER_VERTEX + 0];
|
|
||||||
float y = data[i * COORDS_PER_VERTEX + 1];
|
|
||||||
float z = data[i * COORDS_PER_VERTEX + 2];
|
|
||||||
|
|
||||||
assertEquals(RADIUS, Math.sqrt(x * x + y * y + z * z), EPSILON);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMeshTextureCoordinates() throws Exception {
|
|
||||||
// 360 mono video.
|
|
||||||
float[] data =
|
|
||||||
Mesh.createUvSphereVertexData(
|
|
||||||
RADIUS,
|
|
||||||
LATITUDES,
|
|
||||||
LONGITUDES,
|
|
||||||
VERTICAL_FOV_DEGREES,
|
|
||||||
HORIZONTAL_FOV_DEGREES,
|
|
||||||
Mesh.MEDIA_MONOSCOPIC);
|
|
||||||
// There should be more vertices than quads.
|
|
||||||
assertThat(data.length).isGreaterThan(LATITUDES * LONGITUDES * COORDS_PER_VERTEX);
|
|
||||||
assertEquals(0, data.length % COORDS_PER_VERTEX);
|
|
||||||
|
|
||||||
for (int i = 0; i < data.length; i += COORDS_PER_VERTEX) {
|
|
||||||
// For monoscopic meshes, the (3, 4) and (5, 6) tex coords in each vertex should be the same.
|
|
||||||
assertEquals(data[i + 3], data[i + 5], EPSILON);
|
|
||||||
assertEquals(data[i + 4], data[i + 6], EPSILON);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hemispherical stereo where longitudes := latitudes. This is not exactly Wally format, but
|
|
||||||
// it's close.
|
|
||||||
data =
|
|
||||||
Mesh.createUvSphereVertexData(
|
|
||||||
RADIUS,
|
|
||||||
LATITUDES,
|
|
||||||
LATITUDES,
|
|
||||||
VERTICAL_FOV_DEGREES,
|
|
||||||
VERTICAL_FOV_DEGREES,
|
|
||||||
Mesh.MEDIA_STEREO_LEFT_RIGHT);
|
|
||||||
assertThat(data.length).isGreaterThan(LATITUDES * LATITUDES * COORDS_PER_VERTEX);
|
|
||||||
assertEquals(0, data.length % COORDS_PER_VERTEX);
|
|
||||||
|
|
||||||
for (int i = 0; i < data.length; i += COORDS_PER_VERTEX) {
|
|
||||||
// U coordinates should be on the left & right halves of the texture.
|
|
||||||
assertThat(data[i + 3]).isAtMost(.5f);
|
|
||||||
assertThat(data[i + 5]).isAtLeast(.5f);
|
|
||||||
// V coordinates should be the same.
|
|
||||||
assertEquals(data[i + 4], data[i + 6], EPSILON);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flat stereo.
|
|
||||||
data =
|
|
||||||
Mesh.createUvSphereVertexData(
|
|
||||||
RADIUS,
|
|
||||||
1,
|
|
||||||
1, // Single quad.
|
|
||||||
30,
|
|
||||||
60, // Approximate "cinematic" screen.
|
|
||||||
Mesh.MEDIA_STEREO_TOP_BOTTOM);
|
|
||||||
assertEquals(0, data.length % COORDS_PER_VERTEX);
|
|
||||||
|
|
||||||
for (int i = 0; i < data.length; i += COORDS_PER_VERTEX) {
|
|
||||||
// U coordinates should be the same
|
|
||||||
assertEquals(data[i + 3], data[i + 5], EPSILON);
|
|
||||||
// V coordinates should be on the top & bottom halves of the texture.
|
|
||||||
assertThat(data[i + 4]).isAtMost(.5f);
|
|
||||||
assertThat(data[i + 6]).isAtLeast(.5f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testArgumentValidation() {
|
|
||||||
checkIllegalArgumentException(0, 1, 1, 1, 1);
|
|
||||||
checkIllegalArgumentException(1, 0, 1, 1, 1);
|
|
||||||
checkIllegalArgumentException(1, 1, 0, 1, 1);
|
|
||||||
checkIllegalArgumentException(1, 1, 1, 0, 1);
|
|
||||||
checkIllegalArgumentException(1, 1, 1, 181, 1);
|
|
||||||
checkIllegalArgumentException(1, 1, 1, 1, 0);
|
|
||||||
checkIllegalArgumentException(1, 1, 1, 1, 361);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkIllegalArgumentException(
|
|
||||||
float radius,
|
|
||||||
int latitudes,
|
|
||||||
int longitudes,
|
|
||||||
float verticalFovDegrees,
|
|
||||||
float horizontalFovDegrees) {
|
|
||||||
try {
|
|
||||||
Mesh.createUvSphereVertexData(
|
|
||||||
radius,
|
|
||||||
latitudes,
|
|
||||||
longitudes,
|
|
||||||
verticalFovDegrees,
|
|
||||||
horizontalFovDegrees,
|
|
||||||
Mesh.MEDIA_MONOSCOPIC);
|
|
||||||
fail();
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// Do nothing. Expected.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import com.google.android.exoplayer2.ui.spherical.SphericalSurfaceView.TouchTracker;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.robolectric.RobolectricTestRunner;
|
|
||||||
|
|
||||||
/** Tests the interaction between the View's input (TouchTracker) and output (Renderer). */
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
|
||||||
public class SphericalSurfaceViewTouchTrackerTest {
|
|
||||||
private static final float EPSILON = 0.00001f;
|
|
||||||
private static final int SWIPE_PX = 100;
|
|
||||||
|
|
||||||
private static class MockRenderer extends SphericalSurfaceView.Renderer {
|
|
||||||
private float yaw;
|
|
||||||
private float pitch;
|
|
||||||
|
|
||||||
public MockRenderer() {
|
|
||||||
super(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setPitchOffset(float pitch) {
|
|
||||||
this.pitch = pitch;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setYawOffset(float yaw) {
|
|
||||||
this.yaw = yaw;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final MockRenderer mockRenderer = new MockRenderer();
|
|
||||||
|
|
||||||
private TouchTracker tracker;
|
|
||||||
|
|
||||||
private static void swipe(TouchTracker tracker, float x0, float y0, float x1, float y1) {
|
|
||||||
tracker.onTouch(null, MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, x0, y0, 0));
|
|
||||||
tracker.onTouch(null, MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, x1, y1, 0));
|
|
||||||
tracker.onTouch(null, MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, x1, y1, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
tracker = new TouchTracker(mockRenderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testTap() {
|
|
||||||
// Tap is a noop.
|
|
||||||
swipe(tracker, 0, 0, 0, 0);
|
|
||||||
assertThat(mockRenderer.yaw).isWithin(EPSILON).of(0);
|
|
||||||
assertThat(mockRenderer.pitch).isWithin(EPSILON).of(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBasicYaw() {
|
|
||||||
swipe(tracker, 0, 0, SWIPE_PX, 0);
|
|
||||||
assertThat(mockRenderer.yaw).isWithin(EPSILON).of(-SWIPE_PX / TouchTracker.PX_PER_DEGREES);
|
|
||||||
assertThat(mockRenderer.pitch).isWithin(EPSILON).of(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBigYaw() {
|
|
||||||
swipe(tracker, 0, 0, -10 * SWIPE_PX, 0);
|
|
||||||
assertThat(mockRenderer.yaw).isEqualTo(10 * SWIPE_PX / TouchTracker.PX_PER_DEGREES);
|
|
||||||
assertThat(mockRenderer.pitch).isWithin(EPSILON).of(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testYawUnaffectedByPitch() {
|
|
||||||
swipe(tracker, 0, 0, 0, SWIPE_PX);
|
|
||||||
assertThat(mockRenderer.yaw).isWithin(EPSILON).of(0);
|
|
||||||
|
|
||||||
swipe(tracker, 0, 0, SWIPE_PX, SWIPE_PX);
|
|
||||||
assertThat(mockRenderer.yaw).isWithin(EPSILON).of(-SWIPE_PX / TouchTracker.PX_PER_DEGREES);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBasicPitch() {
|
|
||||||
swipe(tracker, 0, 0, 0, SWIPE_PX);
|
|
||||||
assertThat(mockRenderer.yaw).isWithin(EPSILON).of(0);
|
|
||||||
assertThat(mockRenderer.pitch).isWithin(EPSILON).of(SWIPE_PX / TouchTracker.PX_PER_DEGREES);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPitchClipped() {
|
|
||||||
// Big reverse pitch should be clipped.
|
|
||||||
swipe(tracker, 0, 0, 0, -20 * SWIPE_PX);
|
|
||||||
assertThat(mockRenderer.yaw).isWithin(EPSILON).of(0);
|
|
||||||
assertThat(mockRenderer.pitch).isEqualTo(-TouchTracker.MAX_PITCH_DEGREES);
|
|
||||||
|
|
||||||
// Big forward pitch should be clipped.
|
|
||||||
swipe(tracker, 0, 0, 0, 50 * SWIPE_PX);
|
|
||||||
assertThat(mockRenderer.yaw).isWithin(EPSILON).of(0);
|
|
||||||
assertThat(mockRenderer.pitch).isEqualTo(TouchTracker.MAX_PITCH_DEGREES);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWithRoll90() {
|
|
||||||
tracker.setRoll((float) Math.toRadians(90));
|
|
||||||
|
|
||||||
// Y-axis should now control yaw.
|
|
||||||
swipe(tracker, 0, 0, 0, 2 * SWIPE_PX);
|
|
||||||
assertThat(mockRenderer.yaw).isWithin(EPSILON).of(-2 * SWIPE_PX / TouchTracker.PX_PER_DEGREES);
|
|
||||||
|
|
||||||
// X-axis should now control reverse pitch.
|
|
||||||
swipe(tracker, 0, 0, -3 * SWIPE_PX, 0);
|
|
||||||
assertThat(mockRenderer.pitch).isWithin(EPSILON).of(3 * SWIPE_PX / TouchTracker.PX_PER_DEGREES);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWithRoll180() {
|
|
||||||
tracker.setRoll((float) Math.toRadians(180));
|
|
||||||
|
|
||||||
// X-axis should now control reverse yaw.
|
|
||||||
swipe(tracker, 0, 0, -2 * SWIPE_PX, 0);
|
|
||||||
assertThat(mockRenderer.yaw).isWithin(EPSILON).of(-2 * SWIPE_PX / TouchTracker.PX_PER_DEGREES);
|
|
||||||
|
|
||||||
// Y-axis should now control reverse pitch.
|
|
||||||
swipe(tracker, 0, 0, 0, -3 * SWIPE_PX);
|
|
||||||
assertThat(mockRenderer.pitch).isWithin(EPSILON).of(3 * SWIPE_PX / TouchTracker.PX_PER_DEGREES);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWithRoll270() {
|
|
||||||
tracker.setRoll((float) Math.toRadians(270));
|
|
||||||
|
|
||||||
// Y-axis should now control reverse yaw.
|
|
||||||
swipe(tracker, 0, 0, 0, -2 * SWIPE_PX);
|
|
||||||
assertThat(mockRenderer.yaw).isWithin(EPSILON).of(-2 * SWIPE_PX / TouchTracker.PX_PER_DEGREES);
|
|
||||||
|
|
||||||
// X-axis should now control pitch.
|
|
||||||
swipe(tracker, 0, 0, 3 * SWIPE_PX, 0);
|
|
||||||
assertThat(mockRenderer.pitch).isWithin(EPSILON).of(3 * SWIPE_PX / TouchTracker.PX_PER_DEGREES);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue