聚焦位置-选择您喜欢的位置放置虚拟物体

在上一个视频中,您学习了如何检测水平曲面并能够透视它。正如我所提到的,它们是放置物体的锚点。但是,在飞机上我们应该添加我们的物体?为此,我们需要在屏幕上选择一个点。在本节中,我们将形成并个性化焦点方块。我们将使用焦点方块跟随相机,直到我们对放置感到满意为止。我们将讨论世界变换和命中测试,这是ARKit的两个重要概念。

下载

要学习本教程,您需要Xcode 10或更高版本,以及平面检测的最终Xcode项目。您可以下载本节的最终Xcode项目,以帮助您与自己的进度进行比较。

焦点方块 Focus Square 类

首先,我们将为Focus Square创建一个新类,以便我们可以个性化其风格和状态。让我们为焦点方块添加一个新的Swift文件。右键单击视图控制器+ ARSCNViewDelegate并选择新建文件...。然后,选择Swift File,单击Next。将其命名为FocusSquare,然后创建。现在,我们在FocusSquare.swift文件中。

接下来,替换** FoundationSceneKit**。

然后,声明一个名为FocusSquare的新类,默认类型为SCNNode。要注意命名类的规则,它以大写字母开头。

初始化

在类中,我们将定义一个初始化程序,这样每当我们创建一个新的焦点方形节点时,它将执行一些额外的步骤。作为其父级,SCNNode类具有自己的属性。要添加新的,我们需要覆盖它。由于初始值设定项上没有必需参数,因此请将括号内的空白留空。

另外因为我们重写,请使用super.init()。这将调用SCNNode超类的默认初始化程序,并在我们使用自己的代码自定义之前设置所有内容。

override init() {
    super.init()
}

您应该看到一个错误:'required' initializer 'init(coder:)' must be provided by subclass of 'SCNNode'。显然,此方法是必需的,因此单击“ Fix ”以实现它。我们甚至不必写它。感谢Xcode让我们的生活更轻松。

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

焦点方块属性

以与我们对网格相同的方式,让我们为焦点方块创建一个平面。后super.init(),声明一个平面并分配一个恒定宽度高度0.1这个时候。

let plane = SCNPlane(width: 0.1, height: 0.1)
plane.firstMaterial?.diffuse.contents = UIImage(named: "FocusSquare/close")
plane.firstMaterial?.isDoubleSided = true

geometry = plane
eulerAngles.x = GLKMathDegreesToRadians(-90)

然后,使用FocusSquare / close图像作为漫反射材质。也是双面的。将焦点方块的几何设置为我们刚刚定义的平面。这里,我们不需要planeNode,因为FocusSquare已经是一个节点。最后,旋转平面节点,使焦点方块与表格对齐,并且不垂直于表格。真棒,我们刚刚完成了课程,但我们还没有看到它。

类变量

为此,请转到ViewController.swift文件。我们将首先在sceneView声明之后为焦点方块声明一个类变量。它将是具有焦点方形类属性的节点。它也是一个可选项,因为有时它会在那里,有时候,它不是。两个名称之间的区别在于,类以大写字母F开头,而变量大小写为f。命名focusSquare的方法称为camel case,它是Swift中的标准命名约定。

var focusSquare: FocusSquare?

新实例

现在,是时候调用这个变量了。我们将在didAdd方法中生成焦点方块,仅在检测到表面时才在场景中显示。让我们首先设置一个安全措施,如果焦点平方为零,则继续。否则,退出。换句话说,如果它已经存在,那么不要创建一个新的。

guard focusSquare == nil else {return}
let focusSquareLocal = FocusSquare()
self.sceneView.scene.rootNode.addChildNode(focusSquareLocal)
self.focusSquare = focusSquareLocal

之后,创建焦点方形节点的新实例。这个将在本地使用,所以让我们在末尾添加单词Local以防止混淆。然后,通过将其添加到场景的根节点将其显示在屏幕上。最后,将其保存在稍后要使用的类变量下。运行该应用程序以查看我们的焦点方块。

我们现在能够看到它,但它的位置并不理想,就好像它是在相机的起始位置,这是世界起源。最重要的是,它是空闲的。我们希望它在场景中移动,以便我们可以选择一个位置来添加模型。

屏幕中心

让我们回到ViewController.swift并为屏幕的中心声明另一个变量。我们将它用作焦点方块的参考点,以便在我们移动相机时跟随它们。屏幕中心始终存在,因此它不是可选的。

var screenCenter: CGPoint!

viewDidLoad中,将屏幕的中心设置为视图的中心。

screenCenter = view.center

帧更新

现在,让我们导航回ViewController + ARSCNViewDelegate。为了使焦点方向移动,我们将使用渲染器方法updateAtTime。这是为了指示代表每帧更新一次,并在系统当前时间更新。

func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {}

在此方法中,创建另一个本地焦点方形节点。这一次,我们将确保在继续之前存在焦点方块。该focusSquare是我们以前存储的变量。如果有,则将其存储在局部变量中以更新场景。

 guard let focusSquareLocal = focusSquare else {return}

命中测试

我之前提到过,我们希望使用屏幕中心作为焦点方块的基准。我们知道屏幕中心是2D点,我们甚至将其定义为CGPoint。然而,为了在场景上定位节点,我们需要3D坐标。那么,我们如何将某些东西从2D转换为3D呢?答案是hitTest,这是一种ARHitTestResult方法,用于搜索与2D点和这些对象相交的真实世界对象。然后,它沿着相机指向的线对应于y坐标向2D点添加第三维。在代码中,我们解释为:

let hitTest = sceneView.hitTest(screenCenter, types: .existingPlane)

