上一篇文章有写到,需求是要求3D模型姿态要根据真实物理设备的姿态来动,之前只是实现了模型单独在世界坐标系中旋转,现在来实现姿态同步。这个就需要得到真实物理设备的xyz坐标轴的偏移角度,这些角度就是欧拉角,陀螺仪传感器可以帮助我们获得这些数据,得到这些数据之后我们就能实现3D模型和真实物理设备的同步。(欧拉角造成的万向节锁问题我们下文会说到,以及对应的解决方法)
public class DemoGLSurfaceView extends GLSurfaceView {
DemoRender demoRender;
...
//增加旋转调用
public void rotation(float x, float y, float z){
queueEvent(()->demoRender.rotation(x, y, z));
}
}
在原来的GLSurfaceView类中加入旋转方法的调用,把xyz轴的偏移角度传给opengles,进行角度偏移处理在对应的Render和Filiter类中加入rotation方法,一层一层传下去;
public class DemoRender implements GLSurfaceView.Renderer {
private RenderFilterrenderFilter;
...
public void rotation(float x, float y, float z){
renderFilter.rotation(x, y, z);
}
}
public class RenderFilter {
...
public void drawBaseGrid(){
coordinationView.drawSelf();
}
public void draw3dObj() {
//绕Z轴旋转,单位旋转1度
// Matrix.rotateM(sensorDevice.getMatrix(), 0, 1, 0, 0, 1);
sensorDevice.drawSelf();
}
//注释掉上面的固定按照z轴旋转的代码,增加这个在xyz轴上旋转的方法
public void rotation(float x, float y, float z){
Matrix.rotateM(sensorDevice.getMatrix(), 0, x, 1, 0, 0);
Matrix.rotateM(sensorDevice.getMatrix(), 0, y, 0, 1, 0);
Matrix.rotateM(sensorDevice.getMatrix(), 0, z, 0, 0, 1);
}
}
到此,基本就能实现3D模型在xyz轴上的自由旋转了。
可是,当真实调试的时候发现,我猛烈旋转摇晃设备,刚开始动作还是可以同步,后面我把设备放回原来的姿态时,发现3D模型怎么歪了,不是我想要得到的结果,emm...
经过我一个轴一个轴的单独旋转调试发现,当固定z轴和x轴,绕y轴旋转到达90度,再固定y轴和z轴不动,绕x轴旋转时,问题出现了。设备反馈上来的数据显示,此时z轴的数据和x轴同步了,3D模型按照这个数据旋转时,出现的效果就和真实物理设备姿态出现了偏差。。。
查阅咨询一番以后,才知道这个就是万向节锁现象,这是由三轴陀螺仪本身的限制引起的,而解决这个问题的办法呢,就用到四元数啦。四元数是通过四维空间的方式来表达三维空间的姿态,具体四维空间是怎么样的形态,咱也不清楚,咱只需要知道业界就是这么定义的。四元数由四个值表示姿态,分别是wxyz,在原来的三维坐标xyz的基础上增加了w的值,四元数怎么通过代码来表示物体姿态呢,咱们上一节提到有个4X4的矩阵来表示,咦?又出现个4的数字,跟四元数和四维空间有没有什么联系呢,或者能不能相互转化呢?
经过一番百度之后,果然~四元数是可以转换成4X4矩阵来表示的~
那么,我们现在需要改造一下了,把原来传的欧拉角,改成传四元数,如果陀螺仪不支持四元数,没关系,欧拉角和四元数是可以相互转换的。得到四元数之后,我们再将四元数转换成矩阵数组,就完美解决啦~
//定义一个四元数类
public class Quaternion {
public float x;
public float y;
public float z;
public float w;
}
//四元数转矩阵数组
public static float[] matrix3DSetUsingQuaternion3D(Quaternion quat){
float[] matrix =new float[16];
matrix[0] = (1.0f-(2.0f * ((quat.y * quat.y) + (quat.z * quat.z))));
matrix[1] = (2.0f * ((quat.x * quat.y)-(quat.z * quat.w)));
matrix[2] = (2.0f * ((quat.x * quat.z) + (quat.y * quat.w)));
matrix[3] =0.0f;
matrix[4] = (2.0f * ((quat.x * quat.y) + (quat.z * quat.w)));
matrix[5] = (1.0f-(2.0f * ((quat.x * quat.x) + (quat.z * quat.z))));
matrix[6] = (2.0f * ((quat.y * quat.z)-(quat.x * quat.w)));
matrix[7] =0.0f;
matrix[8] = (2.0f * ((quat.x * quat.z)-(quat.y * quat.w)));
matrix[9] = (2.0f * ((quat.y * quat.z) + (quat.x * quat.w)));
matrix[10] = (1.0f-(2.0f * ((quat.x * quat.x) + (quat.y * quat.y))));
matrix[11] =0.0f;
matrix[12] =0.0f;
matrix[13] =0.0f;
matrix[14] =0.0f;
matrix[15] =1.0f;
return matrix;
}
public class DemoGLSurfaceView extends GLSurfaceView {
DemoRender demoRender;
...
//增加旋转调用
//public void rotation(float x, float y, float z){
// queueEvent(()->demoRender.rotation(x, y, z));
// }
public void setMatrix(float[] matrix){
queueEvent(() -> gyroRender.setMatrix(matrix));
}
}
public class RenderFilter {
...
public void drawBaseGrid(){
coordinationView.drawSelf();
}
public void draw3dObj() {
//绕Z轴旋转,单位旋转1度
// Matrix.rotateM(sensorDevice.getMatrix(), 0, 1, 0, 0, 1);
sensorDevice.drawSelf();
}
//注释掉上面的固定按照z轴旋转的代码,增加这个在xyz轴上旋转的方法
//public void rotation(float x, float y, float z){
// Matrix.rotateM(sensorDevice.getMatrix(), 0, x, 1, 0, 0);
// Matrix.rotateM(sensorDevice.getMatrix(), 0, y, 0, 1, 0);
// Matrix.rotateM(sensorDevice.getMatrix(), 0, z, 0, 0, 1);
// }
public void setMatrix(float[] matrix){
Matrix.setIdentityM(mvpMatrix, 0);
Matrix.multiplyMM(mvpMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
Matrix.multiplyMM(mvpMatrix, 0, mvpMatrix, 0, matrix, 0);
System.arraycopy(mvpMatrix, 0, sensorDevice.getMatrix(), 0, mvpMatrix.length);
}
}
这样,就用四元数的矩阵和投影矩阵左乘换算出来世界坐标系中矩阵的变化,然后再复制给3D模型,大功告成~