Для создания многопользовательского приложения с использованием технологии Cloud Anchors
используйте следующий проект для начальной точки:
package com.google.ar.core.codelab.cloudanchor;
public class MainActivity extends AppCompatActivity implements GLSurfaceView.Renderer {
private static final String TAG = MainActivity.class.getSimpleName();
private GLSurfaceView surfaceView;
private final BackgroundRenderer backgroundRenderer = new BackgroundRenderer();
private final ObjectRenderer virtualObject = new ObjectRenderer();
private final ObjectRenderer virtualObjectShadow = new ObjectRenderer();
private final PlaneRenderer planeRenderer = new PlaneRenderer();
private final PointCloudRenderer pointCloudRenderer = new PointCloudRenderer();
private final float[] anchorMatrix = new float[16];
private final float[] projectionMatrix = new float[16];
private final float[] viewMatrix = new float[16];
private final float[] colorCorrectionRgba = new float[4];
private final Object singleTapAnchorLock = new Object();
@GuardedBy("singleTapAnchorLock")
private MotionEvent queuedSingleTap;
private final SnackbarHelper snackbarHelper = new SnackbarHelper();
private GestureDetector gestureDetector;
private DisplayRotationHelper displayRotationHelper;
private Session session;
private boolean installRequested;
@Nullable
@GuardedBy("singleTapAnchorLock")
private Anchor anchor;
private void handleTapOnDraw(TrackingState currentTrackingState, Frame currentFrame) {
synchronized (singleTapAnchorLock) {
if (anchor == null
&& queuedSingleTap != null
&& currentTrackingState == TrackingState.TRACKING) {
for (HitResult hit : currentFrame.hitTest(queuedSingleTap)) {
if (shouldCreateAnchorWithHit(hit)) {
Anchor newAnchor = hit.createAnchor();
setNewAnchor(newAnchor);
break;
}
}
}
queuedSingleTap = null;
}
}
private static boolean shouldCreateAnchorWithHit(HitResult hit) {
Trackable trackable = hit.getTrackable();
if (trackable instanceof Plane) {
return ((Plane) trackable).isPoseInPolygon(hit.getHitPose());
} else if (trackable instanceof Point) {
return ((Point) trackable).getOrientationMode() == OrientationMode.ESTIMATED_SURFACE_NORMAL;
}
return false;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
surfaceView = findViewById(R.id.surfaceview);
displayRotationHelper = new DisplayRotationHelper(/*context=*/ this);
gestureDetector =
new GestureDetector(
this,
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) {
synchronized (singleTapAnchorLock) {
queuedSingleTap = e;
}
return true;
}
@Override
public boolean onDown(MotionEvent e) {
return true;
}
});
surfaceView.setOnTouchListener((unusedView, event) -> gestureDetector.onTouchEvent(event));
surfaceView.setPreserveEGLContextOnPause(true);
surfaceView.setEGLContextClientVersion(2);
surfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
surfaceView.setRenderer(this);
surfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
installRequested = false;
Button clearButton = findViewById(R.id.clear_button);
clearButton.setOnClickListener(
(unusedView) -> {
synchronized (singleTapAnchorLock) {
setNewAnchor(null);
}
});
}
@Override
protected void onResume() {
super.onResume();
if (session == null) {
Exception exception = null;
int messageId = -1;
try {
switch (ArCoreApk.getInstance().requestInstall(this, !installRequested)) {
case INSTALL_REQUESTED:
installRequested = true;
return;
case INSTALLED:
break;
}
if (!CameraPermissionHelper.hasCameraPermission(this)) {
CameraPermissionHelper.requestCameraPermission(this);
return;
}
session = new Session(this);
} catch (UnavailableArcoreNotInstalledException e) {
messageId = R.string.snackbar_arcore_unavailable;
exception = e;
} catch (UnavailableApkTooOldException e) {
messageId = R.string.snackbar_arcore_too_old;
exception = e;
} catch (UnavailableSdkTooOldException e) {
messageId = R.string.snackbar_arcore_sdk_too_old;
exception = e;
} catch (Exception e) {
messageId = R.string.snackbar_arcore_exception;
exception = e;
}
if (exception != null) {
snackbarHelper.showError(this, getString(messageId));
Log.e(TAG, "Exception creating session", exception);
return;
}
Config config = new Config(session);
session.configure(config);
}
try {
session.resume();
} catch (CameraNotAvailableException e) {
snackbarHelper.showError(this, getString(R.string.snackbar_camera_unavailable));
session = null;
return;
}
surfaceView.onResume();
displayRotationHelper.onResume();
}
@Override
public void onPause() {
super.onPause();
if (session != null) {
displayRotationHelper.onPause();
surfaceView.onPause();
session.pause();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] results) {
if (!CameraPermissionHelper.hasCameraPermission(this)) {
Toast.makeText(this, "Camera permission is needed to run this application", Toast.LENGTH_LONG)
.show();
if (!CameraPermissionHelper.shouldShowRequestPermissionRationale(this)) {
CameraPermissionHelper.launchPermissionSettings(this);
}
finish();
}
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
FullScreenHelper.setFullScreenOnWindowFocusChanged(this, hasFocus);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
try {
backgroundRenderer.createOnGlThread(/*context=*/ this);
planeRenderer.createOnGlThread(/*context=*/ this, "models/trigrid.png");
pointCloudRenderer.createOnGlThread(/*context=*/ this);
virtualObject.createOnGlThread(/*context=*/ this, "models/andy.obj", "models/andy.png");
virtualObject.setMaterialProperties(0.0f, 2.0f, 0.5f, 6.0f);
virtualObjectShadow.createOnGlThread(
/*context=*/ this, "models/andy_shadow.obj", "models/andy_shadow.png");
virtualObjectShadow.setBlendMode(BlendMode.Shadow);
virtualObjectShadow.setMaterialProperties(1.0f, 0.0f, 0.0f, 1.0f);
} catch (IOException ex) {
Log.e(TAG, "Failed to read an asset file", ex);
}
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
displayRotationHelper.onSurfaceChanged(width, height);
GLES20.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
if (session == null) {
return;
}
displayRotationHelper.updateSessionIfNeeded(session);
try {
session.setCameraTextureName(backgroundRenderer.getTextureId());
Frame frame = session.update();
Camera camera = frame.getCamera();
TrackingState cameraTrackingState = camera.getTrackingState();
handleTapOnDraw(cameraTrackingState, frame);
backgroundRenderer.draw(frame);
if (cameraTrackingState == TrackingState.PAUSED) {
return;
}
camera.getProjectionMatrix(projectionMatrix, 0, 0.1f, 100.0f);
camera.getViewMatrix(viewMatrix, 0);
PointCloud pointCloud = frame.acquirePointCloud();
pointCloudRenderer.update(pointCloud);
pointCloudRenderer.draw(viewMatrix, projectionMatrix);
pointCloud.release();
planeRenderer.drawPlanes(
session.getAllTrackables(Plane.class), camera.getDisplayOrientedPose(), projectionMatrix);
boolean shouldDrawAnchor = false;
synchronized (singleTapAnchorLock) {
if (anchor != null && anchor.getTrackingState() == TrackingState.TRACKING) {
frame.getLightEstimate().getColorCorrection(colorCorrectionRgba, 0);
anchor.getPose().toMatrix(anchorMatrix, 0);
shouldDrawAnchor = true;
}
}
if (shouldDrawAnchor) {
float scaleFactor = 1.0f;
frame.getLightEstimate().getColorCorrection(colorCorrectionRgba, 0);
virtualObject.updateModelMatrix(anchorMatrix, scaleFactor);
virtualObjectShadow.updateModelMatrix(anchorMatrix, scaleFactor);
virtualObject.draw(viewMatrix, projectionMatrix, colorCorrectionRgba);
virtualObjectShadow.draw(viewMatrix, projectionMatrix, colorCorrectionRgba);
}
} catch (Throwable t) {
Log.e(TAG, "Exception on the OpenGL thread", t);
}
}
@GuardedBy("singleTapAnchorLock")
private void setNewAnchor(@Nullable Anchor newAnchor) {
if (anchor != null) {
anchor.detach();
}
anchor = newAnchor;
}
}
... а вот класс StorageManager
:
package com.google.ar.core.codelab.cloudanchor;
class StorageManager {
interface CloudAnchorIdListener {
void onCloudAnchorIdAvailable(String cloudAnchorId);
}
interface ShortCodeListener {
void onShortCodeAvailable(Integer shortCode);
}
private static final String TAG = StorageManager.class.getName();
private static final String KEY_ROOT_DIR = "shared_anchor_codelab_root";
private static final String KEY_NEXT_SHORT_CODE = "next_short_code";
private static final String KEY_PREFIX = "anchor;";
private static final int INITIAL_SHORT_CODE = 142;
private final DatabaseReference rootRef;
StorageManager(Context context) {
FirebaseApp firebaseApp = FirebaseApp.initializeApp(context);
rootRef = FirebaseDatabase.getInstance(firebaseApp).getReference().child(KEY_ROOT_DIR);
DatabaseReference.goOnline();
}
void nextShortCode(ShortCodeListener listener) {
rootRef
.child(KEY_NEXT_SHORT_CODE)
.runTransaction(
new Transaction.Handler() {
@Override
public Transaction.Result doTransaction(MutableData currentData) {
Integer shortCode = currentData.getValue(Integer.class);
if (shortCode == null) {
shortCode = INITIAL_SHORT_CODE - 1;
}
currentData.setValue(shortCode + 1);
return Transaction.success(currentData);
}
@Override
public void onComplete(
DatabaseError error, boolean committed, DataSnapshot currentData) {
if (!committed) {
Log.e(TAG, "Firebase Error", error.toException());
listener.onShortCodeAvailable(null);
} else {
listener.onShortCodeAvailable(currentData.getValue(Integer.class));
}
}
});
}
void storeUsingShortCode(int shortCode, String cloudAnchorId) {
rootRef.child(KEY_PREFIX + shortCode).setValue(cloudAnchorId);
}
void getCloudAnchorID(int shortCode, CloudAnchorIdListener listener) {
rootRef
.child(KEY_PREFIX + shortCode)
.addListenerForSingleValueEvent(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
listener.onCloudAnchorIdAvailable(String.valueOf(dataSnapshot.getValue()));
}
@Override
public void onCancelled(DatabaseError error) {
Log.e(TAG, "The database operation for getCloudAnchorID was cancelled.",
error.toException());
listener.onCloudAnchorIdAvailable(null);
}
});
}
}
Надеюсь, это поможет.