A0af28712f4e1844f8f9e6b817bb5e8a
42-如何仿写一个简易版的3自由度 AR 导航

说明

ARKit系列文章目录

AR 导航简介

AR 导航功能一直是大家期待的,但是直到今天也没有哪个 app 能把 GPS 和 ARKit 完美结合起来。GPS 在短距离内不准和 ARKit 在长距离时不准的问题始终困扰着大家。

不过仍然有很多开发者进行了尝试。比如

  • ARKit-CoreLocation
    ARKit-CoreLocation 是利用 ARKit 的世界追踪和 CoreLocation 的 GPS 定位来综合实现 AR 导航的项目,整体效果非常好。

不过在真正实用中,各家 app 使用的还是 3 自由度的导航比较多,比如百度地图的步行实景导航,美团外卖等等。
下图是百度地图的 AR 导航功能演示(为保护隐私做了遮挡),百度地图的 SDK 中也直接提供了步行 AR 导航功能:

接下来我们就以此为原型,进行分析并完成一个 3 自由度的 AR 导航功能。

实现思路

首先,是地图 sdk 的准备,这里我选用了百度地图,并将显示模式设置为跟随手机方向BMKUserTrackingModeFollowWithHeading。这样当手机旋转时,地图就会旋转,地图上的箭头始终是朝向屏幕前方。

其次,是 ARKit 的使用,就使用朝向追踪配置AROrientationTrackingConfiguration并设置configuration.worldAlignment = ARWorldAlignmentGravityAndHeading;这样 z 轴会自动对齐真实世界的南方。

接着,我们调用地图的步行路线规划并在地图上画线,这里可以参照百度地图的 demo。

最后,在 GPS 位置第一次更新时,我们取出路线上最近的 6 个点,计算其相对于手机的位置,并在 AR 中显示出来。并在点之间连线。

此后,每次位置更新时,都计算手机与附近 6 个点的距离,当发现距离足够近了(如小于 20 米),就把该点及更近的点都删除,重新取新的 6 个点显示在 AR 中。
```objc
// 每次位置更新时,调用该方法

  • (void)updateTargetNodePositionAndGuideLine {
    if (self.pointCount > 0 && !(self.userLocation.location.coordinate.latitude == 0 && self.userLocation.location.coordinate.longitude == 0)) {
    BMKMapPoint userPoint = BMKMapPointForCoordinate(self.userLocation.location.coordinate);
    // 百度地图上每一点,对应的真实距离,和纬度有关
    double metersPerMapPoint = BMKMetersPerMapPointAtLatitude(self.userLocation.location.coordinate.latitude);

    // 更新 AR 中的终点
    double targetDisplayX = 0,targetDisplayY = 0;//AR显示终点的位置
    double targetDistance = BMKMetersBetweenMapPoints(userPoint, self.polylinePoints[self.pointCount - 1]);//终点的距离
    if (targetDistance > 20) {//超过了 20 米,放在 20 米处
        targetDisplayX = 20.0 / targetDistance * (self.polylinePoints[self.pointCount - 1].x - userPoint.x) * metersPerMapPoint;
        targetDisplayY = 20.0 / targetDistance * (self.polylinePoints[self.pointCount - 1].y - userPoint.y) * metersPerMapPoint;
    } else {
        targetDisplayX = (self.polylinePoints[self.pointCount - 1].x - userPoint.x) * metersPerMapPoint;//东西方向实际距离
        targetDisplayY = (self.polylinePoints[self.pointCount - 1].y - userPoint.y) * metersPerMapPoint;//南北方向实际距离
    }

    // 终点的显示位置
    self.targetNode.simdPosition = simd_make_float3(targetDisplayX, 0, targetDisplayY);
    // 终点的朝向,始终指向(0,0,0)点,即手机处
    [self.targetNode simdLookAt:simd_make_float3(0)];

    // 更新最近的 6 个点
    NSInteger displayCount = self.pointCount > 5 ? 6 : self.pointCount;//可能少于 6 个,以实际为准
    
    int tmp = 0;//记录已经跨过/绕过的点。用户绕过2,3,4 个点也能正常导航。一次绕过 6 个点则认为偏航,需重新规划路线
    for (int i = 1; i <= displayCount; i++) {
        double distance = BMKMetersBetweenMapPoints(userPoint, self.polylinePoints[i]);
        if (distance < 20 && self.pointCount > 1) {//走到最近 6 个点附近时,跨越太靠近的点,如果只剩最后一个点不再跨越
            tmp = i;
        }
    }
    self.pointCount -= tmp;//跨过太近的若干个点
    self.polylinePoints += tmp;//跨过太近的若干个点
    
    //刷新地图polyline路线显示
    [self.mapView removeOverlays:self.mapView.overlays];
    //根据指定直角坐标点生成一段折线
    BMKPolyline *polyline = [BMKPolyline polylineWithPoints:self.polylinePoints count: self.pointCount];
    [self.mapView addOverlay:polyline];
    //再画一根,从自己位置到最近的点

    // BMKMapPoint *pointsMe = [Tools creatMapPoints:2];
    // pointsMe[0] = userPoint;
    // pointsMe[1] = self.polylinePoints[0];
    // BMKPolyline *polylineMe = [BMKPolyline polylineWithPoints:pointsMe count:2];
    // [self.mapView addOverlay:polylineMe];

top Created with Sketch.