ARKit 物理检测

在上一篇中我们通过ARKit检测现实中的水平屏幕并覆盖上网格,接着上一篇的内容继续进行实践,在之前的平台检测中通过点击屏幕加入一些虚拟正方体,实现物理检测。

Hit Testing

正如上一篇中看到的,我们可以在任何X、Y、Z位置插入虚拟3D内容,它将在现实世界中呈现和跟踪。现在我们有了平面检测,我们想添加与这些平面相互交互的内容。在这个项目中,我另开了一个分支AR_Physics,实现的内容有。

当点击屏幕时,会执行一个hit test,会从网格上方掉落正方体。这里设计到2D屏幕坐标,这涉及到2D屏幕坐标和3D坐标,通过2D屏幕点(在投影平面上有一个3D位置)从相机原点发射一束光线到场景中。如果射线与任何平面相交,我们得到一个坠落点,然后我们取三维坐标,在那里射线和平面相交并将我们的正方体容放置在那个3D位置。 创建单击长按等的手势不多说,代码中也有。ARSCNView有一个hitTest方法,通过传入点击的2D左边返回一个一个3D坐标数组NSArray

  • 单击屏幕的方法
//单击方法
- (void)handleTapFrom: (UITapGestureRecognizer *)recognizer {
    //获取屏幕空间tap坐标,并将它传递给ARSCNView的hitTest方法
    CGPoint tapPoint = [recognizer locationInView:self.sceneView];
    
    NSArray *result = [self.sceneView hitTest:tapPoint types:ARHitTestResultTypeExistingPlaneUsingExtent];
    
    if (result.count == 0) {
        return;
    }
    
    // 插入正方体
    ARHitTestResult * hitResult = [result firstObject];
    [self insertGeometry:hitResult];
}
复制代码
// 插入正方体
- (void)insertGeometry:(ARHitTestResult *)hitResult {
    
    float dimension = 0.1;
    SCNBox *cube = [SCNBox boxWithWidth:dimension height:dimension length:dimension chamferRadius:0];
    SCNNode *node = [SCNNode nodeWithGeometry:cube];
    
    // SCNPhysicsBody告诉SceneKit这个几何图形应该被物理引擎操纵
    node.physicsBody = [SCNPhysicsBody bodyWithType:SCNPhysicsBodyTypeDynamic shape:nil];
    node.physicsBody.mass = 2.0;
    node.physicsBody.categoryBitMask = CollisionCategoryCube;
    
    // 将几何图形略高于用户点击的点,这样就可以制造出正方体在平面上降落的效果
    float insertionYOffset = 0.5;
    node.position = SCNVector3Make(
                                   hitResult.worldTransform.columns[3].x,
                                   hitResult.worldTransform.columns[3].y + insertionYOffset,
                                   hitResult.worldTransform.columns[3].z
                                   );
    [self.sceneView.scene.rootNode addChildNode:node];
    [self.boxes addObject:node];
}
复制代码

为了效果看起来更加接近真实,我们会增加一些物理学来展示一种重力感。 在Plane类中,为每一个正方体加上physicsBody,表明正方体由SceneKit的物理引擎控制,更多的细节查看Plane.m中的代码

  • 清除world平面中的正方体

通过一个手指长按,所有的正方体呈现爆炸的方式掉落到最下面的节点平面上, 首先,需要创造一个bottomNode,当所有的正方体和这个bottomNode产生碰撞时,便表示掉落出我们所创建的虚拟world,移除掉落的正方体

//将一个大的节点放到虚拟世界的下面,当正方体爆炸掉落到这个节点上时,就将正方体移除
SCNBox *bottomPlane = [SCNBox boxWithWidth:1000 height:0.5 length:1000 chamferRadius:0];
SCNMaterial *bottomMaterial = [SCNMaterial new];
bottomMaterial.diffuse.contents = [UIColor colorWithWhite:1.0 alpha:0.2];
bottomPlane.materials = @[bottomMaterial];
SCNNode *bottomNode = [SCNNode nodeWithGeometry:bottomPlane];
bottomNode.position = SCNVector3Make(0, -10, 0);
bottomNode.physicsBody = [SCNPhysicsBody
                            bodyWithType:SCNPhysicsBodyTypeKinematic
                                   shape: nil];
