Commit 5e538a2a by olly Committed by Oliver Woodman

Clean up GvrPlayerActivity

PiperOrigin-RevId: 274845045
parent 34c53176
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer2.ext.gvr; package com.google.android.exoplayer2.ext.gvr;
import android.content.Context; import android.content.Context;
...@@ -21,8 +20,6 @@ import android.content.Intent; ...@@ -21,8 +20,6 @@ import android.content.Intent;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
import android.opengl.Matrix; import android.opengl.Matrix;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.ContextThemeWrapper; import android.view.ContextThemeWrapper;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.Surface; import android.view.Surface;
...@@ -47,6 +44,7 @@ import com.google.vr.sdk.base.HeadTransform; ...@@ -47,6 +44,7 @@ import com.google.vr.sdk.base.HeadTransform;
import com.google.vr.sdk.base.Viewport; import com.google.vr.sdk.base.Viewport;
import com.google.vr.sdk.controller.Controller; import com.google.vr.sdk.controller.Controller;
import com.google.vr.sdk.controller.ControllerManager; import com.google.vr.sdk.controller.ControllerManager;
import com.google.vr.sdk.controller.Orientation;
import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLConfig;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...@@ -58,46 +56,36 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -58,46 +56,36 @@ public abstract class GvrPlayerActivity extends GvrActivity {
private static final int EXIT_FROM_VR_REQUEST_CODE = 42; private static final int EXIT_FROM_VR_REQUEST_CODE = 42;
private final Handler mainHandler;
@Nullable private Player player; @Nullable private Player player;
private @MonotonicNonNull GlViewGroup glView;
private @MonotonicNonNull ControllerManager controllerManager; private @MonotonicNonNull ControllerManager controllerManager;
private @MonotonicNonNull SurfaceTexture surfaceTexture; private @MonotonicNonNull SurfaceTexture surfaceTexture;
private @MonotonicNonNull Surface surface; private @MonotonicNonNull Surface surface;
private @MonotonicNonNull SceneRenderer scene; private @MonotonicNonNull SceneRenderer sceneRenderer;
private @MonotonicNonNull PlayerControlView playerControl; private @MonotonicNonNull PlayerControlView playerControlView;
public GvrPlayerActivity() {
mainHandler = new Handler(Looper.getMainLooper());
}
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setScreenAlwaysOn(true); setScreenAlwaysOn(true);
GvrView gvrView = new GvrView(this); GvrView gvrView = new GvrView(/* context= */ this);
// Since videos typically have fewer pixels per degree than the phones, reducing the render gvrView.setRenderTargetScale(getRenderTargetScale());
// target scaling factor reduces the work required to render the scene.
gvrView.setRenderTargetScale(.5f);
// If a custom theme isn't specified, the Context's theme is used. For VR Activities, this is // If a custom theme isn't specified, the Context's theme is used. For VR Activities, this is
// the old Android default theme rather than a modern theme. Override this with a custom theme. // the old Android default theme rather than a modern theme. Override this with a custom theme.
Context theme = new ContextThemeWrapper(this, R.style.VrTheme); Context theme = new ContextThemeWrapper(this, R.style.ExoVrTheme);
glView = new GlViewGroup(theme, R.layout.vr_ui); GlViewGroup glViewGroup = new GlViewGroup(theme, R.layout.exo_vr_ui);
playerControl = Assertions.checkNotNull(glView.findViewById(R.id.controller)); playerControlView = Assertions.checkNotNull(glViewGroup.findViewById(R.id.controller));
playerControl.setShowVrButton(true); playerControlView.setShowVrButton(true);
playerControl.setVrButtonListener(v -> exit()); playerControlView.setVrButtonListener(v -> exit());
sceneRenderer = new SceneRenderer();
PointerRenderer pointerRenderer = new PointerRenderer(); PointerRenderer pointerRenderer = new PointerRenderer();
scene = new SceneRenderer(); Renderer renderer = new Renderer(sceneRenderer, pointerRenderer, glViewGroup);
Renderer renderer = new Renderer(scene, glView, pointerRenderer);
// Attach glView to gvrView in order to properly handle UI events.
gvrView.addView(glView, 0);
// Attach glViewGroup to gvrView in order to properly handle UI events.
gvrView.addView(glViewGroup);
// Standard GvrView configuration // Standard GvrView configuration
gvrView.setEGLConfigChooser( gvrView.setEGLConfigChooser(
8, 8, 8, 8, // RGBA bits. 8, 8, 8, 8, // RGBA bits.
...@@ -106,15 +94,13 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -106,15 +94,13 @@ public abstract class GvrPlayerActivity extends GvrActivity {
gvrView.setRenderer(renderer); gvrView.setRenderer(renderer);
setContentView(gvrView); setContentView(gvrView);
// Most Daydream phones can render a 4k video at 60fps in sustained performance mode. These
// options can be tweaked along with the render target scale.
if (gvrView.setAsyncReprojectionEnabled(true)) { if (gvrView.setAsyncReprojectionEnabled(true)) {
AndroidCompat.setSustainedPerformanceMode(this, true); AndroidCompat.setSustainedPerformanceMode(/* activity= */ this, true);
} }
// Handle the user clicking on the 'X' in the top left corner. Since this is done when the user // Handle the user clicking on the 'X' in the top left corner. Since this is done when the user
// has taken the headset out of VR, it should launch the app's exit flow directly rather than // has taken the headset out of VR, it should launch the app's exit flow directly rather than
// using the transition flow. // using Daydream's exit transition.
gvrView.setOnCloseButtonListener(this::finish); gvrView.setOnCloseButtonListener(this::finish);
ControllerManager.EventListener listener = ControllerManager.EventListener listener =
...@@ -126,15 +112,14 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -126,15 +112,14 @@ public abstract class GvrPlayerActivity extends GvrActivity {
@Override @Override
public void onRecentered() { public void onRecentered() {
// TODO if in cardboard mode call gvrView.recenterHeadTracker(); // TODO: If in cardboard mode call gvrView.recenterHeadTracker().
glView.post(() -> Util.castNonNull(playerControl).show()); runOnUiThread(() -> Util.castNonNull(playerControlView).show());
} }
}; };
controllerManager = new ControllerManager(this, listener); controllerManager = new ControllerManager(this, listener);
Controller controller = controllerManager.getController(); Controller controller = controllerManager.getController();
ControllerEventListener controllerEventListener = ControllerEventListener controllerEventListener =
new ControllerEventListener(controller, pointerRenderer, glView); new ControllerEventListener(controller, pointerRenderer, glViewGroup);
controller.setEventListener(controllerEventListener); controller.setEventListener(controllerEventListener);
} }
...@@ -144,7 +129,7 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -144,7 +129,7 @@ public abstract class GvrPlayerActivity extends GvrActivity {
* @param newPlayer The {@link Player} to use, or {@code null} to detach the current player. * @param newPlayer The {@link Player} to use, or {@code null} to detach the current player.
*/ */
protected void setPlayer(@Nullable Player newPlayer) { protected void setPlayer(@Nullable Player newPlayer) {
Assertions.checkNotNull(scene); Assertions.checkNotNull(sceneRenderer);
if (player == newPlayer) { if (player == newPlayer) {
return; return;
} }
...@@ -154,20 +139,20 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -154,20 +139,20 @@ public abstract class GvrPlayerActivity extends GvrActivity {
if (surface != null) { if (surface != null) {
videoComponent.clearVideoSurface(surface); videoComponent.clearVideoSurface(surface);
} }
videoComponent.clearVideoFrameMetadataListener(scene); videoComponent.clearVideoFrameMetadataListener(sceneRenderer);
videoComponent.clearCameraMotionListener(scene); videoComponent.clearCameraMotionListener(sceneRenderer);
} }
} }
player = newPlayer; player = newPlayer;
if (player != null) { if (player != null) {
Player.VideoComponent videoComponent = player.getVideoComponent(); Player.VideoComponent videoComponent = player.getVideoComponent();
if (videoComponent != null) { if (videoComponent != null) {
videoComponent.setVideoFrameMetadataListener(scene); videoComponent.setVideoFrameMetadataListener(sceneRenderer);
videoComponent.setCameraMotionListener(scene); videoComponent.setCameraMotionListener(sceneRenderer);
videoComponent.setVideoSurface(surface); videoComponent.setVideoSurface(surface);
} }
} }
Assertions.checkNotNull(playerControl).setPlayer(player); Assertions.checkNotNull(playerControlView).setPlayer(player);
} }
/** /**
...@@ -177,7 +162,19 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -177,7 +162,19 @@ public abstract class GvrPlayerActivity extends GvrActivity {
* @param stereoMode A {@link C.StereoMode} value. * @param stereoMode A {@link C.StereoMode} value.
*/ */
protected void setDefaultStereoMode(@C.StereoMode int stereoMode) { protected void setDefaultStereoMode(@C.StereoMode int stereoMode) {
Assertions.checkNotNull(scene).setDefaultStereoMode(stereoMode); Assertions.checkNotNull(sceneRenderer).setDefaultStereoMode(stereoMode);
}
/**
* Returns the render target scale passed to {@link GvrView#setRenderTargetScale(float)}. Since
* videos typically have fewer pixels per degree than the phone displays, the target can normally
* be lower than 1 to reduce the amount of work required to render the scene. The default value is
* 0.5.
*
* @return The render target scale passed to {@link GvrView#setRenderTargetScale(float)}.
*/
protected float getRenderTargetScale() {
return 0.5f;
} }
@CallSuper @CallSuper
...@@ -210,12 +207,12 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -210,12 +207,12 @@ public abstract class GvrPlayerActivity extends GvrActivity {
/** Tries to exit gracefully from VR using a VR transition dialog. */ /** Tries to exit gracefully from VR using a VR transition dialog. */
@SuppressWarnings("nullness:argument.type.incompatible") @SuppressWarnings("nullness:argument.type.incompatible")
protected void exit() { protected void exit() {
// This needs to use GVR's exit transition to avoid disorienting the user. DaydreamApi daydreamApi = DaydreamApi.create(this);
DaydreamApi api = DaydreamApi.create(this); if (daydreamApi != null) {
if (api != null) { // Use Daydream's exit transition to avoid disorienting the user. This will cause
api.exitFromVr(this, EXIT_FROM_VR_REQUEST_CODE, null); // onActivityResult to be called.
// Eventually, the Activity's onActivityResult will be called. daydreamApi.exitFromVr(/* activity= */ this, EXIT_FROM_VR_REQUEST_CODE, /* intent= */ null);
api.close(); daydreamApi.close();
} else { } else {
finish(); finish();
} }
...@@ -224,16 +221,16 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -224,16 +221,16 @@ public abstract class GvrPlayerActivity extends GvrActivity {
/** Toggles PlayerControl visibility. */ /** Toggles PlayerControl visibility. */
@UiThread @UiThread
protected void togglePlayerControlVisibility() { protected void togglePlayerControlVisibility() {
if (Assertions.checkNotNull(playerControl).isVisible()) { if (Assertions.checkNotNull(playerControlView).isVisible()) {
playerControl.hide(); playerControlView.hide();
} else { } else {
playerControl.show(); playerControlView.show();
} }
} }
// Called on GL thread.
private void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture) { private void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture) {
mainHandler.post( // Called on the GL thread. Post to the main thread.
runOnUiThread(
() -> { () -> {
SurfaceTexture oldSurfaceTexture = this.surfaceTexture; SurfaceTexture oldSurfaceTexture = this.surfaceTexture;
Surface oldSurface = this.surface; Surface oldSurface = this.surface;
...@@ -260,18 +257,20 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -260,18 +257,20 @@ public abstract class GvrPlayerActivity extends GvrActivity {
} }
private class Renderer implements GvrView.StereoRenderer { private class Renderer implements GvrView.StereoRenderer {
private static final float Z_NEAR = .1f; private static final float Z_NEAR = 0.1f;
private static final float Z_FAR = 100; private static final float Z_FAR = 100;
private final float[] viewProjectionMatrix = new float[16]; private final SceneRenderer sceneRenderer;
private final SceneRenderer scene;
private final GlViewGroup glView;
private final PointerRenderer pointerRenderer; private final PointerRenderer pointerRenderer;
private final GlViewGroup glViewGroup;
private final float[] viewProjectionMatrix;
public Renderer(SceneRenderer scene, GlViewGroup glView, PointerRenderer pointerRenderer) { public Renderer(
this.scene = scene; SceneRenderer sceneRenderer, PointerRenderer pointerRenderer, GlViewGroup glViewGroup) {
this.glView = glView; this.sceneRenderer = sceneRenderer;
this.pointerRenderer = pointerRenderer; this.pointerRenderer = pointerRenderer;
this.glViewGroup = glViewGroup;
viewProjectionMatrix = new float[16];
} }
@Override @Override
...@@ -281,9 +280,9 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -281,9 +280,9 @@ public abstract class GvrPlayerActivity extends GvrActivity {
public void onDrawEye(Eye eye) { public void onDrawEye(Eye eye) {
Matrix.multiplyMM( Matrix.multiplyMM(
viewProjectionMatrix, 0, eye.getPerspective(Z_NEAR, Z_FAR), 0, eye.getEyeView(), 0); viewProjectionMatrix, 0, eye.getPerspective(Z_NEAR, Z_FAR), 0, eye.getEyeView(), 0);
scene.drawFrame(viewProjectionMatrix, eye.getType() == Eye.Type.RIGHT); sceneRenderer.drawFrame(viewProjectionMatrix, eye.getType() == Eye.Type.RIGHT);
if (glView.isVisible()) { if (glViewGroup.isVisible()) {
glView.getRenderer().draw(viewProjectionMatrix); glViewGroup.getRenderer().draw(viewProjectionMatrix);
pointerRenderer.draw(viewProjectionMatrix); pointerRenderer.draw(viewProjectionMatrix);
} }
} }
...@@ -293,8 +292,8 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -293,8 +292,8 @@ public abstract class GvrPlayerActivity extends GvrActivity {
@Override @Override
public void onSurfaceCreated(EGLConfig config) { public void onSurfaceCreated(EGLConfig config) {
onSurfaceTextureAvailable(scene.init()); onSurfaceTextureAvailable(sceneRenderer.init());
glView.getRenderer().init(); glViewGroup.getRenderer().init();
pointerRenderer.init(); pointerRenderer.init();
} }
...@@ -303,9 +302,9 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -303,9 +302,9 @@ public abstract class GvrPlayerActivity extends GvrActivity {
@Override @Override
public void onRendererShutdown() { public void onRendererShutdown() {
glView.getRenderer().shutdown(); glViewGroup.getRenderer().shutdown();
pointerRenderer.shutdown(); pointerRenderer.shutdown();
scene.shutdown(); sceneRenderer.shutdown();
} }
} }
...@@ -313,16 +312,16 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -313,16 +312,16 @@ public abstract class GvrPlayerActivity extends GvrActivity {
private final Controller controller; private final Controller controller;
private final PointerRenderer pointerRenderer; private final PointerRenderer pointerRenderer;
private final GlViewGroup glView; private final GlViewGroup glViewGroup;
private final float[] controllerOrientationMatrix; private final float[] controllerOrientationMatrix;
private boolean clickButtonDown; private boolean clickButtonDown;
private boolean appButtonDown; private boolean appButtonDown;
public ControllerEventListener( public ControllerEventListener(
Controller controller, PointerRenderer pointerRenderer, GlViewGroup glView) { Controller controller, PointerRenderer pointerRenderer, GlViewGroup glViewGroup) {
this.controller = controller; this.controller = controller;
this.pointerRenderer = pointerRenderer; this.pointerRenderer = pointerRenderer;
this.glView = glView; this.glViewGroup = glViewGroup;
controllerOrientationMatrix = new float[16]; controllerOrientationMatrix = new float[16];
} }
...@@ -330,7 +329,8 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -330,7 +329,8 @@ public abstract class GvrPlayerActivity extends GvrActivity {
@BinderThread @BinderThread
public void onUpdate() { public void onUpdate() {
controller.update(); controller.update();
controller.orientation.toRotationMatrix(controllerOrientationMatrix); Orientation orientation = controller.orientation;
orientation.toRotationMatrix(controllerOrientationMatrix);
pointerRenderer.setControllerOrientation(controllerOrientationMatrix); pointerRenderer.setControllerOrientation(controllerOrientationMatrix);
if (clickButtonDown || controller.clickButtonState) { if (clickButtonDown || controller.clickButtonState) {
...@@ -341,18 +341,19 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -341,18 +341,19 @@ public abstract class GvrPlayerActivity extends GvrActivity {
} else { } else {
action = MotionEvent.ACTION_MOVE; action = MotionEvent.ACTION_MOVE;
} }
glView.post( float[] yawPitchRoll = orientation.toYawPitchRollRadians(new float[3]);
() -> { runOnUiThread(() -> dispatchClick(action, yawPitchRoll[0], yawPitchRoll[1]));
float[] angles = controller.orientation.toYawPitchRollRadians(new float[3]);
boolean clickedOnView = glView.simulateClick(action, angles[0], angles[1]);
if (action == MotionEvent.ACTION_DOWN && !clickedOnView) {
togglePlayerControlVisibility();
}
});
} else if (!appButtonDown && controller.appButtonState) { } else if (!appButtonDown && controller.appButtonState) {
glView.post(GvrPlayerActivity.this::togglePlayerControlVisibility); runOnUiThread(GvrPlayerActivity.this::togglePlayerControlVisibility);
} }
appButtonDown = controller.appButtonState; appButtonDown = controller.appButtonState;
} }
private void dispatchClick(int action, float yaw, float pitch) {
boolean clickedOnView = glViewGroup.simulateClick(action, yaw, pitch);
if (action == MotionEvent.ACTION_DOWN && !clickedOnView) {
togglePlayerControlVisibility();
}
}
} }
} }
...@@ -13,16 +13,8 @@ ...@@ -13,16 +13,8 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<merge xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.exoplayer2.ui.PlayerControlView
xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/video_ui_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/black"
android:orientation="horizontal"
tools:ignore="Overdraw">
<com.google.android.exoplayer2.ui.PlayerControlView
android:id="@+id/controller" android:id="@+id/controller"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="match_parent"/>
</merge>
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<resources>
<style name="VrTheme" parent="android:Theme.Material"/>
</resources>
...@@ -14,5 +14,5 @@ ...@@ -14,5 +14,5 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<style name="VrTheme" parent="android:Theme.Holo"/> <style name="ExoVrTheme" parent="android:Theme.DeviceDefault"/>
</resources> </resources>
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer2.ui.spherical; package com.google.android.exoplayer2.ui.spherical;
import static com.google.android.exoplayer2.util.GlUtil.checkGlError; import static com.google.android.exoplayer2.util.GlUtil.checkGlError;
......
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer2.ui.spherical; package com.google.android.exoplayer2.ui.spherical;
import android.content.Context; import android.content.Context;
......
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer2.ui.spherical; package com.google.android.exoplayer2.ui.spherical;
import static com.google.android.exoplayer2.util.GlUtil.checkGlError; import static com.google.android.exoplayer2.util.GlUtil.checkGlError;
...@@ -26,7 +25,7 @@ import java.nio.FloatBuffer; ...@@ -26,7 +25,7 @@ import java.nio.FloatBuffer;
/** Renders a pointer. */ /** Renders a pointer. */
public final class PointerRenderer { public final class PointerRenderer {
// The pointer quad is 2 * SIZE units. // The pointer quad is 2 * SIZE units.
private static final float SIZE = .01f; private static final float SIZE = 0.01f;
private static final float DISTANCE = 1; private static final float DISTANCE = 1;
// Standard vertex shader. // Standard vertex shader.
......
...@@ -55,7 +55,7 @@ public final class SphericalSurfaceView extends GLSurfaceView { ...@@ -55,7 +55,7 @@ public final class SphericalSurfaceView extends GLSurfaceView {
// Arbitrary vertical field of view. // Arbitrary vertical field of view.
private static final int FIELD_OF_VIEW_DEGREES = 90; private static final int FIELD_OF_VIEW_DEGREES = 90;
private static final float Z_NEAR = .1f; private static final float Z_NEAR = 0.1f;
private static final float Z_FAR = 100; private static final float Z_FAR = 100;
// TODO Calculate this depending on surface size and field of view. // TODO Calculate this depending on surface size and field of view.
...@@ -84,7 +84,7 @@ public final class SphericalSurfaceView extends GLSurfaceView { ...@@ -84,7 +84,7 @@ public final class SphericalSurfaceView extends GLSurfaceView {
// Configure sensors and touch. // Configure sensors and touch.
sensorManager = sensorManager =
(SensorManager) Assertions.checkNotNull(context.getSystemService(Context.SENSOR_SERVICE)); (SensorManager) Assertions.checkNotNull(context.getSystemService(Context.SENSOR_SERVICE));
Sensor orientationSensor = null; @Nullable Sensor orientationSensor = null;
if (Util.SDK_INT >= 18) { if (Util.SDK_INT >= 18) {
// TYPE_GAME_ROTATION_VECTOR is the easiest sensor since it handles all the complex math for // TYPE_GAME_ROTATION_VECTOR is the easiest sensor since it handles all the complex math for
// fusion. It's used instead of TYPE_ROTATION_VECTOR since the latter uses the magnetometer on // fusion. It's used instead of TYPE_ROTATION_VECTOR since the latter uses the magnetometer on
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment