viromedia / virocore

ViroCore cross-platform AR/VR renderer
MIT License
363 stars 108 forks source link

Set camera lookat to a fixed point #30

Closed ssaxena0891 closed 6 years ago

ssaxena0891 commented 6 years ago

I am trying to set lookat of my camera to a fixed point of my model and then rotating my camera to view it from different angles. But the lookat doesn’t seems to be fixed and my model goes out of window when I move my camera. I am using below code to achieve this:

final Scene scene = new Scene();
    obj = new Object3D();
    Vector position = new Vector(0, -1, 0);
    obj.setPosition(position);

    obj.loadModel(Uri.parse("file:///android_asset/truck.obj"), Object3D.Type.OBJ, new AsyncObject3DListener() {
        public void onObject3DFailed(String error) {
            Log.w(TAG, "Failed to load the model");
        }

        public void onObject3DLoaded(Object3D object, Object3D.Type type) {
            Log.i(TAG, "Successfully loaded the model!");
            object.setScale(new Vector(0.2f, 0.2f, 0.2f));
        }
    });

    AmbientLight ambient = new AmbientLight(Color.WHITE, 1000.0f);
    scene.getRootNode().addLight(ambient);

    cameraNode = new Node();

    camera = new Camera();
    camera.setRotationType(Camera.RotationType.ORBIT);
    camera.setPosition(new Vector(0, 50, 3));
    cameraNode.setRotation(new Vector((float) 0, -Math.PI / 2.0f, 0));
    camera.setOrbitFocalPoint(new Vector(0, -1, 0));
    cameraNode.setCamera(camera);

    scene.getRootNode().addChildNode(cameraNode);
    scene.getRootNode().addChildNode(obj);
    mViroView.setPointOfView(cameraNode);

    mViroView.setScene(scene);

