我正在尝试为 Android 制作一个 360 视频播放器应用程序。到目前为止,我已经成功地创建了一个球体并将视频投影到它上面。我还有一个跟随球体的 Arcball 相机,以处理用户触摸事件和对象旋转。
我已经扩展了ArcballCamera
Rajawali 的课程以对其进行一些更改,因为我不喜欢视频在旋转球体时所做的一些奇怪的动作。我设法改变了垂直和水平旋转方向。
问题是,如果用户向上和向上滚动,当您经过球体的极点时,视频会颠倒过来。此外,根据球体的旋转方式(我不确定究竟是什么原因),视频也会倾斜。我想让用户上下、前后看,但我总是想让视频北极点向上,我不知道我是否解释得很好。
我只能发布两个链接,所以这是应用程序的屏幕截图 - 当用户继续向上或向下滚动通过球体的极点时会发生这种情况:
我使用四元数进行旋转,因为它们是由旋转轴和旋转角度制成的,所以我首先尝试将 x 轴或 z 轴保持在 0,但这不起作用。我已经搜索过类似的东西,但我找不到出路。
这是ArcballCamera
类代码(仅与旋转相关的内容):
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 存储库。我主要在三星 Galaxy Tab4 和 Xperia Z 上测试过它。
如果您看到代码,我还尝试通过在手势侦听器的方法中将起点移动到屏幕中心来更改旋转onScroll
,但这也不起作用(尽管更改了移动方向,我也想做) 并留下一些不需要的副作用。
那么,有没有办法平衡球体,使北极始终指向上方,而用户可以四处移动?任何可以帮助我朝着正确方向前进的东西都会受到欢迎。
我正在开发 Android Studio,主要使用 Rajawali(基于 OpenGL ES 2.0 构建的库)
如果您需要更多信息,请告诉我,我将编辑问题。此外,如果英语不好并且没有很好地解释自己,我会尝试做得更好。