SkAR Java: refactored project structure
1) Changed file paths 2) Added helpers that were missing previously Bug: skia: Change-Id: Idb4194fcef815a23277a17670acf46255a47451d Reviewed-on: https://skia-review.googlesource.com/143680 Reviewed-by: Mike Reed <reed@google.com>
This commit is contained in:
parent
3ab3b562b1
commit
f27b479f95
@ -1,4 +1,4 @@
|
|||||||
package com.google.ar.core.examples.java.helloskar;
|
package com.google.skar.examples.helloskar.app;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.ar.core.examples.java.helloskar;
|
package com.google.skar.examples.helloskar.app;
|
||||||
|
|
||||||
import android.animation.PropertyValuesHolder;
|
import android.animation.PropertyValuesHolder;
|
||||||
import android.animation.ValueAnimator;
|
import android.animation.ValueAnimator;
|
@ -1,4 +1,4 @@
|
|||||||
package com.google.skar;
|
package com.google.skar.examples.helloskar.app;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Google Inc. All Rights Reserved.
|
||||||
|
* 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.skar.examples.helloskar.helpers;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to ask camera permission.
|
||||||
|
*/
|
||||||
|
public final class CameraPermissionHelper {
|
||||||
|
private static final int CAMERA_PERMISSION_CODE = 0;
|
||||||
|
private static final String CAMERA_PERMISSION = Manifest.permission.CAMERA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see we have the necessary permissions for this app.
|
||||||
|
*/
|
||||||
|
public static boolean hasCameraPermission(Activity activity) {
|
||||||
|
return ContextCompat.checkSelfPermission(activity, CAMERA_PERMISSION)
|
||||||
|
== PackageManager.PERMISSION_GRANTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see we have the necessary permissions for this app, and ask for them if we don't.
|
||||||
|
*/
|
||||||
|
public static void requestCameraPermission(Activity activity) {
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
activity, new String[]{CAMERA_PERMISSION}, CAMERA_PERMISSION_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see if we need to show the rationale for this permission.
|
||||||
|
*/
|
||||||
|
public static boolean shouldShowRequestPermissionRationale(Activity activity) {
|
||||||
|
return ActivityCompat.shouldShowRequestPermissionRationale(activity, CAMERA_PERMISSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch Application Setting to grant permission.
|
||||||
|
*/
|
||||||
|
public static void launchPermissionSettings(Activity activity) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||||
|
intent.setData(Uri.fromParts("package", activity.getPackageName(), null));
|
||||||
|
activity.startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Google Inc. All Rights Reserved.
|
||||||
|
* 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.skar.examples.helloskar.helpers;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.hardware.display.DisplayManager;
|
||||||
|
import android.hardware.display.DisplayManager.DisplayListener;
|
||||||
|
import android.view.Display;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import com.google.ar.core.Session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to track the display rotations. In particular, the 180 degree rotations are not notified
|
||||||
|
* by the onSurfaceChanged() callback, and thus they require listening to the android display
|
||||||
|
* events.
|
||||||
|
*/
|
||||||
|
public final class DisplayRotationHelper implements DisplayListener {
|
||||||
|
private boolean viewportChanged;
|
||||||
|
private int viewportWidth;
|
||||||
|
private int viewportHeight;
|
||||||
|
private final Context context;
|
||||||
|
private final Display display;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the DisplayRotationHelper but does not register the listener yet.
|
||||||
|
*
|
||||||
|
* @param context the Android {@link Context}.
|
||||||
|
*/
|
||||||
|
public DisplayRotationHelper(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
display = context.getSystemService(WindowManager.class).getDefaultDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the display listener. Should be called from {@link Activity#onResume()}.
|
||||||
|
*/
|
||||||
|
public void onResume() {
|
||||||
|
context.getSystemService(DisplayManager.class).registerDisplayListener(this, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters the display listener. Should be called from {@link Activity#onPause()}.
|
||||||
|
*/
|
||||||
|
public void onPause() {
|
||||||
|
context.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records a change in surface dimensions. This will be later used by {@link
|
||||||
|
* #updateSessionIfNeeded(Session)}. Should be called from {@link
|
||||||
|
* android.opengl.GLSurfaceView.Renderer
|
||||||
|
* #onSurfaceChanged(javax.microedition.khronos.opengles.GL10, int, int)}.
|
||||||
|
*
|
||||||
|
* @param width the updated width of the surface.
|
||||||
|
* @param height the updated height of the surface.
|
||||||
|
*/
|
||||||
|
public void onSurfaceChanged(int width, int height) {
|
||||||
|
viewportWidth = width;
|
||||||
|
viewportHeight = height;
|
||||||
|
viewportChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the session display geometry if a change was posted either by {@link
|
||||||
|
* #onSurfaceChanged(int, int)} call or by {@link #onDisplayChanged(int)} system callback. This
|
||||||
|
* function should be called explicitly before each call to {@link Session#update()}. This
|
||||||
|
* function will also clear the 'pending update' (viewportChanged) flag.
|
||||||
|
*
|
||||||
|
* @param session the {@link Session} object to update if display geometry changed.
|
||||||
|
*/
|
||||||
|
public void updateSessionIfNeeded(Session session) {
|
||||||
|
if (viewportChanged) {
|
||||||
|
int displayRotation = display.getRotation();
|
||||||
|
session.setDisplayGeometry(displayRotation, viewportWidth, viewportHeight);
|
||||||
|
viewportChanged = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current rotation state of android display. Same as {@link Display#getRotation()}.
|
||||||
|
*/
|
||||||
|
public int getRotation() {
|
||||||
|
return display.getRotation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisplayAdded(int displayId) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisplayRemoved(int displayId) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisplayChanged(int displayId) {
|
||||||
|
viewportChanged = true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Google Inc. All Rights Reserved.
|
||||||
|
* 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.skar.examples.helloskar.helpers;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to set up the Android full screen mode.
|
||||||
|
*/
|
||||||
|
public final class FullScreenHelper {
|
||||||
|
/**
|
||||||
|
* Sets the Android fullscreen flags. Expected to be called from {@link
|
||||||
|
* Activity#onWindowFocusChanged(boolean hasFocus)}.
|
||||||
|
*
|
||||||
|
* @param activity the Activity on which the full screen mode will be set.
|
||||||
|
* @param hasFocus the hasFocus flag passed from the {@link Activity#onWindowFocusChanged(boolean
|
||||||
|
* hasFocus)} callback.
|
||||||
|
*/
|
||||||
|
public static void setFullScreenOnWindowFocusChanged(Activity activity, boolean hasFocus) {
|
||||||
|
if (hasFocus) {
|
||||||
|
// https://developer.android.com/training/system-ui/immersive.html#sticky
|
||||||
|
activity
|
||||||
|
.getWindow()
|
||||||
|
.getDecorView()
|
||||||
|
.setSystemUiVisibility(
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
|
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
|
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||||
|
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
|
||||||
|
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Google Inc. All Rights Reserved.
|
||||||
|
* 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.skar.examples.helloskar.helpers;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.support.design.widget.BaseTransientBottomBar;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to manage the sample snackbar. Hides the Android boilerplate code, and exposes simpler
|
||||||
|
* methods.
|
||||||
|
*/
|
||||||
|
public final class SnackbarHelper {
|
||||||
|
private static final int BACKGROUND_COLOR = 0xbf323232;
|
||||||
|
private Snackbar messageSnackbar;
|
||||||
|
|
||||||
|
private enum DismissBehavior {HIDE, SHOW, FINISH}
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
public boolean isShowing() {
|
||||||
|
return messageSnackbar != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a snackbar with a given message.
|
||||||
|
*/
|
||||||
|
public void showMessage(Activity activity, String message) {
|
||||||
|
show(activity, message, DismissBehavior.HIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a snackbar with a given message, and a dismiss button.
|
||||||
|
*/
|
||||||
|
public void showMessageWithDismiss(Activity activity, String message) {
|
||||||
|
show(activity, message, DismissBehavior.SHOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a snackbar with a given error message. When dismissed, will finish the activity. Useful
|
||||||
|
* for notifying errors, where no further interaction with the activity is possible.
|
||||||
|
*/
|
||||||
|
public void showError(Activity activity, String errorMessage) {
|
||||||
|
show(activity, errorMessage, DismissBehavior.FINISH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the currently showing snackbar, if there is one. Safe to call from any thread. Safe to
|
||||||
|
* call even if snackbar is not shown.
|
||||||
|
*/
|
||||||
|
public void hide(Activity activity) {
|
||||||
|
activity.runOnUiThread(
|
||||||
|
new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (messageSnackbar != null) {
|
||||||
|
messageSnackbar.dismiss();
|
||||||
|
}
|
||||||
|
messageSnackbar = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void show(
|
||||||
|
final Activity activity, final String message, final DismissBehavior dismissBehavior) {
|
||||||
|
activity.runOnUiThread(
|
||||||
|
new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
messageSnackbar =
|
||||||
|
Snackbar.make(
|
||||||
|
activity.findViewById(android.R.id.content),
|
||||||
|
message,
|
||||||
|
Snackbar.LENGTH_INDEFINITE);
|
||||||
|
messageSnackbar.getView().setBackgroundColor(BACKGROUND_COLOR);
|
||||||
|
if (dismissBehavior != DismissBehavior.HIDE) {
|
||||||
|
messageSnackbar.setAction(
|
||||||
|
"Dismiss",
|
||||||
|
new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
messageSnackbar.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (dismissBehavior == DismissBehavior.FINISH) {
|
||||||
|
messageSnackbar.addCallback(
|
||||||
|
new BaseTransientBottomBar.BaseCallback<Snackbar>() {
|
||||||
|
@Override
|
||||||
|
public void onDismissed(Snackbar transientBottomBar, int event) {
|
||||||
|
super.onDismissed(transientBottomBar, event);
|
||||||
|
activity.finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
messageSnackbar.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Google Inc. All Rights Reserved.
|
||||||
|
* 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.skar.examples.helloskar.helpers;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.GestureDetector;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnTouchListener;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to detect taps using Android GestureDetector, and pass the taps between UI thread and
|
||||||
|
* render thread.
|
||||||
|
*/
|
||||||
|
public final class TapHelper implements OnTouchListener {
|
||||||
|
private final GestureDetector gestureDetector;
|
||||||
|
private final BlockingQueue<MotionEvent> queuedSingleTaps = new ArrayBlockingQueue<>(16);
|
||||||
|
private final BlockingQueue<ScrollEvent> queuedFingerHold = new ArrayBlockingQueue<>(16);
|
||||||
|
private boolean isScrolling = false;
|
||||||
|
private boolean previousScroll = true;
|
||||||
|
|
||||||
|
public static class ScrollEvent {
|
||||||
|
public MotionEvent e;
|
||||||
|
public boolean isStartOfScroll;
|
||||||
|
|
||||||
|
public ScrollEvent(MotionEvent e, boolean isStartOfScroll) {
|
||||||
|
this.e = e;
|
||||||
|
this.isStartOfScroll = isStartOfScroll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the tap helper.
|
||||||
|
*
|
||||||
|
* @param context the application's context.
|
||||||
|
*/
|
||||||
|
public TapHelper(Context context) {
|
||||||
|
gestureDetector =
|
||||||
|
new GestureDetector(
|
||||||
|
context,
|
||||||
|
new GestureDetector.SimpleOnGestureListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onSingleTapUp(MotionEvent e) {
|
||||||
|
// Queue tap if there is space. Tap is lost if queue is full.
|
||||||
|
queuedSingleTaps.offer(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onScroll (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||||
|
// Queue motion events when scrolling
|
||||||
|
if (e2.getPointerCount() == 1 && e1.getPointerCount() == 1) {
|
||||||
|
previousScroll = isScrolling;
|
||||||
|
isScrolling = true;
|
||||||
|
queuedFingerHold.offer(new ScrollEvent(e2, startedScrolling()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onDown(MotionEvent e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polls for a tap.
|
||||||
|
*
|
||||||
|
* @return if a tap was queued, a MotionEvent for the tap. Otherwise null if no taps are queued.
|
||||||
|
*/
|
||||||
|
public MotionEvent poll() {
|
||||||
|
return queuedSingleTaps.poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScrollEvent holdPoll() { return queuedFingerHold.poll(); }
|
||||||
|
|
||||||
|
public boolean startedScrolling() {
|
||||||
|
return isScrolling && !previousScroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View view, MotionEvent motionEvent) {
|
||||||
|
boolean val = gestureDetector.onTouchEvent(motionEvent);
|
||||||
|
|
||||||
|
// If finger is up + is scrolling: don't scroll anymore, and empty Touch Hold queue
|
||||||
|
if (motionEvent.getAction() == MotionEvent.ACTION_UP && isScrolling) {
|
||||||
|
previousScroll = true;
|
||||||
|
isScrolling = false;
|
||||||
|
queuedFingerHold.clear();
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Google Inc. All Rights Reserved.
|
||||||
|
* 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.skar.examples.helloskar.rendering;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.opengl.GLES11Ext;
|
||||||
|
import android.opengl.GLES20;
|
||||||
|
import android.opengl.GLSurfaceView;
|
||||||
|
|
||||||
|
import com.google.ar.core.Frame;
|
||||||
|
import com.google.ar.core.Session;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.FloatBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class renders the AR background from camera feed. It creates and hosts the texture given to
|
||||||
|
* ARCore to be filled with the camera image.
|
||||||
|
*/
|
||||||
|
public class BackgroundRenderer {
|
||||||
|
private static final String TAG = BackgroundRenderer.class.getSimpleName();
|
||||||
|
|
||||||
|
// Shader names.
|
||||||
|
private static final String VERTEX_SHADER_NAME = "shaders/screenquad.vert";
|
||||||
|
private static final String FRAGMENT_SHADER_NAME = "shaders/screenquad.frag";
|
||||||
|
|
||||||
|
private static final int COORDS_PER_VERTEX = 3;
|
||||||
|
private static final int TEXCOORDS_PER_VERTEX = 2;
|
||||||
|
private static final int FLOAT_SIZE = 4;
|
||||||
|
|
||||||
|
private FloatBuffer quadVertices;
|
||||||
|
private FloatBuffer quadTexCoord;
|
||||||
|
private FloatBuffer quadTexCoordTransformed;
|
||||||
|
|
||||||
|
private int quadProgram;
|
||||||
|
|
||||||
|
private int quadPositionParam;
|
||||||
|
private int quadTexCoordParam;
|
||||||
|
private int textureId = -1;
|
||||||
|
|
||||||
|
public BackgroundRenderer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTextureId() {
|
||||||
|
return textureId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates and initializes OpenGL resources needed by the background renderer. Must be called on
|
||||||
|
* the OpenGL thread, typically in {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10,
|
||||||
|
* EGLConfig)}.
|
||||||
|
*
|
||||||
|
* @param context Needed to access shader source.
|
||||||
|
*/
|
||||||
|
public void createOnGlThread(Context context) throws IOException {
|
||||||
|
// Generate the background texture.
|
||||||
|
int[] textures = new int[1];
|
||||||
|
GLES20.glGenTextures(1, textures, 0);
|
||||||
|
textureId = textures[0];
|
||||||
|
int textureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
|
||||||
|
GLES20.glBindTexture(textureTarget, textureId);
|
||||||
|
GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
|
||||||
|
GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
|
||||||
|
GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
|
||||||
|
GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
|
||||||
|
|
||||||
|
int numVertices = 4;
|
||||||
|
if (numVertices != QUAD_COORDS.length / COORDS_PER_VERTEX) {
|
||||||
|
throw new RuntimeException("Unexpected number of vertices in BackgroundRenderer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer bbVertices = ByteBuffer.allocateDirect(QUAD_COORDS.length * FLOAT_SIZE);
|
||||||
|
bbVertices.order(ByteOrder.nativeOrder());
|
||||||
|
quadVertices = bbVertices.asFloatBuffer();
|
||||||
|
quadVertices.put(QUAD_COORDS);
|
||||||
|
quadVertices.position(0);
|
||||||
|
|
||||||
|
ByteBuffer bbTexCoords =
|
||||||
|
ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE);
|
||||||
|
bbTexCoords.order(ByteOrder.nativeOrder());
|
||||||
|
quadTexCoord = bbTexCoords.asFloatBuffer();
|
||||||
|
quadTexCoord.put(QUAD_TEXCOORDS);
|
||||||
|
quadTexCoord.position(0);
|
||||||
|
|
||||||
|
ByteBuffer bbTexCoordsTransformed =
|
||||||
|
ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE);
|
||||||
|
bbTexCoordsTransformed.order(ByteOrder.nativeOrder());
|
||||||
|
quadTexCoordTransformed = bbTexCoordsTransformed.asFloatBuffer();
|
||||||
|
|
||||||
|
int vertexShader =
|
||||||
|
ShaderUtil.loadGLShader(TAG, context, GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_NAME);
|
||||||
|
int fragmentShader =
|
||||||
|
ShaderUtil.loadGLShader(TAG, context, GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_NAME);
|
||||||
|
|
||||||
|
quadProgram = GLES20.glCreateProgram();
|
||||||
|
GLES20.glAttachShader(quadProgram, vertexShader);
|
||||||
|
GLES20.glAttachShader(quadProgram, fragmentShader);
|
||||||
|
GLES20.glLinkProgram(quadProgram);
|
||||||
|
GLES20.glUseProgram(quadProgram);
|
||||||
|
|
||||||
|
ShaderUtil.checkGLError(TAG, "Program creation");
|
||||||
|
|
||||||
|
quadPositionParam = GLES20.glGetAttribLocation(quadProgram, "a_Position");
|
||||||
|
quadTexCoordParam = GLES20.glGetAttribLocation(quadProgram, "a_TexCoord");
|
||||||
|
|
||||||
|
ShaderUtil.checkGLError(TAG, "Program parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the AR background image. The image will be drawn such that virtual content rendered with
|
||||||
|
* the matrices provided by {@link com.google.ar.core.Camera#getViewMatrix(float[], int)} and
|
||||||
|
* {@link com.google.ar.core.Camera#getProjectionMatrix(float[], int, float, float)} will
|
||||||
|
* accurately follow static physical objects. This must be called <b>before</b> drawing virtual
|
||||||
|
* content.
|
||||||
|
*
|
||||||
|
* @param frame The last {@code Frame} returned by {@link Session#update()}.
|
||||||
|
*/
|
||||||
|
public void draw(Frame frame) {
|
||||||
|
// If display rotation changed (also includes view size change), we need to re-query the uv
|
||||||
|
// coordinates for the screen rect, as they may have changed as well.
|
||||||
|
if (frame.hasDisplayGeometryChanged()) {
|
||||||
|
frame.transformDisplayUvCoords(quadTexCoord, quadTexCoordTransformed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No need to test or write depth, the screen quad has arbitrary depth, and is expected
|
||||||
|
// to be drawn first.
|
||||||
|
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
|
||||||
|
GLES20.glDepthMask(false);
|
||||||
|
|
||||||
|
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
|
||||||
|
|
||||||
|
GLES20.glUseProgram(quadProgram);
|
||||||
|
|
||||||
|
// Set the vertex positions.
|
||||||
|
GLES20.glVertexAttribPointer(
|
||||||
|
quadPositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, quadVertices);
|
||||||
|
|
||||||
|
// Set the texture coordinates.
|
||||||
|
GLES20.glVertexAttribPointer(
|
||||||
|
quadTexCoordParam,
|
||||||
|
TEXCOORDS_PER_VERTEX,
|
||||||
|
GLES20.GL_FLOAT,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
quadTexCoordTransformed);
|
||||||
|
|
||||||
|
// Enable vertex arrays
|
||||||
|
GLES20.glEnableVertexAttribArray(quadPositionParam);
|
||||||
|
GLES20.glEnableVertexAttribArray(quadTexCoordParam);
|
||||||
|
|
||||||
|
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
|
||||||
|
|
||||||
|
// Disable vertex arrays
|
||||||
|
GLES20.glDisableVertexAttribArray(quadPositionParam);
|
||||||
|
GLES20.glDisableVertexAttribArray(quadTexCoordParam);
|
||||||
|
|
||||||
|
// Restore the depth state for further drawing.
|
||||||
|
GLES20.glDepthMask(true);
|
||||||
|
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
|
||||||
|
|
||||||
|
ShaderUtil.checkGLError(TAG, "Draw");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final float[] QUAD_COORDS =
|
||||||
|
new float[]{
|
||||||
|
-1.0f, -1.0f, 0.0f, -1.0f, +1.0f, 0.0f, +1.0f, -1.0f, 0.0f, +1.0f, +1.0f, 0.0f,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final float[] QUAD_TEXCOORDS =
|
||||||
|
new float[]{
|
||||||
|
0.0f, 1.0f,
|
||||||
|
0.0f, 0.0f,
|
||||||
|
1.0f, 1.0f,
|
||||||
|
1.0f, 0.0f,
|
||||||
|
};
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package com.google.ar.core.examples.java.helloskar;
|
package com.google.skar.examples.helloskar.rendering;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Google Inc. All Rights Reserved.
|
||||||
|
* 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.skar.examples.helloskar.rendering;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.opengl.GLES20;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shader helper functions.
|
||||||
|
*/
|
||||||
|
public class ShaderUtil {
|
||||||
|
/**
|
||||||
|
* Converts a raw text file, saved as a resource, into an OpenGL ES shader.
|
||||||
|
*
|
||||||
|
* @param type The type of shader we will be creating.
|
||||||
|
* @param filename The filename of the asset file about to be turned into a shader.
|
||||||
|
* @return The shader object handler.
|
||||||
|
*/
|
||||||
|
public static int loadGLShader(String tag, Context context, int type, String filename)
|
||||||
|
throws IOException {
|
||||||
|
String code = readRawTextFileFromAssets(context, filename);
|
||||||
|
int shader = GLES20.glCreateShader(type);
|
||||||
|
GLES20.glShaderSource(shader, code);
|
||||||
|
GLES20.glCompileShader(shader);
|
||||||
|
|
||||||
|
// Get the compilation status.
|
||||||
|
final int[] compileStatus = new int[1];
|
||||||
|
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
|
||||||
|
|
||||||
|
// If the compilation failed, delete the shader.
|
||||||
|
if (compileStatus[0] == 0) {
|
||||||
|
Log.e(tag, "Error compiling shader: " + GLES20.glGetShaderInfoLog(shader));
|
||||||
|
GLES20.glDeleteShader(shader);
|
||||||
|
shader = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shader == 0) {
|
||||||
|
throw new RuntimeException("Error creating shader.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if we've had an error inside of OpenGL ES, and if so what that error is.
|
||||||
|
*
|
||||||
|
* @param label Label to report in case of error.
|
||||||
|
* @throws RuntimeException If an OpenGL error is detected.
|
||||||
|
*/
|
||||||
|
public static void checkGLError(String tag, String label) {
|
||||||
|
int lastError = GLES20.GL_NO_ERROR;
|
||||||
|
// Drain the queue of all errors.
|
||||||
|
int error;
|
||||||
|
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
|
||||||
|
Log.e(tag, label + ": glError " + error);
|
||||||
|
lastError = error;
|
||||||
|
}
|
||||||
|
if (lastError != GLES20.GL_NO_ERROR) {
|
||||||
|
throw new RuntimeException(label + ": glError " + lastError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a raw text file into a string.
|
||||||
|
*
|
||||||
|
* @param filename The filename of the asset file about to be turned into a shader.
|
||||||
|
* @return The context of the text file, or null in case of error.
|
||||||
|
*/
|
||||||
|
private static String readRawTextFileFromAssets(Context context, String filename)
|
||||||
|
throws IOException {
|
||||||
|
try (InputStream inputStream = context.getAssets().open(filename);
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
sb.append(line).append("\n");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user