Now when I move my camera to a position like camera.setPosition(new Vector(0, 50, 40), my object moves somewhere out of the screen.

Please let me know if I am missing something.

jacobvyn commented 6 years ago

What is your implementation of mViroView reference? This shouldn't work with ViroViewARCore... https://virocore.viromedia.com/docs/camera

ssaxena0891 commented 6 years ago

Here mViroView is a reference of ViroViewScene. To be more clear, I am trying to load a model in a ViroViewScene and then want to view this loaded model in different angles similar to a bird-eye view by positioning the com.viro.core.camera to different angles on receiving touch actions by user.

dthian commented 6 years ago

Hi @ssaxena0891, currently with ViroCore, Orbit cameras effectively move the camera around the scene in order to enforce a view at the provided orbitForcalPoint, hence you always "look at that point" irregardless of where you move or rotate the device in VR. As such, these cameras control its rotation/positioning to provide that orbital experience - we shouldn't be setting an additional angle rotation offset / positional offset for these cameras, other than it's focal point.

Instead, I'd recommend using a normal Camera for your use case above, for viewing your 3D model and positioning the camera from a bird-eye view based on a user's touch events. This gives you complete control over camera placement and rotation. You can then set these properties based on touch events.

Else if you would like your users to look around the world by rotating their device, I'd recommend using ViroViewGVR instead, as that would automatically pull in the head rotational mechanics needed for an orbital camera to work. After that, i would also remove any set position or rotational code so that the Orbit camera can have full control over it's own properties.

Hope that helps, let me know if you are having any difficulties with this.

ssaxena0891 commented 6 years ago

Hi @dthian, I am not trying any AR/ VR stuff here. I just want to load an OBJ file using ViroCore to ViroViewScene in my mobile just like what sceneKit does for iOS. Now to this scene I set a camera like this (lets forget about orbit camera): cameraNode = new Node(); camera = new Camera(); camera.setPosition(new Vector(5, 5, 5)); cameraNode.setCamera(camera);

Lets suppose my model is at (0, 0, 0). Now when I move the camera to a position like (5, 10, 5), it doesn't look at the point (0, 0, 0) where the model is placed. For this, in iOS they have a LookAt constraint on node which fixes the focus point for the camera, thus setting any position to the camera, the model always is in the center.

In iOS it works like this: Initial Position: initial_position

Final Position: final_position

dthian commented 6 years ago

Hi @ssaxena0891, thanks for explaining! To confirm, you are trying to set a LookAt constraint on the camera, such that it always faces the focus point as defined by that constraint? For example, if you move the camera from (5,5,5) to (5,10, 5) with a lookAt (0,0,0). In both positions it should look at (0,0,0). Is my understanding correct?

Unfortunately we currently do not have an API that supports this features. I've added it to the backlog and we'll work at in future releases. However, I do have a possible mitigation alternative as show in the code sample below. Here, we can call setCameraPosition with the desired camera position and look at position. We effectively apply a corrected rotation to the camera, based on the new forward direction that faces the lookAt position constraint (in your case, (0,0,0)).

    public void setCameraPosition(Vector cameraPosition, Vector lookAtPosition){
        // Grab new forward vectors facing the lookAtPosition for our Camera
        Vector cameraLastForward = mViroView.getLastCameraForwardRealtime();
        Vector cameraNewForward = lookAtPosition.subtract(cameraPosition);

        // Calculate and apply the needed rotation delta to be applied to our Camera.
        Quaternion rotationDelta = Quaternion.makeRotationFromTo(
                cameraLastForward.normalize(), cameraNewForward.normalize());
        Vector newRot = rotationDelta.toEuler().add(mViroView.getLastCameraRotationEulerRealtime());

        // Animate the Camera to the desired point (optional, you can also just set the position).
        AnimationTransaction.begin();
        AnimationTransaction.setAnimationDuration(2000);
        AnimationTransaction.setTimingFunction(AnimationTimingFunction.Linear);
        mMainCamera.setPosition(cameraPosition);
        mMainCamera.setRotation(new Quaternion(newRot));
        AnimationTransaction.commit();
    }

I've verified that this works with an animation. You can also just set the position of the camera without an animation if you'd like. Let me know if you have difficulties in getting this to work.

ssaxena0891 commented 6 years ago

Hi @dthian, thanks for the above solution. Now the lookAt constraint seems to work but when I am using above setCameraPosition method and passing camera position(5,5,5) and lookAt (0, 0, 0), my model looks to be rotated to an angle. Please see below attached image:

device-2018-03-06-155354

Expected Result: Model should not be rotated. Camera should be at (5, 5, 5) and should be looking at (0, 0, 0).

dthian commented 6 years ago

Hi @ssaxena0891, could you include here any camera code pertaining to camera movement (especially its initial and final rotation and orientation), or on how you are moving the camera with the look at function here, so that we can try and reproduce your issue, thanks!

ssaxena0891 commented 6 years ago

Hi @dthian, How can I apply gimbal lock constraint on a node using ViroCore? May be it resolve this issue.

dthian commented 6 years ago

Hi @ssaxena0891, to better answer your questions, could you include here any camera code pertaining to camera movement (especially its initial and final rotation and orientation), and on how you are moving the camera with the look at function here, so that we can try and reproduce your issue?

Unfortunately, ViroCore doesn't currently have a gimbal lock API. However, without any provided Java camera code representing the scene above, as requested above, I can not yet ascertain the root cause of the issue that you are experiencing above, or if a gimbal lock constraint would even be a viable solution.

ssaxena0891 commented 6 years ago

Hi @dthian, I am using below code for camera rotation:

private Camera camera;
private ViroViewScene mViroView;
private float posX;
private Object3D object3D;

public void onRendererStart() {
    final Scene scene = new Scene();
    object3D = new Object3D();
    Vector position = new Vector(0, 0, 0);
    object3D.setPosition(position);
    object3D.setRotation(new Vector((float) Math.PI / 2.0f, 0, 0));

    object3D.loadModel(Uri.parse("file:///android_asset/model.obj"), Object3D.Type.OBJ, new AsyncObject3DListener() {
        public void onObject3DFailed(String error) {
            Log.w(TAG, "Failed to load the model");
        }

        public void onObject3DLoaded(Object3D object, Object3D.Type type) {
            Log.i(TAG, "Successfully loaded the model!");
        }
    });

    AmbientLight ambient = new AmbientLight(Color.WHITE, 100f);
    scene.getRootNode().addLight(ambient);

    camera = new Camera();
    setCameraPosition(new Vector(5, 5, 5), new Vector(0, 0, 0));
    Node cameraNode = new Node();
    cameraNode.setCamera(camera);

    mViroView.setPointOfView(cameraNode);

    scene.getRootNode().addChildNode(cameraNode);
    scene.getRootNode().addChildNode(object3D);

    mViroView.setScene(scene);
    mViroView.setDebug(true);
}

/**
 * @param dx distance moved along x-axis
 * @param dy distance moved along y-axis
 */
private void onTouch(float dx, float dy) {
    if (dx != 0 || dy != 0) {
        Vector cameraPosition = camera.getPosition();
        if (abs(dy) > abs(dx)) {
            // user moving his finger vertically

            // calculate new camera position
            float radius = (float) sqrt(cameraPosition.x * cameraPosition.x + cameraPosition.y * cameraPosition.y + cameraPosition.z * cameraPosition.z);
            float alpha = cameraPosition.x / cameraPosition.z;
            cameraPosition.y = (float) min(max(cameraPosition.y + dy, radius / 3), radius / 1.1);
            if (cameraPosition.z < 0) {
                cameraPosition.z = (float) sqrt((pow(radius, 2) - cameraPosition.y * cameraPosition.y) / (1 + (alpha * alpha)));
                cameraPosition.z = -abs(cameraPosition.z);
            } else {
                cameraPosition.z = (float) sqrt((pow(radius, 2) - cameraPosition.y * cameraPosition.y) / (1 + (alpha * alpha)));
                cameraPosition.z = abs(cameraPosition.z);
            }
            cameraPosition.x = alpha * cameraPosition.z;

            // set new camera position with looAt(0, 0, 0)
            setCameraPosition(new Vector(cameraPosition.x, cameraPosition.y, cameraPosition.z), new Vector(0, 0, 0));
        } else {
            // user moving his finger horizontally

            // calculate new camera position
            float angle = (float) ((dx * 5) * Math.PI / 180);
            posX = cameraPosition.x;
            cameraPosition.x = (float) (cameraPosition.x * cos(angle) - cameraPosition.y * sin(angle));
            cameraPosition.y = (float) (posX * sin(angle) + cameraPosition.y * cos(angle));

            // set new camera position with looAt(0, 0, 0)
            setCameraPosition(new Vector(cameraPosition.x, cameraPosition.y, cameraPosition.z), new Vector(0, 0, 0));
        }
    }
}

public void setCameraPosition(Vector cameraPosition, Vector lookAtPosition){
    // Grab new forward vectors facing the lookAtPosition for our Camera
    Vector cameraLastForward = mViroView.getLastCameraForwardRealtime();
    Vector cameraNewForward = lookAtPosition.subtract(cameraPosition);

    // Calculate and apply the needed rotation delta to be applied to our Camera.
    Quaternion rotationDelta = Quaternion.makeRotationFromTo(
            cameraLastForward.normalize(), cameraNewForward.normalize());
    Vector newRot = rotationDelta.toEuler().add(mViroView.getLastCameraRotationEulerRealtime());

    camera.setPosition(cameraPosition);
    camera.setRotation(new Quaternion(newRot));
}

I want below results on user's up/down and left/right swipe gesture:

swiping_horizontally swiping_up_down

ssaxena0891 commented 6 years ago

Hi @dthian, are you able to reproduce the issue using above code?

dthian commented 6 years ago

Hi @ssaxena0891, thanks for your patience. Yup, i was able to parametrize a sphere to simulate rotating the camera around the building in a circle, while looking with a fixed point.

Unfortunately, the setCameraPosition(location, focal) itself was not behaving as expected - it would rotate the to look at the location point, but with introduced roll rotation. This is may because there are several ways to perform the fromTo() rotation to get to the final directional vector, and that in itself introduces undesired roll in the xz plane.

Apologies for the lack of an orbit Camera feature in a ViroSceneView - as soon as a fix has been pushed to release, I'll update this github issue.

dthian commented 6 years ago

Hi @ssaxena0891, I've managed to hack together a potential code mitigation as shown below. Here, the code demonstrates us positioning the camera on a parametrized sphere.

To do that, we firstly place the camera in reference to the user's drag: Firstly, we grab the dx and dy values that represents the Touchscreen's dragged x and y values. We then normalize them into phi and theta values to be used as spherical coordinates (how much the user has "rotated" based on drag). We then use the parametrize spherical coordinates to grab the cartesian coordinates for the given theta and phi, so that we can place the camera - this effectively places the camera in a "spherical" position around 0,0,0. Note that we didn't apply any translations here, but you should be able to translate them after (if your point isn't 0,0,0).

