2

我正在尝试为 Android 制作一个 360 视频播放器应用程序。到目前为止,我已经成功地创建了一个球体并将视频投影到它上面。我还有一个跟随球体的 Arcball 相机,以处理用户触摸事件和对象旋转。

我已经扩展了ArcballCameraRajawali 的课程以对其进行一些更改,因为我不喜欢视频在旋转球体时所做的一些奇怪的动作。我设法改变了垂直和水平旋转方向。
问题是,如果用户向上和向上滚动,当您经过球体的极点时,视频会颠倒过来。此外,根据球体的旋转方式(我不确定究竟是什么原因),视频也会倾斜。我想让用户上下、前后看,但我总是想让视频北极点向上,我不知道我是否解释得很好。

我只能发布两个链接,所以这是应用程序的屏幕截图 - 当用户继续向上或向下滚动通过球体的极点时会发生这种情况:

颠倒捕获

我使用四元数进行旋转,因为它们是由旋转轴和旋转角度制成的,所以我首先尝试将 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 构建的库)

如果您需要更多信息,请告诉我,我将编辑问题。此外,如果英语不好并且没有很好地解释自己,我会尝试做得更好。

4

1 回答 1

4

好吧,我设法解决了问题。我在这里发布答案,希望有一天对某人有用。

我现在不使用四元数来限制旋转,而是使用欧拉角(Tait-Bryan 角)。这些有一个称为万向锁的问题,但更容易理解。

为了避免万向节锁定并防止旧系统发生重大变化,我首先计算每次旋转的欧拉角,然后从它们创建一个四元数。从四元数到欧拉角的转换并不是唯一的,所以要小心。另外,请记住,您需要存储累积的旋转,以便新的旋转始终是绝对的,始终围绕初始参考系。

从用户在屏幕上滚动的 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);
    }
}
于 2015-08-03T11:59:33.747 回答