virocore icon indicating copy to clipboard operation
virocore copied to clipboard

Set camera lookat to a fixed point but without vertical invertion

Open soajob opened this issue 5 years ago • 5 comments

My environment:

  1. OS: Linux Manjaro
  2. Version: virocore-release-v_1_11_0.aar
  3. Device: General Mobile GM5Plus

I'm trying to create an Android application with ViroScene (no AR/VR). I have a 3D model which must be rotated by 360 degrees. I tried to use code from "Set camera lookat to a fixed point #30" suggested in the end by dthian:

package md.starlab.myvirocoreapp_1.thumb_up;

import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import com.viro.core.AsyncObject3DListener;
import com.viro.core.Camera;
import com.viro.core.DirectionalLight;
import com.viro.core.Node;
import com.viro.core.Object3D;
import com.viro.core.Quaternion;
import com.viro.core.Scene;
import com.viro.core.Text;
import com.viro.core.Vector;
import com.viro.core.ViroView;
import com.viro.core.ViroViewScene;

import md.starlab.myvirocoreapp_1.utils.EventPos;

public class ThumbUpActivity extends AppCompatActivity {

    private final String TAG = ThumbUpActivity.class.getSimpleName();

    private final int ROTATE_SPEED      = 100;

    private final float LIGHT_X_POS     = 0;
    private final float LIGHT_Y_POS     = 0;
    private final float LIGHT_INTENSITY = 500.0f;
    private final float RADIUS          = 2f;

    private final Vector THUMB_UP_POS_VECTOR = new Vector(0,
                                                           0,
                                                           0);

    private final Vector FRONT_LIGHT_VECTOR = new Vector(   LIGHT_X_POS,
                                                            LIGHT_Y_POS,
                                                            -(RADIUS/2));

    private final Vector BACK_LIGHT_VECTOR  = new Vector(   LIGHT_X_POS,
                                                            LIGHT_Y_POS,
                                                            (RADIUS/2));

    private final Vector ABOVE_LIGHT_VECTOR = new Vector(   LIGHT_X_POS,
                                                            -(RADIUS/2),
                                                            0);

    private final Vector BELOW_LIGHT_VECTOR = new Vector(   LIGHT_X_POS,
                                                            (RADIUS/2),
                                                            0);

    private final Vector LEFT_LIGHT_VECTOR  = new Vector(   -(RADIUS/2),
                                                            LIGHT_Y_POS,
                                                            0);

    private final Vector RIGHT_LIGHT_VECTOR = new Vector(   (RADIUS/2),
                                                            LIGHT_Y_POS,
                                                            0);


    private final Vector CAMERA_ROTATION    = new Vector(   0,
                                                            0,
                                                            0);

    private final Vector CAMERA_POS         = new Vector(   0,
                                                            0,
                                                            RADIUS);

    private Camera      camera;
    private Node        rootNode;
    private Scene       scene;
    private ViroView    view;

    private Object3D    thumbUp;

    private EventPos    previousTouchPos;

    private float       lastKnownDeltaPhi;
    private float       lastKnownDeltaTheta;