bottomNode.physicsBody.categoryBitMask = CollisionCategoryBottom;
bottomNode.physicsBody.contactTestBitMask = CollisionCategoryCube;
    
[self.sceneView.scene.rootNode addChildNode:bottomNode];
self.sceneView.scene.physicsWorld.contactDelegate = self;
复制代码

一个手指长按的方法

//一个手指长按方法
- (void)handleHoldFrom: (UILongPressGestureRecognizer *)recognizer {
    if (recognizer.state != UIGestureRecognizerStateBegan) {
        return;
    }
    //使用屏幕坐标执行 hit test,以查看是否点击了平面
    CGPoint holdPoint = [recognizer locationInView:self.sceneView];
    NSArray *result = [self.sceneView hitTest:holdPoint types:ARHitTestResultTypeExistingPlaneUsingExtent];
    if (result.count == 0) {
        return;
    }
    
    //将正方体以爆炸的方式清除
    ARHitTestResult * hitResult = [result firstObject];
    dispatch_async(dispatch_get_main_queue(), ^{
        [self explode:hitResult];
    });
}
复制代码
//爆炸的方法
- (void)explode:(ARHitTestResult *)hitResult {
    float explosionYOffset = 0.1;
    
    //取explosion在worldTransform的坐标
    SCNVector3 position = SCNVector3Make(
                                         hitResult.worldTransform.columns[3].x,
                                         hitResult.worldTransform.columns[3].y - explosionYOffset,
                                         hitResult.worldTransform.columns[3].z
                                         );
    
    //取每一个正方体的坐标,然后计算正方体和博炸点的距离
    for (SCNNode *cubeNode in self.boxes) {
        SCNVector3 distance = SCNVector3Make(cubeNode.worldPosition.x - position.x,
                                             cubeNode.worldPosition.y - position.y,
                                             cubeNode.worldPosition.z - position.z);
        
        float length = sqrtf(distance.x * distance.x + distance.y * distance.y + distance.z * distance.z);
        
        //设置最大距离,当距离超过maxDistance后,便不会受到force的影响
        float maxDistance = 2;
        float scale = MAX(0, (maxDistance - length));
        
        scale = scale * scale * 2;
        
        // 将距离矢量缩放到合适的尺度
        distance.x = distance.x / length * scale;
        distance.y = distance.y / length * scale;
        distance.z = distance.z / length * scale;
        
        // 对几何图形施加一个force,将force设置到正方体的角使其旋转
        [cubeNode.physicsBody applyForce:distance atPosition:SCNVector3Make(0.05, 0.05, 0.05) impulse:YES];
    }
}
复制代码
  • 清除网格平面

通过两个手指长按,移除平面并停止检测平面

//两个手指长按方法
- (void)handleHidePlaneFrom: (UILongPressGestureRecognizer *)recognizer {
    if (recognizer.state != UIGestureRecognizerStateBegan) {
        return;
    }
    
    //隐藏所有平面
    for(NSUUID *planeId in self.planes) {
        [self.planes[planeId] hide];
    }
    
    //停止检测或者更新存在的平面
    ARWorldTrackingConfiguration *configuration = (ARWorldTrackingConfiguration *)self.sceneView.session.configuration;
    configuration.planeDetection = ARPlaneDetectionNone;
    [self.sceneView.session runWithConfiguration:configuration];
}
复制代码
  • 正方体和bottomNode的碰撞检测
#pragma mark - SCNPhysicsContactDelegate

- (void)physicsWorld:(SCNPhysicsWorld *)world didBeginContact:(SCNPhysicsContact *)contact{
//检测正方体和下面底部的碰撞,当正方体掉到了bottomNode下面,就移除正方体
    CollisionCategory contactMask = contact.nodeA.physicsBody.categoryBitMask | contact.nodeB.physicsBody.categoryBitMask;
    
    if (contactMask == (CollisionCategoryBottom | CollisionCategoryCube)) {
        if (contact.nodeA.physicsBody.categoryBitMask == CollisionCategoryBottom) {
            [contact.nodeB removeFromParentNode];
        } else {
            [contact.nodeA removeFromParentNode];
        }
    }
}
复制代码

项目github地址在上一个项目的AR_Physics分支。

转载于:https://juejin.im/post/5aa2267d518825555f0c90e5

你可能感兴趣的:(ARKit 物理检测)