前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聚焦位置-选择您喜欢的位置放置虚拟物体

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

作者头像
iOSDevLog
发布2019-06-17 14:49:53
2.4K0
发布2019-06-17 14:49:53
举报
文章被收录于专栏:iOSDevLogiOSDevLog

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

下载

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

焦点方块 Focus Square 类

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

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

代码语言:javascript
复制

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

代码语言:javascript
复制

初始化

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

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

代码语言:javascript
复制
override init() {
    super.init()
}

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

代码语言:javascript
复制
required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

焦点方块属性

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

代码语言:javascript
复制
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中的标准命名约定。

代码语言:javascript
复制
var focusSquare: FocusSquare?

新实例

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

代码语言:javascript
复制
guard focusSquare == nil else {return}
let focusSquareLocal = FocusSquare()
self.sceneView.scene.rootNode.addChildNode(focusSquareLocal)
self.focusSquare = focusSquareLocal

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

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

屏幕中心

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

代码语言:javascript
复制
var screenCenter: CGPoint!

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

代码语言:javascript
复制
screenCenter = view.center

帧更新

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

代码语言:javascript
复制
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {}

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

代码语言:javascript
复制
 guard let focusSquareLocal = focusSquare else {return}

命中测试

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

代码语言:javascript
复制
let hitTest = sceneView.hitTest(screenCenter, types: .existingPlane)

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

命中测试结果

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

代码语言:javascript
复制
let hitTestResult = hitTest.first

世界变换

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

代码语言:javascript
复制
guard let worldTransform = hitTestResult?.worldTransform else {return}

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

代码语言:javascript
复制
let worldTransformColumn3 = worldTransform.columns.3

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

代码语言:javascript
复制
focusSquareLocal.position = SCNVector3(worldTransformColumn3.x, worldTransformColumn3.y, worldTransformColumn3.z)

现在,运行应用程序。

焦点方块更新

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

代码语言:javascript
复制
func updateFocusSquare() {}

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

代码语言:javascript
复制
guard let focusSquareLocal = focusSquare else {return}

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

代码语言:javascript
复制
let hitTest = sceneView.hitTest(screenCenter, types: .existingPlaneUsingExtent)

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

代码语言:javascript
复制
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类。不要太担心它,随着时间的推移,你将会理解所有这些对象,属性和闭包。

代码语言:javascript
复制
 DispatchQueue.main.async {self.updateFocusSquare()}

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

打开和关闭

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

代码语言:javascript
复制
var isClosed : Bool = true {
    didSet {
        geometry?.firstMaterial?.diffuse.contents = self.isClosed ? UIImage(named: "FocusSquare/close") : UIImage(named: "FocusSquare/open")
    }
}

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

代码语言:javascript
复制
let canAddNewModel = hitTestResult.anchor is ARPlaneAnchor
focusSquareLocal.isClosed = canAddNewModel

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

代码语言:javascript
复制
focusSquareLocal.isClosed = false

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

查看转换

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

代码语言:javascript
复制
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中

代码语言:javascript
复制
//        let planeAnchor = anchor as! ARPlaneAnchor
//        let planeNode = createPlane(planeAnchor: planeAnchor)
//        node.addChildNode(planeNode)

didUpdate中

代码语言:javascript
复制
//        let planeAnchor = anchor as! ARPlaneAnchor
//
//        node.enumerateChildNodes { (childNode, _) in
//            childNode.removeFromParentNode()
//        }
//
//        let planeNode = createPlane(planeAnchor: planeAnchor)
//        node.addChildNode(planeNode)

didRemove中

代码语言:javascript
复制
//        node.enumerateChildNodes { (childNode, _) in
//            childNode.removeFromParentNode()
//        }

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

结论

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

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.02.28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 下载
  • 焦点方块 Focus Square 类
    • 初始化
      • 焦点方块属性
      • 类变量
      • 新实例
      • 屏幕中心
      • 帧更新
      • 命中测试
        • 命中测试结果
        • 世界变换
        • 焦点方块更新
        • 打开和关闭
        • 查看转换
        • 网格删除
        • 结论
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档