We then rotate the camera towards the look at position: We determine the direction vector, and calculate the yaw and pitch needed to build a quaternion to achieve that direction vector.

Please Note: that this code is not fully vetted for corner situations, and is merely served to act as a proof of concept while we work on a more permanent solution. We'll update this github as soon as the Camera look at feature has been pushed into release.

The result of the code blow can be seen here: ezgif-4-f2f5073c53

And the code here:

@Override
    public boolean onTouch(View v, 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:
                mOldTouchPos = newTouchPos;
            case MotionEvent.ACTION_MOVE:
                // Determine normalized dx and dy from touch positions.
                float dx = newTouchPos.x - mOldTouchPos.x;
                float dy = newTouchPos.y - mOldTouchPos.y;

                // Normalized Coordinates (use Android's get screen width / height here to replace numbers).
                float normalizedFingerMovedX = ((float)-dx)/1079;
                float normalizedFingerMovedY = ((float)-dy)/1920;

                // Normalize touch dx into an angle dTheta.
                float rateOfChangeTheta = 30; // Change this to any speed you'd like.
                lastKnownDeltaTheta = normalizedFingerMovedX * rateOfChangeTheta;
                double theta = thetaAngleStart + lastKnownDeltaTheta;

                // Normalize touch dy into an angle dPhi .
                float rateOfChangePhi = 30; // Change this to any speed you'd like.
                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 camZ = radiusConst * Math.cos(Math.toRadians(clampedTheta)) * Math.sin(Math.toRadians(clampedPhi));
                double camX = radiusConst * Math.sin(Math.toRadians(clampedTheta)) * Math.sin(Math.toRadians(clampedPhi));
                double camY = radiusConst * Math.cos(Math.toRadians(clampedPhi));
                setCameraPosition(new Vector(camX, camY, camZ), new Vector(0, 0, 0));
                break;
            case MotionEvent.ACTION_UP:
                thetaAngleStart = thetaAngleStart + lastKnownDeltaTheta;
                phiAngleStart = phiAngleStart + lastKnownDeltaPhi;
        }

        return false;
    }

    public void setCameraPosition(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);
        cameraNode.setRotation(quartEuler);
        cameraNode.setPosition(cameraPosition);
    }
