场景需求
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;
}
}
代码终于完成了,可以看看效果
因为有大面积的白色区域,特征点比较少,有点飘