    private double      phiAngleStart   = 90;
    private double      thetaAngleStart = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        view = new ViroViewScene(this,
                                    startupListener);
        setContentView(view);
    }

    @Override
    protected void onStart() {
        super.onStart();
        view.onActivityStarted(this);
        view.setOnTouchListener(thumbUpTouchListener);
    }

    @Override
    protected void onResume() {
        super.onResume();
        view.onActivityResumed(this);
    }

    @Override
    protected void onPause() {
        super.onPause();
        view.onActivityPaused(this);
    }

    @Override
    protected void onStop() {
        super.onStop();
        view.onActivityStopped(this);
        view.setOnTouchListener(null);
    }

    ///////////////////////////////// GETTERS ////////////////////////////////////////

    private int[] getScreenSize() {
        DisplayMetrics displayMetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
        return new int[] {displayMetrics.widthPixels, displayMetrics.heightPixels};
    }

    private Node getXHandleNode(int value) {
        Text XHandle = getHandle(Color.RED);
        XHandle.setText("" +value+"x");

        Node XHandleNode = new Node();
        XHandleNode.setPosition(new Vector(value, 0, Math.PI/4));
        XHandleNode.setGeometry(XHandle);

        return XHandleNode;
    }

    private Node getYHandleNode(int value) {
        Text YHandle = getHandle(Color.GREEN);
        YHandle.setText("" +value+"y");

        Node YHandleNode = new Node();
        YHandleNode.setPosition(new Vector(0, value, 0));
        YHandleNode.setGeometry(YHandle);

        return YHandleNode;
    }

    private Node getZHandleNode(int value) {
        Text ZHandle = getHandle(Color.YELLOW);
        ZHandle.setText("" +value+"z");

        Node ZHandleNode = new Node();
        ZHandleNode.setPosition(new Vector(0, 0, value));
        ZHandleNode.setGeometry(ZHandle);

        return ZHandleNode;
    }

    private Text getHandle(int colorValue) {
        return new Text.TextBuilder().viroContext(  view.getViroContext()).
                fontFamilyName("Roboto").fontSize(30).
                color(colorValue).
                width(50).height(10).
                lineBreakMode(Text.LineBreakMode.NONE).
                maxLines(1).build();
    }

    ///////////////////////////////// LISTENERS //////////////////////////////////////

    ViroViewScene.StartupListener startupListener = new ViroViewScene.StartupListener() {
        @Override
        public void onSuccess() {
            showThumbUp();
        }

        @Override
        public void onFailure(ViroViewScene.StartupError error, String errorMessage) {
            // Fail as you wish!
        }
    };

    View.OnTouchListener thumbUpTouchListener = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent event) {
            int x = (int)event.getX();
            int y = (int)event.getY();

            EventPos newTouchPos = new EventPos(x,y);
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    previousTouchPos = newTouchPos;
                case MotionEvent.ACTION_MOVE:
                    // Determine normalized dx and dy from touch positions.
                    float dx = newTouchPos.getX() - previousTouchPos.getY();
                    float dy = newTouchPos.getY() - previousTouchPos.getY();

                    int[] screenSize = getScreenSize();

                    // Normalized Coordinates
                    float normalizedFingerMovedX = ((float)-dx)/screenSize[0]; //SCREEN_WIDTH;
                    float normalizedFingerMovedY = ((float)-dy)/screenSize[1]; //SCREEN_HEIGHT;

                    // Normalize touch dx into an angle dTheta.
                    float rateOfChangeTheta = ROTATE_SPEED;
                    lastKnownDeltaTheta = normalizedFingerMovedX * rateOfChangeTheta;
                    double theta = thetaAngleStart + lastKnownDeltaTheta;

                    // Normalize touch dy into an angle dPhi .
                    float rateOfChangePhi = ROTATE_SPEED;
                    lastKnownDeltaPhi = normalizedFingerMovedY * rateOfChangePhi;
                    double phi = phiAngleStart + lastKnownDeltaPhi;

                    // Determine if values should be clamped and clamp them.
                    // Note that lastKnown delta + phi datas are still saved above irregardless of clamp.
                    // Consider saving them after the fact if needed.
                    double clampedTheta = theta % 360;
                    if (theta < 0){
                        clampedTheta = 360  + theta;
                    }

                    double clampedPhi = phi % 360;
                    /*
                    if (phi < 0){
                        clampedPhi = 0;
                    }
                    */

                    // Parametrize the camera's location onto a sphere based on current phi and theta values.
                    double camX = RADIUS * Math.sin(Math.toRadians(clampedTheta)) * Math.sin(Math.toRadians(clampedPhi)); // (y)
                    double camY = RADIUS * Math.cos(Math.toRadians(clampedPhi)); // (z)
                    double camZ = RADIUS * Math.cos(Math.toRadians(clampedTheta)) * Math.sin(Math.toRadians(clampedPhi)); // (x)

                    chgCameraPosition(  new Vector( camX,
                                                    camY,
                                                    camZ),
                                        THUMB_UP_POS_VECTOR);
                    break;
                case MotionEvent.ACTION_UP:
                    thetaAngleStart = thetaAngleStart   + lastKnownDeltaTheta;
                    phiAngleStart   = phiAngleStart     + lastKnownDeltaPhi;
            }

            return false;
        }
    };

    ///////////////////////////////// OTHER //////////////////////////////////////////

    private void showThumbUp() {
        scene       = new Scene();
        rootNode    = scene.getRootNode();

        //////////////////////////////////////////////////////////////////

        rootNode.addLight(new DirectionalLight( Color.WHITE,
                                                LIGHT_INTENSITY,
                                                FRONT_LIGHT_VECTOR));
        rootNode.addLight(new DirectionalLight( Color.WHITE,
                                                LIGHT_INTENSITY,
                                                BACK_LIGHT_VECTOR));
        rootNode.addLight(new DirectionalLight( Color.WHITE,
                                                LIGHT_INTENSITY,
                                                ABOVE_LIGHT_VECTOR));
        rootNode.addLight(new DirectionalLight( Color.WHITE,
                                                LIGHT_INTENSITY,
                                                BELOW_LIGHT_VECTOR));
        rootNode.addLight(new DirectionalLight( Color.WHITE,
                                                LIGHT_INTENSITY,
                                                RIGHT_LIGHT_VECTOR));
        rootNode.addLight(new DirectionalLight( Color.WHITE,
                                                LIGHT_INTENSITY,
                                                LEFT_LIGHT_VECTOR));

        //////////////////////////////////////////////////////////////////

        thumbUp = new Object3D();
        thumbUp.loadModel( view.getViroContext(),
                Uri.parse("file:///android_asset/thumb_up/thumb_up.vrx"),
                Object3D.Type.FBX,
                new AsyncObject3DListener() {
                    public void onObject3DFailed(String error) {
                        Log.e(TAG, "Failed to load the model");
                    }
                    public void onObject3DLoaded(Object3D object, Object3D.Type type) {
                        Log.e(TAG, "Successfully loaded the model of type: " +type.name());
                    }
                }
        );
        //thumbUp.setRotation(new Vector(0,Math.PI/6,0));

        final Node thumbUpNode = new Node();
        thumbUpNode.addChildNode(thumbUp);
        thumbUpNode.setPosition(THUMB_UP_POS_VECTOR);

        //////////////////////////////////////////////////////////////////

        for(int i=-10; i<10; i++) {
            thumbUpNode.addChildNode(getXHandleNode(i));
            thumbUpNode.addChildNode(getYHandleNode(i));
            thumbUpNode.addChildNode(getZHandleNode(i));
        }

        rootNode.addChildNode(thumbUpNode);

        //////////////////////////////////////////////////////////////////

        camera = new Camera();
        camera.setRotation(CAMERA_ROTATION);
        camera.setPosition(CAMERA_POS);

        Node cameraNode = new Node();
        cameraNode.setCamera(camera);
        rootNode.addChildNode(cameraNode);

        //////////////////////////////////////////////////////////////////

        view.setScene(scene);
        view.setPointOfView(cameraNode);
    }

    private void chgCameraPosition(Vector cameraPosition, Vector lookAtPosition){
        Vector dirVector = lookAtPosition.subtract(cameraPosition);

        Vector dirVectorNorm = lookAtPosition.subtract(cameraPosition).normalize();
        Vector globalForward = new Vector(0,0,-1);
        Vector globalUp = new Vector(0,1,0);

        // Calculate the Camera's Yaw from direction vector.
        Vector dirVectorNormNoY = new Vector(dirVector.x, 0, dirVector.z);
        double theta = Math.acos(dirVectorNormNoY.normalize().dot(globalForward.normalize()));
        if (dirVectorNorm.x > 0){
            theta =  Math.toRadians(360) - theta;
        }

        // Calculate the Camera's pitch from direction vector.
        double phi = (Math.acos(dirVector.normalize().dot(globalUp.normalize())) -  Math.toRadians(90))*-1;

        // Apply rotation and position
        Quaternion quartEuler = new Quaternion((float)phi, (float)theta, 0);

        camera.setRotation(quartEuler);
        camera.setPosition(cameraPosition);
    }
}

