如何使用 AR 核心将多个 3d 对象组合为一个
how to group multiple 3d objects as one using AR core
这里我在场景中显示多个 3d 对象(查看下图)
这里我有 3 个 3d 模型对象,现在可以对 3d 模型对象进行分组,例如。如果我移动一个 3d 对象,那么其他 2 个 3d 对象也需要移动。是否可以在不使用统一和云锚的情况下实现这一目标
下面是我的代码
package com.google.ar.sceneform.samples.hellosceneform;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.widget.Toast;
import com.google.ar.core.Anchor;
import com.google.ar.core.HitResult;
import com.google.ar.core.Plane;
import com.google.ar.sceneform.AnchorNode;
import com.google.ar.sceneform.rendering.ModelRenderable;
import com.google.ar.sceneform.ux.ArFragment;
import com.google.ar.sceneform.ux.TransformableNode;
public class HelloSceneformActivity extends AppCompatActivity {
private static final String TAG = HelloSceneformActivity.class.getSimpleName();
private static final double MIN_OPENGL_VERSION = 3.0;
private ArFragment arFragment;
private ModelRenderable andyRenderable;
@Override
@SuppressWarnings({"AndroidApiChecker", "FutureReturnValueIgnored"})
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!checkIsSupportedDeviceOrFinish(this)) {
return;
}
setContentView(R.layout.activity_ux);
arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment);
// When you build a Renderable, Sceneform loads its resources in the background while returning
// a CompletableFuture. Call thenAccept(), handle(), or check isDone() before calling get().
ModelRenderable.builder()
.setSource(this, R.raw.andy)
.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;
});
arFragment.setOnTapArPlaneListener(
(HitResult hitResult, Plane plane, MotionEvent motionEvent) -> {
if (andyRenderable == null) {
return;
}
// Create the Anchor.
Anchor anchor = hitResult.createAnchor();
AnchorNode anchorNode = new AnchorNode(anchor);
anchorNode.setParent(arFragment.getArSceneView().getScene());
// Create the transformable andy and add it to the anchor.
TransformableNode andy = new TransformableNode(arFragment.getTransformationSystem());
andy.setParent(anchorNode);
andy.setRenderable(andyRenderable);
andy.select();
});
}
public static boolean checkIsSupportedDeviceOrFinish(final Activity activity) {
if (Build.VERSION.SDK_INT < 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;
}
}
您可以自己对节点进行分组,并在移动节点时简单地移动组中的每个节点,方法是对每个节点应用相同的变换。
在高级术语中 - 为您的节点创建一个组、数组或某种集合,无论对您的应用程序有用。
然后当你想移动组时,你可以遍历每个节点并为每个节点应用相同的转换 - 例如在下面的示例中,所有应用都将应用翻译 (-0.05f,0,0):
//Looping over each node in your group
{
//For each node, get the current Pose and transform it then set a new anchor at the new pose
Session session = arFragment.getArSceneView().getSession();
Anchor nextAnchor = [Get the next anchor node in your array or group]
Pose oldPose = nextAnchor.getPose();
Pose newPose = oldPose.compose(Pose.makeTranslation(-0.05f,0,0));
movedAnchor = moveRenderable(currentSelectedAnchorNode, newPose);
[Update your array or collection or group with this 'new' movedAnchor]
}
要实际移动各个节点,可以使用下面的代码。这实际上是删除了当前位置的节点,并在目标位置重新创建了新的节点:
private AnchorNode moveRenderable(AnchorNode anchorNodeToMove, Pose newPoseToMoveTo) {
//Move a renderable to a new pose
if (anchorNodeToMove != null) {
arFragment.getArSceneView().getScene().removeChild(anchorNodeToMove);
} else {
Log.d(TAG,"moveRenderable - anchorNodeToMove was null");
return null;
}
Frame frame = arFragment.getArSceneView().getArFrame();
Session session = arFragment.getArSceneView().getSession();
Anchor markAnchor = session.createAnchor(newPoseToMoveTo.extractTranslation());
AnchorNode newAnchorNode = new AnchorNode(markAnchor);
newAnchorNode.setRenderable(andyRenderable);
newAnchorNode.setParent(arFragment.getArSceneView().getScene());
return newAnchorNode;
}
您可以使用 .setParent()
方法将 3D 对象组合在一起。变换对象(移动、旋转或缩放)时,子对象也会变换。
太阳系示例在 createSolarSystem()
中演示了这一点。行星是太阳的子节点,月亮是地球节点的子节点。
然后您可以使用 setLocalPosition()
相对于父对象定位对象。如positioning the planet in solarActivity:
Planet planet =
new Planet(
this, name, planetScale, orbitDegreesPerSecond, axisTilt, renderable, solarSettings);
planet.setParent(orbit);
planet.setLocalPosition(new Vector3(auFromParent * AU_TO_METERS, 0.0f, 0.0f));
您可以通过获取所有 TransformableNode 节点的当前位置来完成此操作。
然后为所有节点分配新的向量值,例如 node1.setLocalPosition(localPosition);
private void modleMovement(TransformableNode node, String moveMent) {
Vector3 currentPosition1 = new Vector3();
Vector3 currentPosition2 = new Vector3();
Vector3 currentPosition3 = new Vector3();
Vector3 move = new Vector3();
currentPosition1 =Objects.requireNonNull( node1.getLocalPosition());
currentPosition2 =Objects.requireNonNull( node2.getLocalPosition());
currentPosition3 =Objects.requireNonNull( node3.getLocalPosition());
if (moveMent.equals("down")) {
move.set(currentPosition.x, (float) (currentPosition.y - 0.1), currentPosition.z);
localPosition = move;
}
if (moveMent.equals("up")) {
move.set(currentPosition.x, (float) (currentPosition.y + 0.1), currentPosition.z);
localPosition = move;
}
node1.setLocalPosition(localPosition);
}
这里我在场景中显示多个 3d 对象(查看下图)
这里我有 3 个 3d 模型对象,现在可以对 3d 模型对象进行分组,例如。如果我移动一个 3d 对象,那么其他 2 个 3d 对象也需要移动。是否可以在不使用统一和云锚的情况下实现这一目标
下面是我的代码
package com.google.ar.sceneform.samples.hellosceneform;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.widget.Toast;
import com.google.ar.core.Anchor;
import com.google.ar.core.HitResult;
import com.google.ar.core.Plane;
import com.google.ar.sceneform.AnchorNode;
import com.google.ar.sceneform.rendering.ModelRenderable;
import com.google.ar.sceneform.ux.ArFragment;
import com.google.ar.sceneform.ux.TransformableNode;
public class HelloSceneformActivity extends AppCompatActivity {
private static final String TAG = HelloSceneformActivity.class.getSimpleName();
private static final double MIN_OPENGL_VERSION = 3.0;
private ArFragment arFragment;
private ModelRenderable andyRenderable;
@Override
@SuppressWarnings({"AndroidApiChecker", "FutureReturnValueIgnored"})
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!checkIsSupportedDeviceOrFinish(this)) {
return;
}
setContentView(R.layout.activity_ux);
arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment);
// When you build a Renderable, Sceneform loads its resources in the background while returning
// a CompletableFuture. Call thenAccept(), handle(), or check isDone() before calling get().
ModelRenderable.builder()
.setSource(this, R.raw.andy)
.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;
});
arFragment.setOnTapArPlaneListener(
(HitResult hitResult, Plane plane, MotionEvent motionEvent) -> {
if (andyRenderable == null) {
return;
}
// Create the Anchor.
Anchor anchor = hitResult.createAnchor();
AnchorNode anchorNode = new AnchorNode(anchor);
anchorNode.setParent(arFragment.getArSceneView().getScene());
// Create the transformable andy and add it to the anchor.
TransformableNode andy = new TransformableNode(arFragment.getTransformationSystem());
andy.setParent(anchorNode);
andy.setRenderable(andyRenderable);
andy.select();
});
}
public static boolean checkIsSupportedDeviceOrFinish(final Activity activity) {
if (Build.VERSION.SDK_INT < 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;
}
}
您可以自己对节点进行分组,并在移动节点时简单地移动组中的每个节点,方法是对每个节点应用相同的变换。
在高级术语中 - 为您的节点创建一个组、数组或某种集合,无论对您的应用程序有用。
然后当你想移动组时,你可以遍历每个节点并为每个节点应用相同的转换 - 例如在下面的示例中,所有应用都将应用翻译 (-0.05f,0,0):
//Looping over each node in your group
{
//For each node, get the current Pose and transform it then set a new anchor at the new pose
Session session = arFragment.getArSceneView().getSession();
Anchor nextAnchor = [Get the next anchor node in your array or group]
Pose oldPose = nextAnchor.getPose();
Pose newPose = oldPose.compose(Pose.makeTranslation(-0.05f,0,0));
movedAnchor = moveRenderable(currentSelectedAnchorNode, newPose);
[Update your array or collection or group with this 'new' movedAnchor]
}
要实际移动各个节点,可以使用下面的代码。这实际上是删除了当前位置的节点,并在目标位置重新创建了新的节点:
private AnchorNode moveRenderable(AnchorNode anchorNodeToMove, Pose newPoseToMoveTo) {
//Move a renderable to a new pose
if (anchorNodeToMove != null) {
arFragment.getArSceneView().getScene().removeChild(anchorNodeToMove);
} else {
Log.d(TAG,"moveRenderable - anchorNodeToMove was null");
return null;
}
Frame frame = arFragment.getArSceneView().getArFrame();
Session session = arFragment.getArSceneView().getSession();
Anchor markAnchor = session.createAnchor(newPoseToMoveTo.extractTranslation());
AnchorNode newAnchorNode = new AnchorNode(markAnchor);
newAnchorNode.setRenderable(andyRenderable);
newAnchorNode.setParent(arFragment.getArSceneView().getScene());
return newAnchorNode;
}
您可以使用 .setParent()
方法将 3D 对象组合在一起。变换对象(移动、旋转或缩放)时,子对象也会变换。
太阳系示例在 createSolarSystem()
中演示了这一点。行星是太阳的子节点,月亮是地球节点的子节点。
然后您可以使用 setLocalPosition()
相对于父对象定位对象。如positioning the planet in solarActivity:
Planet planet =
new Planet(
this, name, planetScale, orbitDegreesPerSecond, axisTilt, renderable, solarSettings);
planet.setParent(orbit);
planet.setLocalPosition(new Vector3(auFromParent * AU_TO_METERS, 0.0f, 0.0f));
您可以通过获取所有 TransformableNode 节点的当前位置来完成此操作。
然后为所有节点分配新的向量值,例如 node1.setLocalPosition(localPosition);
private void modleMovement(TransformableNode node, String moveMent) {
Vector3 currentPosition1 = new Vector3();
Vector3 currentPosition2 = new Vector3();
Vector3 currentPosition3 = new Vector3();
Vector3 move = new Vector3();
currentPosition1 =Objects.requireNonNull( node1.getLocalPosition());
currentPosition2 =Objects.requireNonNull( node2.getLocalPosition());
currentPosition3 =Objects.requireNonNull( node3.getLocalPosition());
if (moveMent.equals("down")) {
move.set(currentPosition.x, (float) (currentPosition.y - 0.1), currentPosition.z);
localPosition = move;
}
if (moveMent.equals("up")) {
move.set(currentPosition.x, (float) (currentPosition.y + 0.1), currentPosition.z);
localPosition = move;
}
node1.setLocalPosition(localPosition);
}