- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
目前我正在探索阿科尔和云锚。我能够使用sceneform和云锚创建一个应用程序。基本上,我们可以使用device-1
在任何曲面上放置一个对象,并且device-2
可以使用云锚共享相同的体验。
解析云锚后,无法侦听对象转换中的任何更改,即如果用户在device-1
上旋转或移动对象,则在device-2
上没有任何有关该对象的信息。
如果我们想使用云锚创建多人体验,我找到的唯一方法是将对象的状态与服务器和两个设备同步。
我的问题是:
这是在arcore中实现多人同步的正确方法吗?
如果此方法可行,我们需要同步哪些属性?
最佳答案
要使用Cloud Anchors
技术构建多人应用程序,请使用following project作为起点:
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);
}
});
}
}
关于java - 如何使用Cloud Anchor和Sceneform构建ARCore多人游戏?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53002015/
我正在扩展我非常有限的 ARCore 知识。 我的问题与 this question 相似(但不同) 我想知道我的设备相机节点是否与我的其他节点相交/重叠,但到目前为止我还没有任何运气 我正在尝试这样
我已经使用 SceneView 加载 3D 模型将近一年了,但我一直不明白是什么导致了这种泄漏。我会实现 LeakCanary,但只有这一次泄漏,因为我不知道如何解决这个问题。 但现在我想弄清问题的根
我想更新与每帧更新的 anchor 相关的数据。如何从 ArSceneView 获取每一帧的帧? 最佳答案 ArSceneView 在绘制场景之前更新 ARCore Frame 对象。您可以通过从使用
我使用了 SceneForm SDK 的示例,但是在这个示例中,当我们将对象放在检测到的地板上时,我们不能在墙上移动。如何解决? arFragment.setOnTapArPlaneListener(
private AnchorNode anchorNode; private void removeAnchorNode(Node nodeRemove) { //Remove an
关闭。这个问题需要更多 focused .它目前不接受答案。 想改进这个问题?更新问题,使其仅关注一个问题 editing this post . 2年前关闭。 Improve this questi
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提出有关书籍、工具、软件库等建议的问题。您可以编辑问题,以便可以用事实和引用来回答它。 9 个月
默认情况下,Arcore/Sceneform 使用房间作为 HDR 环境图像。我该如何更换它? Default HDR Room 最佳答案 目前无法设置自己的环境贴图。如果您想跟踪开发,请跟踪此功能请
基于 ARcore 开发指南 ( Lighting Estimation developer guide for Android ),我正在尝试禁用 Scenceform 中的灯光。但什么也没发生。
我在我的应用程序中使用 AR Core 作为 3D 查看器。我没有将 Sceneform 用于 AR 渲染,而是用于渲染 3D 模型。我面临的问题是如何使用滑动手势或触摸事件对模型进行 360 度旋转
我正在构建一个使用 AR 的 Android 应用程序,我正在为此使用 sceneform 包。我显示一个 3D 模型,其 ModelRenderable 是通过从外部 URI 下载相应的 .sfb
我知道 ARCore 尚不支持步行等 3D 动画,但我如何为节点的旋转设置动画? 我知道我可以设置 LocalRotation 或 WorldRotation,但如何以流畅的方式连续制作此动画? 最佳
我添加了一项基于 Google 的 Codelabs 教程 ( https://codelabs.developers.google.com/codelabs/sceneform-intro/inde
ARCore sceneform 示例项目“hellosceneform”很酷并且运行良好。 问题是需要四处移动手机以获得放置 anchor 的表面。太慢了。 我的应用程序不需要任何东西显示在垂直平面
如果我有两个单独的 Sceneform 可变换节点,它们有自己的 AnchorNodes 和 Anchors,我如何将它们“链接”在一起,以便用户的手势(捏合和拖动)对它们产生相同的效果?如果我将它们
我想通过它的置信度值对 PointCloud 进行着色。 首先,我创建了三个 FloatBuffer,我在其中放置基于阈值的单个点。 private FloatBuffer makeFLoatBuff
我一直在努力将垂直放置的 3d 模型 GLB 格式正确放置在垂直表面上。 Just to be clear, I am not referring to the difficulty of ident
我有一种在 AR 环境中构建和生成 3D Assets 的方法。 3D 资源是存储在元数据文件夹中的 .sfb 文件。我的元数据文件夹中有多个 3D 资源,我希望在调用此函数时随机选择一个资源。这是我
我的理解是有几种环境支持 ARCore,Unity 和 Sceneform SDK 是其中的一些选项。 我想知道除了一个在 Java 中,另一个在 C# 中之外,它们之间有什么不同?除了语言偏好之外,
我正在尝试开发一款 AR 应用程序,以帮助视障人士改善他们使用计算机的条件。 我正在研究 AR 如何帮助 HCI 解决视觉障碍问题,因此,该应用程序使用 WebRTC 来使用 Sceneform 在
我是一名优秀的程序员,十分优秀!