But I faced with vertical invertion when phi becomes negative or greater than 180. This is a problem which I can't understand how to resolve. I ask for your help.

soajob avatar Dec 10 '18 12:12 soajob

You can see how it works now:

thumb_up_rotate_1 thumb_up_rotate_2

soajob avatar Dec 12 '18 12:12 soajob

Hi @sergey-ostrovsky, Have you tried to remove the clamping from the code above to see if that fixes the issue?

VikAdvani avatar Dec 15 '18 02:12 VikAdvani

I've tried to use phi and theta instead of clampedPhi and clampedTheta, but this doesn't resolve the problem:

// Parametrize the camera's location onto a sphere based on current phi and theta values. double camX = RADIUS * Math.sin(Math.toRadians(clampedTheta)) * Math.sin(Math.toRadians(clampedPhi)); // (y) double camY = RADIUS * Math.cos(Math.toRadians(clampedPhi)); // (z) double camZ = RADIUS * Math.cos(Math.toRadians(clampedTheta)) * Math.sin(Math.toRadians(clampedPhi)); // (x)

double camX = RADIUS * Math.sin(Math.toRadians(theta)) * Math.sin(Math.toRadians(phi)); // (y) double camY = RADIUS * Math.cos(Math.toRadians(phi)); // (z) double camZ = RADIUS * Math.cos(Math.toRadians(theta)) * Math.sin(Math.toRadians(phi)); // (x)

soajob avatar Dec 17 '18 09:12 soajob

I've tried to change rotate direction..if phi cross boundary values (0 and 180):

Quaternion quartEuler = new Quaternion((float)phi, (float)theta, 0); if (chgDirection) { quartEuler.w = ((1 - quartEuler.w) / 4) * -1; }

But no luck.

soajob avatar Dec 17 '18 09:12 soajob

I've tried to play with z axis:

Vector dirVectorNormNoY = new Vector(dirVector.x, 0, dirVector.z); if(chgDirection) { // new Vector(dirVector.x, Math.PI, dirVector.z); dirVectorNormNoY = new Vector(dirVector.x, 0, -dirVector.z); // globalForward = new Vector(0,0,1); // dirVectorNormNoY = new Vector(dirVector.x, 0, (Math.PI+dirVector.z)); // dirVectorNormNoY = new Vector(dirVector.x, 0, (-dirVector.z+Math.PI)); }

Looks like this is near..but I faced with rotation angle problem. Maybe we have to consider change rotate direction in quaternion.w too?

soajob avatar Dec 17 '18 09:12 soajob