Android:在ARCore SDK中节点旁边的平面中以相同的缩放比例缩放其大小后,需要复制或复制/粘贴添加的对象

时间:2018-11-06 12:00:42

标签: android arcore sceneform

我正在尝试制作一个演示应用程序,我想在其中添加一些选项,例如复制/粘贴所选节点,删除该节点等...

我已经完成删除节点部分的操作。现在,我尝试在缩放和调整节点大小后复制/复制粘贴添加一个节点,但是在平面中复制已编辑的节点没有成功。重复节点将与添加或编辑(缩放或调整大小)节点相同。

谁能告诉我如何在应用程序中复制/粘贴该节点?有没有简单的方法可以做到这一点?

我的主要活动代码如下:

import android.content.DialogInterface;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.BottomSheetBehavior;
import android.support.design.widget.BottomSheetDialog;
import android.support.v7.widget.AppCompatEditText;
import android.support.v7.widget.AppCompatImageView;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
import android.widget.Toast;

import com.ar.furniture.R;
import com.ar.furniture.adapter.FurnitureAdapter;
import com.ar.furniture.model.FurnitureModel;
import com.ar.furniture.utils.Logger;
import com.ar.furniture.utils.PhotoUtils;
import com.ar.furniture.utils.PointerDrawable;
import com.ar.furniture.utils.Utils;
import com.google.ar.core.Frame;
import com.google.ar.core.HitResult;
import com.google.ar.core.Plane;
import com.google.ar.core.Trackable;
import com.google.ar.core.TrackingState;
import com.google.ar.sceneform.FrameTime;
import com.google.ar.sceneform.Scene;
import com.google.ar.sceneform.ux.ArFragment;

import java.util.ArrayList;
import java.util.List;