hvmien commented 6 years ago

@dthian Hello sir, im facing with problem this. can you public code? thanks pro. EDIT: I got it. thanks pro.

dieter115 commented 6 years ago

on which view do you add the ontouchlistener?

dthian commented 6 years ago

Hey @dieter115, i think it was on ViroView, of which really is an Android FrameLayout view.

marcspraragen commented 5 years ago

Hi--

This discussion has taught me several things. I have a question: is there a way to generalize the behavior for non-camera nodes? Like a general SceneKit "lookAt" function.

My case is a 3D object node turning to always look at a moving invisible parent node, while also walking towards the parent.

The parent node is a billboard_X child of camera/root node.

So as the camera moves, the parent node moves with it, and the 3D object should turn/move (at a slower pace than the camera).

Thanks!

marcspraragen commented 5 years ago

I have a workaround for a moving guide point in a ViroCore AR scene. Using a camera raycast (performSceneHitTestWithPoint, high accuracy) from center of device screen to intersect (by tag) a virtual invisible sphere that moves in the scene with the camera node.

The found intersection point remains vertically level in the scene at y=0.

public void findGuidePoint() {

        cameraPos.x = mViroView.getLastCameraPositionRealtime().x;
        cameraPos.z = mViroView.getLastCameraPositionRealtime().z;
        sphereNode.setPosition(new Vector(cameraPos.x, 0, cameraPos.z));

        mViroView.performSceneHitTestWithPoint(new Point(mViroView.getWidth() / 2, mViroView.getHeight() / 2), true, new HitTestListener() {

            @Override
            public void onHitTestFinished(HitTestResult[] hitTestResults) {

                for (HitTestResult htr : hitTestResults) {

                    if (htr.getTag() != null && htr.getTag().equals(GUIDE_SPHERE_TAG)) {
                        Vector newGPos = new Vector(htr.getIntersectionPoint().x, 0, htr.getIntersectionPoint().z);
              }//if
            }//for
       }//onHitTestFinished
   ); //performSceneHitTestWithPoint
} //findGuidePoint
arunavsikdershuvo commented 3 years ago

Can anyone give me any idea about how can I achieve this - https://drive.google.com/file/d/15QnyYp7HVwzNFhpVxLfZAgyOdxL3GF2z/view?usp=sharing?

This issue is basically fix the model in centre and rotate camera 360. But I just need to move camera just like dragging/scrolling. I just need the implementation procedure. Can anyone please help? It's urgent.