在 OpenGL ES 2.0/Rajawali 中,如何避免用户旋转球体时倒置?
How to avoid a sphere from turning upside down when user rotates it, in OpenGL ES 2.0/ Rajawali?
我正在尝试为 Android 制作一个 360 视频播放器应用程序。到目前为止,我已经设法创建了一个球体并将视频投射到它上面。
我还有一个跟随球体的 Arcball 相机,以处理用户触摸事件和对象旋转。
我已经扩展了 Rajawali 的 ArcballCamera
class 以对其进行一些更改,因为我不喜欢视频在旋转球体时所做的一些奇怪的动作。我已经成功地改变了垂直和水平旋转方向。
问题是,如果用户向上滚动,当您经过球体的极点时,视频就会颠倒过来。
此外,根据球体的旋转方式(我不确定确切原因是什么),视频也会倾斜。
我想让用户向上和向下、向后和向前看,但我总是希望北方的视频杆向上,
不知道自己解释的好不好
我只能 post 两个链接,所以这是应用程序的屏幕截图 - 当用户继续向上或向下滚动通过球体的极点时会发生这种情况:
我正在使用四元数进行旋转,因为它们是用旋转轴和旋转角度制成的,所以我首先尝试将 x 轴或 z 轴保持在 0,但这没有用.
我已经搜索过类似的东西,但找不到出路。
这是 ArcballCamera
class 代码(仅与旋转相关的内容):
public class mArcballCamera extends ArcballCamera{
/*...*/
/*with the given x and y coordinates returns a 3D vector with x,y,z sphere coordinates*/
private void mapToSphere(float x, float y, Vector3 out) {
float lengthSquared = x * x + y * y;
if(lengthSquared > 1.0F) {
out.setAll((double)x, (double)y, 0.0D);
out.normalize();
} else {
out.setAll((double)x, (double)y, Math.sqrt((double)(1.0F - lengthSquared)));
}
}
/**with the given x and y coordinates returns a 2D vector with x,y screen coordinates*/
private void mapToScreen(float x, float y, Vector2 out) {
out.setX((double)((2.0F * x - (float)this.mLastWidth) / (float)this.mLastWidth));
out.setY((double)(-(2.0F * y - (float)this.mLastHeight) / (float)this.mLastHeight));
}
/**maps initial x and y touch event coordinates to <mPrevScreenCoord/> and then copies it to
* mCurrScreenCoord */
private void startRotation(float x, float y) {
this.mapToScreen(x, y, this.mPrevScreenCoord);
this.mCurrScreenCoord.setAll(this.mPrevScreenCoord.getX(), this.mPrevScreenCoord.getY());
this.mIsRotating = true;
}
/**updates <mCurrScreenCoord/> to new screen mapped x and y and then applies rotation*/
private void updateRotation(float x, float y) {
this.mapToScreen(x, y, this.mCurrScreenCoord);
this.applyRotation();
}
/** applies the rotation to the target object*/
private void applyRotation() {
if(this.mIsRotating) {
//maps to sphere coordinates previous and current position
this.mapToSphere((float) this.mPrevScreenCoord.getX(), (float) this.mPrevScreenCoord.getY(), this.mPrevSphereCoord);
this.mapToSphere((float) this.mCurrScreenCoord.getX(), (float) this.mCurrScreenCoord.getY(), this.mCurrSphereCoord);
//rotationAxis is the crossproduct between the two resultant vectors (normalized)
Vector3 rotationAxis = this.mPrevSphereCoord.clone();
rotationAxis.cross(this.mCurrSphereCoord);
rotationAxis.normalize();
//rotationAngle is the acos of the vectors' dot product
double rotationAngle = Math.acos(Math.min(1.0D, this.mPrevSphereCoord.dot(this.mCurrSphereCoord)));
//creates a quaternion using rotantionAngle and rotationAxis (normalized)
this.mCurrentOrientation.fromAngleAxis(rotationAxis, MathUtil.radiansToDegrees(rotationAngle));
this.mCurrentOrientation.normalize();
//accumulates start and current orientation in mEmpty object
Quaternion q = new Quaternion(this.mStartOrientation);
q.multiply(this.mCurrentOrientation);
double orientacionX = q.angleBetween(new Quaternion(0f,0f,1f,0f));
this.mEmpty.setOrientation(q);
}
}
/** adds the basic listeners to the camera*/
private void addListeners() {
//runs this on the ui thread
((Activity)this.mContext).runOnUiThread(new Runnable() {
public void run() {
//sets a gesture detector (touch)
mArcballCamera.this.mDetector = new GestureDetector(mArcballCamera.this.mContext, mArcballCamera.this.new GestureListener());
//sets a scale detector (zoom)
mArcballCamera.this.mScaleDetector = new ScaleGestureDetector(mArcballCamera.this.mContext, mArcballCamera.this.new ScaleListener());
//sets a touch listener
mArcballCamera.this.mGestureListener = new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
//sees if it is a scale event
mArcballCamera.this.mScaleDetector.onTouchEvent(event);
if(!mArcballCamera.this.mIsScaling) {
//if not, delivers the event to the movement detector to start rotation
mArcballCamera.this.mDetector.onTouchEvent(event);
if(event.getAction() == 1 && mArcballCamera.this.mIsRotating) {
//ends the rotation if the event ended
mArcballCamera.this.endRotation();
mArcballCamera.this.mIsRotating = false;
}
}
return true;
}
};
//sets the touch listener
mArcballCamera.this.mView.setOnTouchListener(mArcballCamera.this.mGestureListener);
}
});
}
/*gesture listener*/
private class GestureListener extends GestureDetector.SimpleOnGestureListener {
private GestureListener() {
}
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY) {
if(!mArcballCamera.this.mIsRotating) {
mArcballCamera.this.originalX=event2.getX();
mArcballCamera.this.originalY=event2.getY();
mArcballCamera.this.startRotation(mArcballCamera.this.mLastWidth/2, mArcballCamera.this.mLastHeight/2); //0,0 es la esquina superior izquierda. Buscar centro camara en algun lugar
return false;
} else {
float x = Math.abs((mArcballCamera.this.originalX - event2.getX() + mArcballCamera.this.mLastWidth/2)%mArcballCamera.this.mLastWidth);
float y = Math.abs((mArcballCamera.this.originalY - event2.getY() + mArcballCamera.this.mLastHeight/2)%mArcballCamera.this.mLastHeight);
mArcballCamera.this.mIsRotating = true;
mArcballCamera.this.updateRotation(x, y);
return false;
}
}
}
如果你想自己看,这里是项目的Github repository。我主要在 Samsung Galaxy Tab4 和 Xperia Z 上测试过它。
如果你看到代码,我也尝试通过在手势侦听器的 onScroll
方法中将起点移动到屏幕中心来改变旋转,但这也没有用(虽然改变了移动方向,我也想这样做)并留下了一些不需要的副作用。
那么,有没有办法平衡球体,使北极始终指向上方,而用户可以四处移动?任何可以帮助我找到正确方向的东西都会不客气。
我正在开发 Android Studio,主要使用 Rajawali(基于 OpenGL ES 2.0 构建的库)
如果您需要更多信息,请告诉我,我会编辑问题。另外,如果英语不好,没有很好地解释自己,我会努力做得更好。
好吧,我设法解决了问题。我在这里发布答案,希望有一天能对某人有用。
我现在使用 Euler angles(Tait-Bryan ones). These have a problem called the Gimbal lock 但更容易理解,而不是使用四元数来限制旋转。
为了避免 Gimbal lock 和旧系统的重大变化,我首先计算每个旋转的欧拉角,然后从中创建一个四元数。从四元数到欧拉角的转换不是唯一的,所以要小心。另外,请记住,您需要存储累积的旋转,以便新旋转始终是绝对的,始终围绕初始参考系统。
根据用户在屏幕上滚动的 X 轴和 Y 轴的距离,我得到球体必须绕 x 轴和 y 轴旋转的角度。我在屏幕上设置了 3 个完整的水平滚动意味着球体中的 360º 运动。为了避免滑动问题,我将滚动(沿 Z 轴旋转,相机面向的方向)设置为 0。避免视频上下颠倒只是裁剪俯仰(沿 X 轴面向旋转)的问题右)从 -PI/2 到 PI/2.
注意直接使用屏幕滚动的距离而不是球体的坐标也解决了我之前得到的奇怪的旋转方向。
这是更新后的代码:
private void startRotation(final float x, final float y){
mapToScreen(x, y, mPrevScreenCoord);
mCurrScreenCoord.setAll(mPrevScreenCoord.getX(), mPrevScreenCoord.getY());
mIsRotating = true;
this.xAnterior=x;
this.yAnterior=y;
}
private void applyRotation(float x, float y){
this.gradosxpixelX = gbarridoX/mLastWidth;
this.gradosxpixelY = gbarridoY/mLastHeight;
double gradosX = (x - this.xAnterior)*this.gradosxpixelX; //rotation around Y axis - yaw
double gradosY = (y - this.yAnterior)*this.gradosxpixelY; //rotation around X axis - pitch
if(this.mIsRotating) {
this.mCurrentOrientation.fromEuler(gradosX, gradosY, 0);
this.mCurrentOrientation.normalize();
Quaternion q = new Quaternion(this.mStartOrientation);
q.multiply(this.mCurrentOrientation);
this.mEmpty.setOrientation(q);
}
}
我正在尝试为 Android 制作一个 360 视频播放器应用程序。到目前为止,我已经设法创建了一个球体并将视频投射到它上面。 我还有一个跟随球体的 Arcball 相机,以处理用户触摸事件和对象旋转。
我已经扩展了 Rajawali 的 ArcballCamera
class 以对其进行一些更改,因为我不喜欢视频在旋转球体时所做的一些奇怪的动作。我已经成功地改变了垂直和水平旋转方向。
问题是,如果用户向上滚动,当您经过球体的极点时,视频就会颠倒过来。
此外,根据球体的旋转方式(我不确定确切原因是什么),视频也会倾斜。
我想让用户向上和向下、向后和向前看,但我总是希望北方的视频杆向上,
不知道自己解释的好不好
我只能 post 两个链接,所以这是应用程序的屏幕截图 - 当用户继续向上或向下滚动通过球体的极点时会发生这种情况:
我正在使用四元数进行旋转,因为它们是用旋转轴和旋转角度制成的,所以我首先尝试将 x 轴或 z 轴保持在 0,但这没有用. 我已经搜索过类似的东西,但找不到出路。
这是 ArcballCamera
class 代码(仅与旋转相关的内容):
public class mArcballCamera extends ArcballCamera{
/*...*/
/*with the given x and y coordinates returns a 3D vector with x,y,z sphere coordinates*/
private void mapToSphere(float x, float y, Vector3 out) {
float lengthSquared = x * x + y * y;
if(lengthSquared > 1.0F) {
out.setAll((double)x, (double)y, 0.0D);
out.normalize();
} else {
out.setAll((double)x, (double)y, Math.sqrt((double)(1.0F - lengthSquared)));
}
}
/**with the given x and y coordinates returns a 2D vector with x,y screen coordinates*/
private void mapToScreen(float x, float y, Vector2 out) {
out.setX((double)((2.0F * x - (float)this.mLastWidth) / (float)this.mLastWidth));
out.setY((double)(-(2.0F * y - (float)this.mLastHeight) / (float)this.mLastHeight));
}
/**maps initial x and y touch event coordinates to <mPrevScreenCoord/> and then copies it to
* mCurrScreenCoord */
private void startRotation(float x, float y) {
this.mapToScreen(x, y, this.mPrevScreenCoord);
this.mCurrScreenCoord.setAll(this.mPrevScreenCoord.getX(), this.mPrevScreenCoord.getY());
this.mIsRotating = true;
}
/**updates <mCurrScreenCoord/> to new screen mapped x and y and then applies rotation*/
private void updateRotation(float x, float y) {
this.mapToScreen(x, y, this.mCurrScreenCoord);
this.applyRotation();
}
/** applies the rotation to the target object*/
private void applyRotation() {
if(this.mIsRotating) {
//maps to sphere coordinates previous and current position
this.mapToSphere((float) this.mPrevScreenCoord.getX(), (float) this.mPrevScreenCoord.getY(), this.mPrevSphereCoord);
this.mapToSphere((float) this.mCurrScreenCoord.getX(), (float) this.mCurrScreenCoord.getY(), this.mCurrSphereCoord);
//rotationAxis is the crossproduct between the two resultant vectors (normalized)
Vector3 rotationAxis = this.mPrevSphereCoord.clone();
rotationAxis.cross(this.mCurrSphereCoord);
rotationAxis.normalize();
//rotationAngle is the acos of the vectors' dot product
double rotationAngle = Math.acos(Math.min(1.0D, this.mPrevSphereCoord.dot(this.mCurrSphereCoord)));
//creates a quaternion using rotantionAngle and rotationAxis (normalized)
this.mCurrentOrientation.fromAngleAxis(rotationAxis, MathUtil.radiansToDegrees(rotationAngle));
this.mCurrentOrientation.normalize();
//accumulates start and current orientation in mEmpty object
Quaternion q = new Quaternion(this.mStartOrientation);
q.multiply(this.mCurrentOrientation);
double orientacionX = q.angleBetween(new Quaternion(0f,0f,1f,0f));
this.mEmpty.setOrientation(q);
}
}
/** adds the basic listeners to the camera*/
private void addListeners() {
//runs this on the ui thread
((Activity)this.mContext).runOnUiThread(new Runnable() {
public void run() {
//sets a gesture detector (touch)
mArcballCamera.this.mDetector = new GestureDetector(mArcballCamera.this.mContext, mArcballCamera.this.new GestureListener());
//sets a scale detector (zoom)
mArcballCamera.this.mScaleDetector = new ScaleGestureDetector(mArcballCamera.this.mContext, mArcballCamera.this.new ScaleListener());
//sets a touch listener
mArcballCamera.this.mGestureListener = new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
//sees if it is a scale event
mArcballCamera.this.mScaleDetector.onTouchEvent(event);
if(!mArcballCamera.this.mIsScaling) {
//if not, delivers the event to the movement detector to start rotation
mArcballCamera.this.mDetector.onTouchEvent(event);
if(event.getAction() == 1 && mArcballCamera.this.mIsRotating) {
//ends the rotation if the event ended
mArcballCamera.this.endRotation();
mArcballCamera.this.mIsRotating = false;
}
}
return true;
}
};
//sets the touch listener
mArcballCamera.this.mView.setOnTouchListener(mArcballCamera.this.mGestureListener);
}
});
}
/*gesture listener*/
private class GestureListener extends GestureDetector.SimpleOnGestureListener {
private GestureListener() {
}
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY) {
if(!mArcballCamera.this.mIsRotating) {
mArcballCamera.this.originalX=event2.getX();
mArcballCamera.this.originalY=event2.getY();
mArcballCamera.this.startRotation(mArcballCamera.this.mLastWidth/2, mArcballCamera.this.mLastHeight/2); //0,0 es la esquina superior izquierda. Buscar centro camara en algun lugar
return false;
} else {
float x = Math.abs((mArcballCamera.this.originalX - event2.getX() + mArcballCamera.this.mLastWidth/2)%mArcballCamera.this.mLastWidth);
float y = Math.abs((mArcballCamera.this.originalY - event2.getY() + mArcballCamera.this.mLastHeight/2)%mArcballCamera.this.mLastHeight);
mArcballCamera.this.mIsRotating = true;
mArcballCamera.this.updateRotation(x, y);
return false;
}
}
}
如果你想自己看,这里是项目的Github repository。我主要在 Samsung Galaxy Tab4 和 Xperia Z 上测试过它。
如果你看到代码,我也尝试通过在手势侦听器的 onScroll
方法中将起点移动到屏幕中心来改变旋转,但这也没有用(虽然改变了移动方向,我也想这样做)并留下了一些不需要的副作用。
那么,有没有办法平衡球体,使北极始终指向上方,而用户可以四处移动?任何可以帮助我找到正确方向的东西都会不客气。
我正在开发 Android Studio,主要使用 Rajawali(基于 OpenGL ES 2.0 构建的库)
如果您需要更多信息,请告诉我,我会编辑问题。另外,如果英语不好,没有很好地解释自己,我会努力做得更好。
好吧,我设法解决了问题。我在这里发布答案,希望有一天能对某人有用。
我现在使用 Euler angles(Tait-Bryan ones). These have a problem called the Gimbal lock 但更容易理解,而不是使用四元数来限制旋转。
为了避免 Gimbal lock 和旧系统的重大变化,我首先计算每个旋转的欧拉角,然后从中创建一个四元数。从四元数到欧拉角的转换不是唯一的,所以要小心。另外,请记住,您需要存储累积的旋转,以便新旋转始终是绝对的,始终围绕初始参考系统。
根据用户在屏幕上滚动的 X 轴和 Y 轴的距离,我得到球体必须绕 x 轴和 y 轴旋转的角度。我在屏幕上设置了 3 个完整的水平滚动意味着球体中的 360º 运动。为了避免滑动问题,我将滚动(沿 Z 轴旋转,相机面向的方向)设置为 0。避免视频上下颠倒只是裁剪俯仰(沿 X 轴面向旋转)的问题右)从 -PI/2 到 PI/2.
注意直接使用屏幕滚动的距离而不是球体的坐标也解决了我之前得到的奇怪的旋转方向。
这是更新后的代码:
private void startRotation(final float x, final float y){
mapToScreen(x, y, mPrevScreenCoord);
mCurrScreenCoord.setAll(mPrevScreenCoord.getX(), mPrevScreenCoord.getY());
mIsRotating = true;
this.xAnterior=x;
this.yAnterior=y;
}
private void applyRotation(float x, float y){
this.gradosxpixelX = gbarridoX/mLastWidth;
this.gradosxpixelY = gbarridoY/mLastHeight;
double gradosX = (x - this.xAnterior)*this.gradosxpixelX; //rotation around Y axis - yaw
double gradosY = (y - this.yAnterior)*this.gradosxpixelY; //rotation around X axis - pitch
if(this.mIsRotating) {
this.mCurrentOrientation.fromEuler(gradosX, gradosY, 0);
this.mCurrentOrientation.normalize();
Quaternion q = new Quaternion(this.mStartOrientation);
q.multiply(this.mCurrentOrientation);
this.mEmpty.setOrientation(q);
}
}