ARCore 使用 SceneForm 框架 —— 三维空间中不基于 Plane 加载 3D 模型

场景需求

Arcore 的 SceneForm 提供的示例都是基于获取到 Plane 后,基于 Plane 的上可以跟踪的点绘制 3D 模型,然而对于有些特殊情况,例如空中飞行的点,需要在没有平面的前提下去绘制 3D 模型

示例代码

1、对 SceneForm 示例修改时发现,应用在进入到探测平面的界面时,会自动提示摇摆手机以检测平面,但是,并不是必须的,且没有提供隐藏的接口。
无奈,只好跟踪源码,查看显示机制,界面加载出来就会显示提示,直觉告诉我,查看 ARFragment 的生命周期 onResume(),
在这里插入图片描述
发现 ARFragment 只是包装了 BaseArFragment,继续跟踪 BaseArFragment
在这里插入图片描述
在这里插入图片描述
这里十分可疑,一开始就显示,更新完成后就隐藏,可是没有直接操作的接口,只好自定义 ARFragment 测试一下

public class CleanArFragment extends BaseArFragment {
    private static final String TAG = CleanArFragment.class.getSimpleName();

    @Override
    public boolean isArRequired() {
        return true;
    }

    @Override
    public String[] getAdditionalPermissions() {
        return new String[0];
    }

    @Override
    protected void handleSessionException(UnavailableException sessionException) {

        String message;
        if (sessionException instanceof UnavailableArcoreNotInstalledException) {
            message = "Please install ARCore";
        } else if (sessionException instanceof UnavailableApkTooOldException) {
            message = "Please update ARCore";
        } else if (sessionException instanceof UnavailableSdkTooOldException) {
            message = "Please update this app";
        } else if (sessionException instanceof UnavailableDeviceNotCompatibleException) {
            message = "This device does not support AR";
        } else {
            message = "Failed to create AR session";
        }
        Log.e(TAG, "Error: " + message, sessionException);
        Toast.makeText(requireActivity(), message, Toast.LENGTH_LONG).show();
    }

    @Override
    protected Config getSessionConfiguration(Session session) {
        return new Config(session);
    }


    @Override
    protected Set<Session.Feature> getSessionFeatures() {
        return Collections.emptySet();
    }

    @Override
    public void onUpdate(FrameTime frameTime) {
        super.onUpdate(frameTime);
        //getPlaneDiscoveryController().hide();
    }

    @Override
    public void onResume() {
        super.onResume();
        //开始就隐藏
        getPlaneDiscoveryController().hide();
    }
}

测试发现,完美隐藏。

2、在空间中显示 3D 模型(测试时发现,相机世界坐标系的单位精度是米)

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();
    private static final double MIN_OPENGL_VERSION = 3.0;
    private static final int SHOW_OBJECT = 0x1101;

    private CleanArFragment arFragment;
    private ModelRenderable andyRenderable;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == SHOW_OBJECT) {
            	// 提供 3D 模型显示的位置(世界坐标系,相机面向的方向 2m 处)
                Vector3 point = new Vector3(0, 0, -2);
                showObj(point);
            }
            super.handleMessage(msg);
        }
    };


    @Override
    @SuppressWarnings({"AndroidApiChecker", "FutureReturnValueIgnored"})
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (!checkIsSupportedDeviceOrFinish(this)) {
            return;
        }

        setContentView(R.layout.activity_main);
        arFragment = (CleanArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment);
        ArFragment arFragment;

        //构造 3D 模型资源
        ModelRenderable.builder()
                .setSource(this, R.raw.charango)
                .build()
                .thenAccept(renderable -> andyRenderable = renderable)
                .exceptionally(
                        throwable -> {
                            Toast toast =
                                    Toast.makeText(this, "Unable to load andy renderable", Toast.LENGTH_LONG);
                            toast.setGravity(Gravity.CENTER, 0, 0);
                            toast.show();
                            return null;
                        });

		// 线程通知延迟绘制
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Message msg = handler.obtainMessage();
                msg.what = SHOW_OBJECT;
                handler.sendMessage(msg);
            }
        }){}.start();
    }

    private void showObj(Vector3 worldSet) {
        AnchorNode anchorNode = new AnchorNode();
        //设置锚点在世界坐标系的位置
        anchorNode.setWorldPosition(worldSet);
        anchorNode.setParent(arFragment.getArSceneView().getScene());

        TransformableNode andy = new TransformableNode(arFragment.getTransformationSystem());
        andy.setParent(anchorNode);
        andy.setRenderable(andyRenderable);

        andy.select();
        andy.setWorldScale(new Vector3(0.1f, 0.1f, 0.1f));
        // 禁止缩放,没禁止缩放,设置的倍数会失效,自动加载默认的大小
        andy.getScaleController().setEnabled(false);
    }

    public static boolean checkIsSupportedDeviceOrFinish(final Activity activity) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            Log.e(TAG, "Sceneform requires Android N or later");
            Toast.makeText(activity, "Sceneform requires Android N or later", Toast.LENGTH_LONG).show();
            activity.finish();
            return false;
        }
        String openGlVersionString =
                ((ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE))
                        .getDeviceConfigurationInfo()
                        .getGlEsVersion();
        if (Double.parseDouble(openGlVersionString) < MIN_OPENGL_VERSION) {
            Log.e(TAG, "Sceneform requires OpenGL ES 3.0 later");
            Toast.makeText(activity, "Sceneform requires OpenGL ES 3.0 or later", Toast.LENGTH_LONG)
                    .show();
            activity.finish();
            return false;
        }
        return true;
    }
}

代码终于完成了,可以看看效果
在这里插入图片描述
因为有大面积的白色区域,特征点比较少,有点飘

发布了23 篇原创文章 · 获赞 22 · 访问量 3898

猜你喜欢

转载自blog.csdn.net/qq_19154605/article/details/103071912