这确定了屏幕中心与检测到的水平表面的交点。

命中测试结果

命中测试返回结果列表,我们只想要这些结果的第一个元素。第一个元素是离相机最近的平面。例如,如果您将相机对准您的桌子,则您希望桌子不是地板。

let hitTestResult = hitTest.first

世界变换

命中测试的目的是检索表面的位置。并且该位置存储在世界变换中。世界变换是命中测试结果相对于世界坐标的节点变换属性。简而言之,这些结果包含有关变换的信息,如方向,位置和比例。

guard let worldTransform = hitTestResult?.worldTransform else {return}

世界变换是一个4x4矩阵,位置保留在第四列。因为矩阵是多维数组并且数组的值从0开始,所以第四列的数量是3。

let worldTransformColumn3 = worldTransform.columns.3

最后,将该位置指定给焦点方块。同时,它会随着相机的移动而更新。

focusSquareLocal.position = SCNVector3(worldTransformColumn3.x, worldTransformColumn3.y, worldTransformColumn3.z)

现在,运行应用程序。

焦点方块更新

接下来,我们想对焦点方块进行其他类型的更新。在viewWillDisappear之后的ViewController.swift中,为更新创建一个新函数。

func updateFocusSquare() {}

在那里,再次使用类变量在本地实例化一个新的焦点方块。另外,请确保它是第一手存在的。

guard let focusSquareLocal = focusSquare else {return}

现在,我们将进行另一次热门测试。但是这一次,我们将使用现有平面的范围,这意味着它将取决于平面的大小。原因是我们使用焦点方块告诉我们该点是否可以用作锚点,而不仅仅是用于查看目的。

let hitTest = sceneView.hitTest(screenCenter, types: .existingPlaneUsingExtent)

像以前一样,获得命中测试的第一个结果,我们将检查它是否击中了飞机。

if let hitTestResult = hitTest.first {
    print("Focus square hits a plane")
} else {
    print("Focus square does not hit a plane")
}

让我们在updateAtTime方法中调用updateFocusSquare()。我们需要使用DispatchQueue.main.async来在主线程中进行更新,这意味着在UI上,因为我们正在后台线程上执行代码。self绝对是必需的,因为它在一个闭包中并引用了ViewController类。不要太担心它,随着时间的推移,你将会理解所有这些对象,属性和闭包。

 DispatchQueue.main.async {self.updateFocusSquare()}

再次运行应用程序并注意控制台。

打开和关闭

我们如何为焦点方块添加漂亮的触感?您可能已经意识到我们有两个用于焦点方块的资产图像,一个是开放的,一个是关闭的。这应该会给你一个提示,我们都会在不同情况下使用它们。因此,在FocusSquare类中,让我们将一个变量isClosed作为布尔值(true或false)添加,以在打开和关闭状态之间切换图像。默认情况下,我们将其设置为true,因为它只在我们检测到曲面时才会显示在屏幕上。如果isClosed为true,请使用图像FocusSquare / close。如果没有,请改用FocusSquare / open

var isClosed : Bool = true {
    didSet {
        geometry?.firstMaterial?.diffuse.contents = self.isClosed ? UIImage(named: "FocusSquare/close") : UIImage(named: "FocusSquare/open")
    }
}

接下来,返回updateFocusSquare()函数。在if else语句中,如果焦点方块击中平面,则添加此代码。

let canAddNewModel = hitTestResult.anchor is ARPlaneAnchor
focusSquareLocal.isClosed = canAddNewModel

如果结果的锚点是平面锚点,那么它将是真的,我们将能够添加模型。如果是这种情况,那么焦点方块将是关闭方的图像。否则,将焦点方块打开。

focusSquareLocal.isClosed = false

运行应用程序。一切看起来都很棒但是如果你旋转设备怎么办?您将看到焦点方块不再粘在屏幕中间。

查看转换

当我们切换到横向模式时,我们将不得不更新屏幕的中心点。首先,让我们在updateFocusSquare()函数的正上方添加一个viewWillTransition子类。然后,将viewCenter声明为视图大小的中间点,并将该点分配给screenCenter

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        let viewCenter = CGPoint(x: size.width / 2, y: size.height / 2)
        screenCenter = viewCenter
}

网格删除

此时,我们不再需要看到网格了。我们现在有焦点方块向我们展示我们是否找到了合适的平面。到目前为止,它是为了帮助我们更好地可视化飞机和命中测试的结果。我们将在委托方法中注释掉与网格相关的代码。

didAdd中

//        let planeAnchor = anchor as! ARPlaneAnchor
//        let planeNode = createPlane(planeAnchor: planeAnchor)
//        node.addChildNode(planeNode)

didUpdate中

//        let planeAnchor = anchor as! ARPlaneAnchor
//
//        node.enumerateChildNodes { (childNode, _) in
//            childNode.removeFromParentNode()
//        }
//
//        let planeNode = createPlane(planeAnchor: planeAnchor)
//        node.addChildNode(planeNode)

didRemove中

//        node.enumerateChildNodes { (childNode, _) in
//            childNode.removeFromParentNode()
//        }

运行应用程序或最后一次并检查出来。

结论

在本课程中,您已经学习了很多很棒的东西,从创建自己的类开始并自定义它。你能够将焦点方块从非活动变形到整个房间循环,并在打开和关闭状态之间切换。焦点方块广泛用于要检测表面的AR应用程序中。命中测试也是一项重要功能。它允许用户在纯粹的设备和现实世界之间进行交互,提供这种娱乐体验。事实上,在增强现实之外,即使您点击此视频观看,也可以在任何地方找到热门测试。有了这个,继续下一节。到时候那里见。

原文: https://designcode.io/focus-square

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券