本文目的: 分析 HDAPI demo,如何产生一个触碰平面的力
Haptic Device(触觉设备)API 包含两个主要的部分:设备以及调度程序。设备抽象化就可以让任何支持3维触觉的机器在 HDAPI 下使用。调度程序回调允许程序员输入在伺服循环线程内执行的命令。
HDAPI 典型用途:
HDAPI 需要一个安装驱动的 3D 触觉设备,以及安装的 HDAPI
项目应该包含 HDAPI 的头文件以及 HDAPI 库文件以及实用工具(utility)的库文件
由于3维触觉设备不易表示出来,下面采用语言描述
ConsoleExamples 中的 FrictionlessPlane:
创建一个高度为0的平面,触觉设备从上或者从下碰到平面时产生推力,当触觉设备的力大于阈值时会发生穿透。
如图(左侧),当触觉设备位置在平面法向量(绿色部分)上方,此时投影为正,表示没有发生穿透现象
接着设备朝着平面运动(图右侧),当投影为负时表示发生穿透。
现在用穿透的位移(点到平面的距离)当作弹簧形变,根据胡可定律产生力,力的方向和当前平面的法向量一致(所以产生排斥力)。
当力大于阈值时设置力为0,同时改变朝向(directionFlag)如下图
这样当设备朝着上方移动时(重复上述过程)仍然可以感受到平面,符合实际情况。
int main(int argc, char* argv[])
{
HDErrorInfo error;
// 初始化默认的 haptic device.
HHD hHD = hdInitDevice(HD_DEFAULT_DEVICE);
if (HD_DEVICE_ERROR(error = hdGetError()))
{
hduPrintError(stderr, &error, "Failed to initialize haptic device");
fprintf(stderr, "\nPress any key to quit.\n");
getch();
return -1;
}
// 创建一个随动调度程序并施夹力
hdEnable(HD_FORCE_OUTPUT);
hdStartScheduler();
if (HD_DEVICE_ERROR(error = hdGetError()))
{
hduPrintError(stderr, &error, "Failed to start the scheduler");
fprintf(stderr, "\nPress any key to quit.\n");
getch();
return -1;
}
// 安排一个无摩擦平面的回调, 会运行在servoloop 中,当刺破平面时施加力
HDCallbackCode hPlaneCallback = hdScheduleAsynchronous(
FrictionlessPlaneCallback, 0, HD_DEFAULT_SCHEDULER_PRIORITY);
printf("Plane example.\n");
printf("Move the device up and down to feel a plane along Y=0.\n");
printf("Push hard against the plane to popthrough to the other side.\n");
printf("Press any key to quit.\n\n");
while (!_kbhit())
{
if (!hdWaitForCompletion(hPlaneCallback, HD_WAIT_CHECK_STATUS))
{
fprintf(stderr, "\nThe main scheduler callback has exited\n");
fprintf(stderr, "\nPress any key to quit.\n");
getch();
break;
}
}
// 清理并关闭 haptic device, 清除所有的回调函数.
hdStopScheduler();
hdUnschedule(hPlaneCallback);
hdDisableDevice(hHD);
return 0;
}
// 平面在y=0处,当穿破物体时提供一个排斥力
HDCallbackCode HDCALLBACK FrictionlessPlaneCallback(void *data)
{
// 硬度,值越大平面越硬
const double planeStiffness = 2.85;
// 穿透平面时最大的力
const double popthroughForceThreshold = 30.0;
// 朝向:穿破平面时改变,1和-1表示面对平面的正面和背面
// 这个例子中,1表示位于平面上方,-1表示位于平面下方
static int directionFlag = 1;
hdBeginFrame(hdGetCurrentDevice());
// device 的位置.
hduVector3Dd position;
hdGetDoublev(HD_CURRENT_POSITION, position);
// 如果用户穿透平面, 设置一个沿着平面法向量上排斥的力
// 当平面法向量+y但位置为负时,发生穿透
if ((position[1] <= 0 && directionFlag > 0) ||
(position[1] > 0) && (directionFlag < 0))
{
double penetrationDistance = fabs(position[1]);
hduVector3Dd forceDirection(0,directionFlag,0);
// 胡克定律
double k = planeStiffness;
hduVector3Dd x = penetrationDistance*forceDirection;
hduVector3Dd f = k*x;
// 发生穿刺时力归0,更改朝向
if (f.magnitude() > popthroughForceThreshold)
{
f.set(0.0,0.0,0.0);
directionFlag = -directionFlag;
}
hdSetDoublev(HD_CURRENT_FORCE, f);
}
hdEndFrame(hdGetCurrentDevice());
// 发生错误时结束回调函数
HDErrorInfo error;
if (HD_DEVICE_ERROR(error = hdGetError()))
{
hduPrintError(stderr, &error, "Error detected during main scheduler callback\n");
if (hduIsSchedulerError(&error))
{
return HD_CALLBACK_DONE;
}
}
return HD_CALLBACK_CONTINUE;
}
可以看到整个过程中间有明显的阻力,当继续施加力之后会直接穿透平面