在上一篇文档中,我们实现了通过手势控制模型节点的旋转、缩放和平移。现在本文将介绍如何优化上一篇做的手势控制器,从而实现更好的跟手效果。
相关链接:【Sceneform-EQR】(手势控制器实现)通过手势事件实现在AR/VR等三维场景中的控制模型旋转、平移与缩放
手势优化
平移手势优化
当前存在的问题
直接采用安卓提供的onScroll方法,能够获取到distanceX\Y,而不同设备的屏幕密度不一致,会导致distanceX\Y的值都会有差异。
此外,在AR场景中,不同手机平板的相机的内参(相机焦距、感光元件尺寸等)都不同,会导致相机的视场角不同。
总之,这些都会导致无法实现“指哪打哪”的效果。
优化后的效果
“指哪打哪”的效果
解决思路
- 思路是屏幕坐标转空间坐标
需要注意的是,屏幕坐标是二维的,空间坐标是三维的。
这里通过屏幕坐标计算出一条射线,然后通过这条射线取指定距离的空间点或是求射线与指定平面的交点。(两种方式都可以很好的实现平移效果,但是实际上有些区别(与相机的距离不一样))
- 采用指定距离
Sceneform-EQR提供了屏幕坐标转射线的方法camera.screenPointToRay(x,y),然后我们传入指定距离即可。
//计算射线,再取得固定距离的空间点坐标.作为新的空间位置Vector3 newPosition = camera.screenPointToRay(screenPoint.x, screenPoint.y).getPoint(/*外部传入*/distance);
- 采用线面相交
原理是,求射线与前方指定距离的平面的交点。用高中的立体几何知识,就不列举公式了,直接看下面代码。
代码如下:
//向量AB
Vector3 vectorAB = Vector3.subtract(b, a);
//向量AC
Vector3 vectorAC = Vector3.subtract(c, a);
//直线方向向量i
Vector3 i = Vector3.subtract(v1, v0);
//平面法向量n
Vector3 n = Vector3.cross(vectorAB,vectorAC);//点法式平面方程常数K(条件:代入A点坐标计算=>)
float constK = n.x * a.x + n.y * a.y + n.z * a.z;
//点向式直线方程常数M(条件:直线与平面相交=>)
float constM = (constK - n.x * v0.x - n.y * v0.y - n.z * v0.z)/(i.x * n.x + i.y * n.y + i.z * n.z);//D点坐标
Vector3 d=new Vector3(v0.x + i.x * constM,v0.y + i.y * constM,v0.z + i.z * constM);
EQR-源码定位
private void onDoubleFingerScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {isDoubleFingerScroll = true;//计算当前Node的世界坐标系下的空间位置。Vector3 screenPoint = camera.worldToScreenPoint(target.getWorldPosition());screenPoint.x -= distanceX;screenPoint.y -= distanceY;//计算射线,再取得固定距离的空间点坐标.作为新的空间位置Vector3 newPosition = camera.screenPointToRay(screenPoint.x, screenPoint.y).getPoint(/*外部传入*/distance);target.setWorldPosition(newPosition);}
旋转手势优化
当前存在的问题
在AR/VR等三维场景中,若场景相机的位姿(位置、姿态)发生变化,原旋转轴的计算已不再适用了
由上图,可发现,当前相机视角发生改变后,原来的旋转手势只能实现模型在它自身坐标系下旋转。
优化后的效果
优化后效果如下:
解决思路
步骤
- 通过屏幕坐标转空间坐标的方法(与平移优化中用到的一样)计算两个MotionEvent事件的屏幕坐标
A
、B
对应的空间坐标A、B。 - 获取当前相机的在世界坐标系下的位置C。
- 通过余弦定义求∠ACB,这个作为旋转角度。
- 通过向量CA与向量CB的叉乘求旋转轴。
叉乘计算:
/*** 叉乘* @return 垂直于两个矢量的矢量*/public static Vector3 cross(Vector3 lhs, Vector3 rhs) {float lhsX = lhs.x;float lhsY = lhs.y;float lhsZ = lhs.z;float rhsX = rhs.x;float rhsY = rhs.y;float rhsZ = rhs.z;return new Vector3(lhsY * rhsZ - lhsZ * rhsY, lhsZ * rhsX - lhsX * rhsZ, lhsX * rhsY - lhsY * rhsX);}
- 通过计算得到的旋转角度与旋转轴构造四元数,以此做旋转
EQR-源码定位
Commit记录
- 优化旋转手势
//单指实现旋转(计算旋转轴和旋转角度)//原理://上一触摸点转为空间坐标点A,当前触摸点转为空间触摸点B。记当前场景相机的位置为点O//那么向量OA与向量OB的叉积则是旋转轴//转为空间坐标(屏幕坐标->射线->固定距离的点)Vector3 pointA = camera.screenPointToRay(currentEvent.getX() - distanceX,currentEvent.getY() - distanceY).getPoint(/*外部传入*/distance);Vector3 pointB = camera.screenPointToRay(currentEvent.getX(),currentEvent.getY()).getPoint(distance);Vector3 pointO = camera.getWorldPosition();Vector3 oa = Vector3.subtract(pointA, pointO);Vector3 ob = Vector3.subtract(pointB, pointO);float c = Vector3.subtract(pointA, pointB).length();float a = oa.length();float b = ob.length();float cosC = (a * a + b * b - c * c) / (2 * a * b);//旋转轴Vector3 cross = Vector3.cross(oa, ob).normalized();
查看示例demo请转至git。欢迎交流讨论!
Git仓库
- Sceneform-EQR