//get yaw offset
m_dRecenterYawOffset = simple_math::GetYawDegree(m_OriginRotation)
//DoOrientationRecenter
inline vr::HmdQuaternion_t DoOrientationRecenter(const vr::HmdQuaternion_t quaternion_origin,const double yaw_offset){
double yaw_degree_new,yaw_degree_origin;
vr::HmdVector3d_t degree;
vr::HmdQuaternion_t quaternion_dest;
//get origin yaw from quaternion
degree = simple_math::QuaternionToEulerDegree(quaternion_origin);
yaw_degree_origin = degree.v[0];
LOG_EVERY_N(INFO,5 * 60) << "DoOrientationRecenter[0]:quat(" << quaternion_origin.w << "," << quaternion_origin.x << ","
<< quaternion_origin.y << "," << quaternion_origin.z << "),degree(" << degree.v[0] << "," << degree.v[1] << ","
<< degree.v[2] << "),yaw_offset=" << yaw_offset;
//recenter yaw ,get yaw_degree_new
yaw_degree_new = yaw_degree_origin - yaw_offset;
yaw_degree_origin = degree.v[0];
LOG_EVERY_N(INFO,5 * 60) << "DoOrientationRecenter[0]:quat(" << quaternion_origin.w << "," << quaternion_origin.x << ","
<< quaternion_origin.y << "," << quaternion_origin.z << "),degree(" << degree.v[0] << "," << degree.v[1] << ","
<< degree.v[2] << "),yaw_offset=" << yaw_offset;
//recenter yaw ,get yaw_degree_new
yaw_degree_new = yaw_degree_origin - yaw_offset;
if(yaw_degree_new > 180){
yaw_degree_new= -360 + yaw_degree_new;
}else if(yaw_degree_new < -180){
yaw_degree_new = 360 + yaw_degree_new;
}
//transform degree to quaternion
degree.v[0] = yaw_degree_new;
quaternion_dest = simple_math::DegreeEulerToQuaternion(degree);
LOG_EVERY_N(INFO,5 * 60) << "DoOrientationRecenter[1]:quat(" << quaternion_dest.w << "," << quaternion_dest.x << ","
<< quaternion_dest.y << "," << quaternion_dest.z << "),degree(" << degree.v[0] << "," << degree.v[1] << ","
<< degree.v[2] << "),yaw_offset=" << yaw_offset;
return quaternion_dest;
}
详细见openvr_survivor的提交:
commit 72ec5ee7a0d22aceb79991a185763982bc09aed6
Author: HelenXR .com>
Date: Fri Jul 14 15:57:16 2017 +0800
实际验证可行,这个复位方法比较容易理解,当然这个复位转换应该有一个更快的操作方法:
quaternion(new) = quaternion(ori) * Rotation matrix.
后续有时间会用GLM库来实现这个转换.
之前学习整理过一个关于定位追踪的资料,详见VR定位追踪,这里就直接进入主题.我们主要是选择了国内目前比较成熟的定位追踪方案,一个是NOLO,另外一个是Ximmerse.两种方案技术路径不同,NOLO是激光+声波,Ximmerse是类似PSVR的可见光定位,它们提供的是相同的功能:6DOF的头部追踪和6DOF的手柄.有了定位追踪,带来了更好的VR体验.
NOLO提供的是一种激光定位+声波定位的方案,类似HTC VIVE,它包含一个基站,一个头盔定位器(6DOF),两个手柄控制器(6DOF).激光定位原理(激光定位原理,之前翻译过Hypereal开源的激光定位文档,想了解的,点击这里)上单基站相对于双基站的情况,定位精度会低一些(双基站可以达到0.5mm),单基站能达到2mm的精度,在精度上问题不大,最大的缺点是不能实现360度追踪,只能达到180度,还是蛮遗憾的(文章最后提供一个单基站扩展为360的方案”一键转身”功能).
NOLO定位参数:
1. 定位基站
尺寸:81*41*71mm
功耗:400 ma(2000mAh)
续航:4小时
充电:USB充电(1.5小时)
支持USB数据传输
2. 头盔定位器
尺寸:105*30*53mm
功耗:100 ma
续航:手机供电
支持USB数据传输
3. 交互手柄
尺寸:146*40*40mm
功耗:120 ma(1000mAh)
续航:USB充电(1.5小时)
支持USB数据传输
支持滑动触摸和点触摸
支持可调节震动
4. 定位参数
定位范围:FOV100°5.3m(以基站为原点)
定位精度:<2mm
定位刷新率:60hz
定位延时: <20ms
基于NOLO SDK开发,对应修改代码,见如下提交:
commit ba2a6c68e88480b60ce244894f6ba25dec541118
Author: HelenXR
Date: Fri Aug 25 18:42:29 2017 +0800
add six dof module:nolo.
Ximmerse提供的是一种类似PSVR的可见光追踪的方案.它包含一个双目摄像头,一个头盔定位器(6DOF),两个手柄控制器(6DOF).定位精度2mm,缺点也是只有180度范围定位,相比NOLO刷新率高一些(ximmerse:90,NOLO:60),使用起来更加流畅一些,但定位距离和范围没有NOLO的大,同样存在只有180度追踪范围的尴尬情况.
Ximmerse定位参数:
基于Ximmerse SDK开发,对应修改代码,见如下提交:
commit 3416a43d25e48b2acdf0120cb8d530e656606be0
Author: HelenXR
Date: Mon Aug 21 16:25:04 2017 +0800
add six dof module:ximmerse.
在处理两种定位方案过程中,基于SDK开发还是比较顺利的,这里有2个有点意思的数学问题
TouchPad区域识别
在定位方案SDK中都可以通过接口获取到TouchPad的x,y坐标,TouchPad的是一个圆盘(半径为1.0),如下图所示:
分为up,down,left,right区域,每次手触摸在触摸板上时,都可以读取到一组x,y的值,如何通过x,y判断按下的区域是哪一个?
区域”UP”:黑色直线(y=x)左侧与蓝色直线(y=-x)右侧,以及半径为1的圆相交的区域.
黑色直线左侧:y>x
蓝色直线右侧:y>-x
xy在圆之内:因触摸板上报的x,y坐标值一定会在圆盘之内,不需要处理.
up区域:-y < x < y
同理可以得出其他三个区域:
down区域:y < x < -y
left区域:x < y < -x
right区域:-x < y < x
对应代码:
vr::EVRButtonId CHandControllerDevice::GetDPadButton(float float_x,float float_y){
if(float_x > 1.0 || float_x < -1.0
||float_y > 1.0 || float_y < -1.0){
LOG(WARNING) << "GetDPadButton[" << m_cControllerRole << "]: error postion(" << float_x << "," << float_y << ")";
return vr::k_EButton_Max;
}
int x = float_x * 10000.0f,y = float_y * 10000.0f;
//UP:-y
if(x > -y && x < y){
LOG_EVERY_N(INFO,1 * 90) << "GetDPadButton[" << m_cControllerRole << "]:k_EButton_DPad_Up";
return vr::k_EButton_DPad_Up;
}
//DOWN:y
if(x > y && x < -y){
LOG_EVERY_N(INFO,1 * 90) << "GetDPadButton[" << m_cControllerRole << "]:k_EButton_DPad_Down";
return vr::k_EButton_DPad_Down;
}
//LEFT:x
if(y > x && y < -x){
LOG_EVERY_N(INFO,1 * 90) << "GetDPadButton[" << m_cControllerRole << "]:k_EButton_DPad_Left";
return vr::k_EButton_DPad_Left;
}
//RIGHT:-x
if(y > -x && y < x){
LOG_EVERY_N(INFO,1 * 90) << "GetDPadButton[" << m_cControllerRole << "]:k_EButton_DPad_Right";
return vr::k_EButton_DPad_Right;
}
LOG_EVERY_N(INFO,1 * 90) << "GetDPadButton[" << m_cControllerRole << "]:unknown region(" << x << "," << y << "),float(" << float_x << "," << "" << float_y <<")";
return vr::k_EButton_Max;
}
vr::HmdQuaternion_t quaternion_rotate = HmdQuaternion_Init( 0, 0, 1, 0 );
vr::HmdQuaternion_t quaternion_origin = HmdQuaternion_Init( m_Pose.qRotation.w,m_Pose.qRotation.x,m_Pose.qRotation.y,m_Pose.qRotation.z);
//rotate rotation
m_Pose.qRotation = glm_adapter::QuaternionMultiplyQuaternion(quaternion_rotate,m_Pose.qRotation);
//rotate position
glm_adapter::PointAroundPointRotate(quaternion_rotate,m_Pose.vecPosition,m_dHmdPositionWhenTurnAround,m_Pose.vecPosition);
//其中PointAroundPointRotate代码如下:
void PointAroundPointRotate(const vr::HmdQuaternion_t quaternion_rotate, const double point_origin[3], const double point_center[3], double point_dest[3]) {
glm::tquat tquat_rotate(quaternion_rotate.w, quaternion_rotate.x, quaternion_rotate.y, quaternion_rotate.z);
glm::tvec3 tvec3_origin(point_origin[0], point_origin[1], point_origin[2]), tvec3_center(point_center[0], point_center[1], point_center[2]),tvec3_dest;
tvec3_dest = tquat_rotate * (tvec3_origin - tvec3_center) + tvec3_center;
point_dest[0] = tvec3_dest.x;
point_dest[1] = tvec3_dest.y;
point_dest[2] = tvec3_dest.z;
}
commit 7bf5f86d408c871fdb1936edce1d0000747180b6
感谢ice,bikasuo,我们一起用空闲时间完成开发活动,这是一个开始,我们希望更多的人可以加入到我们当中来,VR还处于很初期的阶段,我们通过努力,也许可以给VR的发展带来一些好的东西,分享的过程也是一个很好的互相学习的过程,openvr_survivor下一期活动再会:-).