public class FurnitureActivity extends BaseActivity implements
        View.OnLongClickListener,               //  View Long Click Listener
        View.OnClickListener,                   //  View Click Listener
        DialogInterface.OnDismissListener,      //  Dialog Dismiss Listener
        TextWatcher,                            //  Edit Text TextWatcher
        FurnitureAdapter.OnItemClickListener,   //  Furniture Item Click Listener
        Scene.OnUpdateListener {                //  ARScene Update Listener
    //AR Fragment
    private ArFragment fragment;
    private ImageView ivAddAR, ivClear, ivScreenShot;

    //For BottomSheet dialog
    private BottomSheetDialog mBottomSheetDialog;
    private BottomSheetBehavior behavior;

    //Adapter For RecyclerView
    private FurnitureAdapter adapter;

    //For Pointer Drawable
    private PointerDrawable pointer = new PointerDrawable();

    //For indicating if ARCore is in tracking state.
    private boolean isTracking;

    //For  indicating the user is looking at a plane.
    private boolean isHitting;

    //For Avoiding double click on view click
    private long mLastClickTime;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_furniture);

        init();
    }

    //region View Click Events
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.ivAddAR:
                //region To avoid double click evnets
                if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
                    return;
                }
                mLastClickTime = SystemClock.elapsedRealtime();
                //endregion

                //Open Bottom Sheet Dialog
                openARBottomSheetDialog();
                break;

            case R.id.ivClear:
                clearNodes(fragment);
                //removePreviousAnchors();
                break;

            case R.id.ivScreenShot:
                PhotoUtils.takePhoto(fragment);
                break;

            default:
                break;
        }
    }
    //endregion

    //region View Long Click Listener
    @Override
    public boolean onLongClick(View v) {
        String message = "";

        switch (v.getId()) {
            case R.id.ivAddAR:
                message = "Add 3D Image";
                break;

            case R.id.ivScreenShot:
                message = "Take snap shot of view";
                break;

            case R.id.ivClear:
                message = "Clear view";
                break;

            default:
                break;
        }

        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();

        return true;
    }
    //endregion

    //region Furniture Adapter
    @Override
    public void onClick(View view, FurnitureModel furnitureModel) {
        switch (view.getId()) {
            case R.id.tvFurnitureName:
            case R.id.llFurniture:
            case R.id.ivFurniture:
                //region To avoid double Click events
                if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
                    return;
                }
                mLastClickTime = SystemClock.elapsedRealtime();
                //endregion

                //Dismiss Bottom Sheet Dialog
                if (mBottomSheetDialog != null) {
                    mBottomSheetDialog.dismiss();
                }

                //Add 3D Object in ARSceneView
                addObject(Uri.parse(/*"furniture_models/" + */furnitureModel.getFurnitureModel() + ".sfb"));
                break;

            default:
                break;
        }
    }
    //endregion

    //region Bottom Sheet Dialog Dismiss Event
    @Override
    public void onDismiss(DialogInterface dialog) {
        //Dismiss Bottom Sheet Dialog
        dialog.dismiss();

        //On Dismiss of bottom sheet dialog null initialize bottom sheet dialog
        mBottomSheetDialog = null;

        //Hide Keyboard
        Utils.hideKeyboard(this);
    }
    //endregion

    //region TextWatcher
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        //Filter List
        if (adapter != null) {
            adapter.getFilter().filter(s.toString());
        }
    }

    @Override
    public void afterTextChanged(Editable s) {
    }
    //endregion

    //region ARScene View Update Listener
    @Override
    public void onUpdate(FrameTime frameTime) {
        /***
         * To update ARCore View scene view
         * First, update the tracking state. If ARCore is not tracking, remove the pointer until tracking is restored.
         * Next, if ARCore is tracking, check for the gaze of the user hitting a plane detected by ARCore and enable the pointer accordingly.
         */
        boolean trackingChanged = updateTracking();

        View contentView = findViewById(android.R.id.content);
        if (trackingChanged) {
            if (isTracking) {
                contentView.getOverlay().add(pointer);
            } else {
                contentView.getOverlay().remove(pointer);
            }

            contentView.invalidate();
        }

        if (isTracking) {
            boolean hitTestChanged = updateHitTest();

            if (hitTestChanged) {
                pointer.setEnabled(isHitting);
                contentView.invalidate();
            }
        }
    }
    //endregion

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }

    /***
     * Initialize Views and other things for first time
     */
    private void init() {
        //Initialize ARFragment With Views Id
        fragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.sceneFormFragment);

        //Initialize Listener to the ARSceneView scene which will get called before processing every frame.
        //In this listener we can make ARCore API calls and update the status of pointer.
        if (fragment != null) {
            //Add Update Listener
            fragment.getArSceneView().getScene().addOnUpdateListener(this);

            //To change plane color
            /*fragment
                    .getArSceneView()
                    .getPlaneRenderer()
                    .getMaterial()
                    .thenAccept(material -> material.setFloat3(PlaneRenderer.MATERIAL_COLOR, new Color(0.0f, 0.0f, 1.0f, 1.0f)));*/

            // Build texture sampler
            /*Texture.Sampler sampler = Texture.Sampler.builder()
                    .setMinFilter(Texture.Sampler.MinFilter.LINEAR)
                    .setMagFilter(Texture.Sampler.MagFilter.LINEAR)
                    .setWrapMode(Texture.Sampler.WrapMode.REPEAT)
                    .build();

            // Build texture with sampler
            CompletableFuture<Texture> trigrid = Texture.builder()
                    .setSource(this, R.drawable.image)
                    .setSampler(sampler)
                    .build();

            // Set plane texture
            this.fragment.getArSceneView()
                    .getPlaneRenderer()
                    .getMaterial()
                    .thenAcceptBoth(trigrid, (material, texture) -> {
                        material.setTexture(PlaneRenderer.MATERIAL_TEXTURE, texture);
                    });*/
        }

        //region Initialize View
        ivAddAR = findViewById(R.id.ivAddAR);
        ivClear = findViewById(R.id.ivClear);
        ivScreenShot = findViewById(R.id.ivScreenShot);
        //endregion

        //region Set View Click Listener
        ivAddAR.setOnClickListener(this);
        ivClear.setOnClickListener(this);
        ivScreenShot.setOnClickListener(this);
        //endregion

        //region Set View Long Click Listener
        ivAddAR.setOnLongClickListener(this);
        ivClear.setOnLongClickListener(this);
        ivScreenShot.setOnLongClickListener(this);
        //endregion

        //List of furniture model to show in list
        List<FurnitureModel> alFurnitureModel = new ArrayList<>();
        alFurnitureModel.add(new FurnitureModel("Table 1", "table_model_1", R.drawable.table_1));
        alFurnitureModel.add(new FurnitureModel("Table 2", "table_model_2", R.drawable.table_2));
        alFurnitureModel.add(new FurnitureModel("Table 3", "table_model_3", R.drawable.table_3));

        //Initialize Adapter With Furniture list and its item click event
        adapter = new FurnitureAdapter(alFurnitureModel);
        adapter.setOnItemClickListener(this);

        //Initialize Bottom sheet view and its behavior
        View bottomSheet = findViewById(R.id.bottom_sheet);
        behavior = BottomSheetBehavior.from(bottomSheet);

        //Initialize Bottom sheet callback event
        behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
            @Override
            public void onStateChanged(@NonNull View bottomSheet, int newState) {
                switch (newState) {
                    case BottomSheetBehavior.STATE_DRAGGING:
                        Logger.e("BottomSheetCallback: BottomSheetBehavior.STATE_DRAGGING");
                        break;

                    case BottomSheetBehavior.STATE_SETTLING:
                        Logger.e("BottomSheetCallback: BottomSheetBehavior.STATE_SETTLING");
                        break;

                    case BottomSheetBehavior.STATE_EXPANDED:
                        Logger.e("BottomSheetCallback: BottomSheetBehavior.STATE_EXPANDED");
                        break;

                    case BottomSheetBehavior.STATE_COLLAPSED:
                        Logger.e("BottomSheetCallback: BottomSheetBehavior.STATE_COLLAPSED");
                        break;

                    case BottomSheetBehavior.STATE_HIDDEN:
                        Logger.e("BottomSheetCallback: BottomSheetBehavior.STATE_HIDDEN");
                        break;

                    default:
                        break;
                }
            }

            @Override
            public void onSlide(@NonNull View bottomSheet, float slideOffset) {
                Logger.e("BottomSheetCallback: slideOffset: " + slideOffset);
            }
        });
    }

    /***
     * To open BottomSheet dialog for selecting AR image
     */
    private void openARBottomSheetDialog() {
        /*if (behavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
            behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
        }*/
        if (behavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
            behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
        } else {
            behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
        }

        //Initialize Bottom Sheet Dialog
        mBottomSheetDialog = new BottomSheetDialog(this);
        mBottomSheetDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);

        //Initialize View of bottom sheet dialog with its child views
        View view = getLayoutInflater().inflate(R.layout.bottom_sheet_furniture, null);

        AppCompatImageView ivClose = view.findViewById(R.id.ivClose);
        AppCompatEditText edtSearch = view.findViewById(R.id.edtSearch);
        RecyclerView rvFurniture = view.findViewById(R.id.rvFurniture);

        //Set Recycler view layout manager and adapter
        rvFurniture.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
        rvFurniture.setAdapter(adapter);

        //Initialize Edit text changed listener
        edtSearch.addTextChangedListener(this);

        //Initialize Close Button Click Event
        ivClose.setOnClickListener(v -> {
            //Hide Keyboard
            Utils.hideKeyboard(FurnitureActivity.this);

            //Dismiss Bottom Sheet Dialog
            if (mBottomSheetDialog != null) {
                mBottomSheetDialog.dismiss();
            }
        });

        //Set Bottom Sheet Content View
        mBottomSheetDialog.setContentView(view);

        //Show Bottom Sheet Dialog
        mBottomSheetDialog.show();

        //Set Bottom Sheet Dismiss Listener Event
        mBottomSheetDialog.setOnDismissListener(this);
    }

    /***
     * Uses ARCore's camera state and returns.
     * @return  -   true if the tracking state has changed since last call
     */
    private boolean updateTracking() {
        //Get ARFrame from ARSceneView
        Frame frame = fragment.getArSceneView().getArFrame();

        boolean wasTracking = isTracking;
        isTracking = frame.getCamera().getTrackingState() == TrackingState.TRACKING;

        return isTracking != wasTracking;
    }

    /***
     * Uses ARCore to call Frame.hitTest()
     * @return      -   As soon as any hit is detected, the method returns
     */
    private boolean updateHitTest() {
        Frame frame = fragment.getArSceneView().getArFrame();

        android.graphics.Point pt = getScreenCenter();

        List<HitResult> hits;
        boolean wasHitting = isHitting;
        isHitting = false;

        if (frame != null) {
            hits = frame.hitTest(pt.x, pt.y);

            for (HitResult hit : hits) {
                Trackable trackable = hit.getTrackable();

                if ((trackable instanceof Plane
                        && ((Plane) trackable).isPoseInPolygon(hit.getHitPose()))) {
                    isHitting = true;
                    break;
                }
            }
        }

        /*Vector3 position = gesture.getPosition();
        List<HitResult> hitResultList = frame.hitTest(position.x, position.y);

        for (int i = 0; i < hitResultList.size(); ++i) {
            HitResult hit = (HitResult) hitResultList.get(i);
            Trackable trackable = hit.getTrackable();
            Pose pose = hit.getHitPose();
            Node parent = this.getTransformableNode().getParent();
            this.desiredLocalPosition = new Vector3(pose.tx(),parent.getLocalPosition().y, pose.tz());
            if (parent != null && this.desiredLocalPosition != null) {
                this.desiredLocalPosition = parent.worldToLocalPoint(this.desiredLocalPosition);
            }

            this.lastArHitResult = hit;
            break;

        }*/

        return wasHitting != isHitting;
    }

    /***
     * To add 3d object on click from gallery layout button
     * @param model -   3d model uri
     */
    private void addObject(Uri model) {
        //Get ARFrame from ARSceneView
        Frame frame = fragment.getArSceneView().getArFrame();

        //Get center point of screen
        android.graphics.Point pt = getScreenCenter();

        List<HitResult> hits;
        if (frame != null) {
            hits = frame.hitTest(pt.x, pt.y);

            for (HitResult hit : hits) {
                Trackable trackable = hit.getTrackable();

                if ((trackable instanceof Plane
                        && ((Plane) trackable).isPoseInPolygon(hit.getHitPose()))) {
                    placeObject(fragment, hit.createAnchor(), model);
                    break;
                }
            }
        }
    }
}

