Search code examples
androidtouchgoogle-cardboardrajawalivr

Move camera around using touch event in CardBoard and Rajawali VR Android


Currently, I am working on VR application for android that using google CardBoard and Rajawali to play the 360 video. The sensor is working well, but I cannot use touch to drag the scene or camera around correctly. Is there any way to enable touch mode in this app?

Any help is greatly appreciated! Thank you.


Solution

  • I've worked on the same thing, and this is what I used:

    First, take a look at Rajawali's ArcballCamera class. You can see there how touch events are handled to rotate the camera with touch events.

    The problem is I didn't liked the default behaviour of the rotation when the user moved across the screen, so I did another implementation on my own, based on the previous one and rotating directly the sphere I was looking to instead of the camera, so here it goes (all of this is inside my Renderer class, btw):

    First, the declarations:

    private GestureDetector detector;           //gesture detector
    private ScaleGestureDetector scaleDetector; //scale detector (for zooming)
    private GestureListener gListener;          //gesture listener
    private ScaleListener sListener;            //scale listener
    private View.OnTouchListener touchListener; //touch events listener
    private boolean isRotating;                 //true if the sphere is rotating
    private boolean isScaling;                  //true if the sphere is scaling
    private float xInicial,yInicial;            //inicial touch point
    //sphere's yaw and pitch, used for rotation
    private double yaw,pitch, yawAcumulado=0, pitchAcumulado=0, yawAcumuladoR=0, pitchAcumuladoR=0;
    //physical to logical (in 3D world) conversion: screen scroll to sphere rotation
    private final double gradosPorBarridoX=120, gradosPorBarridoY=90;
    private final double gradosPorPixelYaw, gradosPorPixelPitch;
    

    In the renderer's constructor I start the inizalizations (timer and control are used for the video control view, so don't pay atention to these):

        DisplayMetrics outMetrics = new DisplayMetrics();
        ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
        gradosPorPixelPitch = gradosPorBarridoY / outMetrics.heightPixels;
        gradosPorPixelYaw = gradosPorBarridoX / outMetrics.widthPixels;
        addListeners();
        ...
        //from Rajawali ArcballCamera class
    private void addListeners(){
        ((Activity)context).runOnUiThread(new Runnable() {
            @Override
            public void run() {
                gListener = new GestureListener();
                sListener = new ScaleListener();
                detector = new GestureDetector(context, gListener);
                scaleDetector = new ScaleGestureDetector(context, sListener);
                touchListener = new View.OnTouchListener() {
                    @Override
                    public boolean onTouch(View v, MotionEvent event) {
                        scaleDetector.onTouchEvent(event); //see if it is a scale event
                        //if not, check whether it is a scroll
                        if (!isScaling) {
                            detector.onTouchEvent(event);
                            //or an up motion
                            if (event.getAction() == MotionEvent.ACTION_UP) {
                                if (!isRotating) {
                                    //change video control view's visibility
                                    TouchActivity.timer.cancel();
                                    if (TouchActivity.control.getVisibility() == View.INVISIBLE) {
                                        TouchActivity.control.setVisibility(View.VISIBLE);
                                        TouchActivity.timer.start(); //timer is restarted
                                    } else {
                                        TouchActivity.control.setVisibility(View.INVISIBLE);
                                    }
                                } else {
                                    isRotating = false;   //cancel rotation
                                }
                            }
                        }
                        return true;
                    }
                };
                TouchActivity.principal.setOnTouchListener(touchListener);
            }
        });
    }
    

    And last but not least, the event listening (both for scaling and rotating):

    /**
     * called when the rotation starts
     * @param x
     * @param y
     */
    private void startRotation(float x, float y){
        xInicial = x;
        yInicial = y;
    }
    
    /**
     * called during the consecutive events of a rotation movement
     * @param x
     * @param y
     */
    private void updateRotation(float x, float y){
        float difX = xInicial - x;
        float difY = yInicial - y;
        yaw= difX * gradosPorPixelYaw;
        pitch = difY * gradosPorPixelPitch;
        yawAcumulado+=yaw;
        pitchAcumulado+=pitch;
    }
    
    /**
     * event listener. if the user scrolls his finger through the screen, it sends the
     * touch event to calculate the sphere's rotation
     */
    private class GestureListener extends GestureDetector.SimpleOnGestureListener{
        @Override
        public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY) {
            //starts or updates the rotation with the upcoming event x and y screen values
            if(!isRotating) {
                startRotation(event2.getX(), event2.getY());
                isRotating=true;
                return false;
            }else{
                isRotating = true;
                updateRotation(event2.getX(), event2.getY());
                return false;
            }
        }
    }
    
    /**
     * event listener. Zooms in or out depending on the user's action
     */
    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener{
        //zooms in or out according to the scale detector value
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            if(detector.getScaleFactor()>1){
                if(earthSphere.getScaleX()*1.1<120){
                    earthSphere.setScaleX(earthSphere.getScaleX()*1.1);
                    earthSphere.setScaleY(earthSphere.getScaleY() * 1.1);
                    earthSphere.setScaleZ(earthSphere.getScaleZ() * 1.1);
                }
            }else{
                if(earthSphere.getScaleX()*0.9>0.95) {
                    earthSphere.setScaleX(earthSphere.getScaleX() * 0.9);
                    earthSphere.setScaleY(earthSphere.getScaleY() * 0.9);
                    earthSphere.setScaleZ(earthSphere.getScaleZ() * 0.9);
                }
            }
            return true;
        }
    
        //the zoom begins
        @Override
        public boolean onScaleBegin (ScaleGestureDetector detector) {
            isScaling = true;
            isRotating = false;
            return super.onScaleBegin(detector);
        }
    
        //the zoom ends
        @Override
        public void onScaleEnd (ScaleGestureDetector detector) {
            isRotating = false;
            isScaling = false;
        }
    }
    

    With all of this settled, you only have to set the orientation on each render, like this:

        yawAcumuladoR = (yawAcumulado) * 0.04;
        pitchAcumuladoR = (pitchAcumulado) * 0.04;
        Quaternion q = new Quaternion();
        q.fromEuler(yawAcumuladoR, pitchAcumuladoR, 0);
        earthSphere.setOrientation(q);
    

    As I've said, this works for me, but I'm only rotating the sphere. It shouldn't be difficult to addapt it to your needs, besides you have the Arcball class that is a camera and might be better for what you want. Anyway, I hope this is useful for you.