virocore
virocore copied to clipboard
Set camera lookat to a fixed point but without vertical invertion
My environment:
- OS: Linux Manjaro
- Version: virocore-release-v_1_11_0.aar
- 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.
You can see how it works now:
Hi @sergey-ostrovsky, Have you tried to remove the clamping from the code above to see if that fixes the issue?
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)
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.
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?