在基本活动中添加了放置对象的代码,如下所示:

/***
     * Take the ARCore anchor from the hitTest result and builds the Sceneform nodes.
     * It starts asynchronous loading of the 3D model using the ModelRenderable builder.
     * Note: Here we are using small models, but larger models could take substantially longer to load.
     * @param fragment      -   ArFragment
     * @param anchor        -   anchor points
     * @param model         -   3d model uri
     */
    public void placeObject(ArFragment fragment, Anchor anchor, Uri model) {
        //Code For Run static object from app
        ModelRenderable
                .builder()
                .setSource(fragment.getContext(), model)
                .build()
                .thenAccept(renderable -> addNodeToScene(fragment, anchor, renderable))
                .exceptionally((throwable -> {
                    AlertDialog.Builder builder = new AlertDialog
                            .Builder(this)
                            .setMessage(throwable.getMessage())
                            .setTitle(getString(R.string.error));

                    AlertDialog dialog = builder.create();
                    dialog.show();

                    return null;
                }));
    }

    /***
     * To add two nodes and attaches them to the ArSceneView's scene object.
     * 1. AnchorNode:           Anchor nodes are positioned based on the pose of an ARCore Anchor.
     *                          As a result, they stay positioned in the sample place relative to the real world.
     * 2. TransformableNode:    We could use the base class type, Node for the placing the objects,
     *                          but Node does not have the interaction functionality to handle moving, scaling
     *                          and rotating based on user gestures.
     * @param fragment      -   ARFragment
     * @param anchor        -   anchor
     * @param renderable    -   renderable
     */
    private void addNodeToScene(ArFragment fragment, Anchor anchor, Renderable renderable) {
         //Build Anchor Node using anchor points
        AnchorNode anchorNode = new AnchorNode(anchor);

        //Build Transformable Node using ArFragment
        TransformableNode node = new TransformableNode(fragment.getTransformationSystem());

        //Set renderable to transformable node
        node.setRenderable(renderable);

        // Set the min and max scales of the ScaleController.
        // Default min is 0.75, default max is 1.75.
        node.getScaleController().setMinScale(0.4f);
        node.getScaleController().setMaxScale(2.0f);

        // Set the local scale of the node BEFORE setting its parent
        node.setLocalScale(new Vector3(0.55f, 0.55f, 0.55f));

        //Set parent to anchor node
        node.setParent(anchorNode);

        //Add anchor node to ArView
        fragment.getArSceneView().getScene().addChild(anchorNode);

        //Set transformable node to be selectable
        node.select();

        //Double tap on node.
        GestureDetector nodeGestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onDoubleTap(MotionEvent e) {
                //For removing added node
                node.getParent().removeChild(node);

                return true;
                //return super.onDoubleTap(e);
            }

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                showPopupWindow(fragment, anchorNode, renderable, node);

                return true;
                //return super.onSingleTapUp(e);
            }
        });

        //For Setting double click on node
        node.setOnTouchListener((hitTestResult, motionEvent) -> {
            nodeGestureDetector.onTouchEvent(motionEvent);
            return true;
            //return false;
        });
    }

1 个答案:

答案 0 :(得分:0)

创建了anchor和anchorNode之后,必须设置其父级:

Anchor anchor = hitResult.createAnchor();
AnchorNode anchorNode = new AnchorNode(anchor);
anchorNode.setParent(arFragment.getArSceneView().getScene());
TransformableNode transformableNode = new TransformableNode(arFragment.getTransformationSystem());
transformableNode.setParent(anchorNode);
transformableNode.setRenderable(heightRenderable);
transformableNode.select();
ScaleController scaleController = transformableNode.getScaleController();
scaleController.setMaxScale(10f);
scaleController.setMinScale(0.01f);
transformableNode.setOnTapListener(this);
arFragment.getArSceneView().getScene().addOnUpdateListener(this);
lastAnchorNode = anchorNode;

查看我的github链接:https://github.com/shubh261096/measurementapp