将模型添加到场景中 - 在您的环境中显示3D内容

在最后几节中,我们能够检测到一个平面并显示一个焦点方块,以帮助我们为模型指定一个位置。我们也熟悉了热门测试和世界变换。现在,我们拥有显示虚拟对象所需的所有工具。在本教程中,我们将学习如何检索模型并使用按钮的触发器将其呈现在场景中。一旦显示,我们将隐藏焦点方块。

下载

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

基本视图

Main.Storyboard中,我们已经提到ARSCNView默认放在视图控制器的顶部。但是,如果没有UIView作为基础,则仅限于您可以在用户界面上执行的操作。为了能够正确添加我们的按钮,我们必须删除当前的 ARSCNView并首先从对象库添加UIView作为底层。接下来,选择相同的ARKit SceneKit View并将其放回UIView之上。调整大小以填充整个视图控制器。

约束

然后,单击Storyboard编辑器左下角的第四个图标,将新约束添加到场景视图中。定义约束以确保您的用户界面适应不同的屏幕尺寸或设备方向。设置为0顶部底部。确保它们都被约束到视图而不是安全区域,然后单击Add Constraints安全区域是凹口下方和主页指示器上方的边距,通常是屏幕的可见部分。此外,请确保未选中“ 限制到边距”

如果被限制在安全区域而不是超级视图,这就是看起来的样子,显然,这看起来并不好看。

横屏约束安全区

重新 Outlet

请记住,一个IBOutletsceneView链接到ARSCNView?因为我们删除了旧的ARSCNView,所以它打破了这个Outlet。我们需要重新考虑新的。为此,请打开“ 助理”编辑器,该图标看起来像两个交织在一起的圆圈。现在,我们并排放置两个分屏,非常适合连接。在右侧,我们有ViewController.swift,在那里我们可以找到该出口的声明。单击并拖动左侧的圆圈,它应该是第15行,然后释放到ARSCNView上。现在,关闭助理编辑。

添加按钮

我们想在视图中添加一个按钮,用作在场景中添加模型的触发器。从对象库中,将UIButton拖动到场景视图的顶部。在“ 属性”检查器中,删除“ 按钮”标题并将图像设置为“ 按钮/添加”

约束到底部20但这次是在安全区域,并取消选中Constrain到边距。然后,将鼠标悬停在左侧的“ 对齐”图标上,并在“容器”中选中“水平”以在屏幕中水平居中。

添加按钮功能

我们刚刚在屏幕上添加了按钮,但它根本没有做任何事情。当我们触摸它时,让按钮执行某些操作。现在,打开Assistant编辑器并控制将故事板中的按钮拖到ViewController类。代码中的顺序并不重要,因为我们稍后会移动此函数。原因是我们不能在扩展类中执行此操作。将Connection更改为Action,将其命名为addObjectButtonTapped。保持原样。完成后,关闭“ 助理”编辑器

@IBAction func addObjectButtonTapped(_ sender: Any) {
    print("Add button tapped")
}

让我们运行应用程序来查看我们的新按钮。但在此之前,评论一些印刷品陈述是明智的。转到updateFocusSquare()并注释掉这些代码行。

// print("Focus square hits a plane")

// print("Focus square does not hit a plane")

对象添加文件

让我们创建另一个swift文件,以便在场景中添加模型。右键单击视图控制器+ ARSCNViewDelegate.swift并选择新建文件...。然后,选择Swift File,单击Next。称之为ViewController + ObjectAddition,然后是Create

导入套件(Kits)

与往常一样,用以下框架替换Foundation。然后,向ViewController添加扩展。

import UIKit
import SceneKit
import ARKit

extension ViewController {}

检索模型

在扩展内部,创建一个新函数来检索我们选择的模型是一个很好的主动。此函数仅在此文件中使用,因此我们将采用fileprivate。将有一个String类型的参数,它将有两个名称。在函数外部使用的那个被命名,而在函数内使用的是名称。它将返回一个可选的SCNNode

fileprivate func getModel(named name: String) -> SCNNode? {}

与飞船场景类似,我们将使用我们指定的名称调用场景。然后,检索该场景SketchUp的父节点。我们递归设置为false以返回具有该名称的直接子节点。如果为true,它将解析所有节点,直到找到它为止。我们知道SketchUp是场景中唯一的节点,所以在我们的情况下,真实的不准确。之后,我们将变量名称分配给模型的名称。最后,此函数将在调用时返回模型。

let scene = SCNScene(named: "art.scnassets/\(name)/\(name).scn")!
guard let model = scene.rootNode.childNode(withName: "SketchUp", recursively: false) else {return nil}
model.name = name
return model

可选:PIVOT POINT FIX

如果您需要将模型的轴心点修改为所有3轴的中心,那么您可以在此处执行此操作。您可以使用以下公式。别客气。

let min = model.boundingBox.min
let max = model.boundingBox.max

model.pivot = SCNMatrix4MakeTranslation(
    min.x + (max.x - min.x) / 2,
    min.y + (max.y - min.y) / 2,
    min.z + (max.z - min.z) / 2)

显示模型

我们刚刚完成了这个功能,现在,我们准备在点击按钮时在场景中显示我们的模型。让我们转到ViewController.swift并剪切动作函数addObjectButtonTapped并将其粘贴到这里以将其全部放在一个地方。

我们首先确保焦点方块首先存在,因为它只在检测到表面时才出现在屏幕上。

guard focusSquare != nil else {return}

我们选择展示的模型是iPhoneX。因此,我们将使用getModel函数检索该模型。如果由于某种原因它失败了,我们将打印一条消息给我们。然后,让我们用一个小消息将它添加到场景中。

let modelName = "iPhoneX"
guard let model = getModel(named: modelName) else {
    print("Unable to load \(modelName) from file")
    return
}

sceneView.scene.rootNode.addChildNode(model)
print("\(modelName) added successfully")

运行应用程序。您将意识到该设备不仅站起来而且漂浮在空中。当然,我们已经在场景中添加了我们的模型,我们还没有把它放在表面上。所以,让我们这样做。

命中测试

显然,我们将再次使用命中测试,方法与之前相同。

let hitTest = sceneView.hitTest(screenCenter, types: .existingPlaneUsingExtent)
guard let worldTransformColumn3 = hitTest.first?.worldTransform.columns.3 else {return}
model.position = SCNVector3(worldTransformColumn3.x, worldTransformColumn3.y, worldTransformColumn3.z)

翻转设备

要将电话平放在桌子上,请打开iPhoneX.scn。在“ 节点”检查器中,将x Euler Angle重置为0

让我们再试一次。现在,我们的设备看起来更像是在房间里。

缩放模型

如果您选择了其他型号,您可能已经注意到尺寸不合适。因此,我们将扩展它们中的每一个。我们在iPhoneX的场景编辑器中完成了它。现在,我们在这里撤消它并代之以编码。让我们为所有边界将比例放回到1

回到ViewController + ObjectAddition并在getModel函数中,我们首先为比例声明一个变量,然后根据模型设置不同的值。在我们的情况下,使用[switch]控制流来匹配我们设置的许多条件是完美的。switch语句必须是详尽的,这就是为什么有一个默认情况来涵盖所有其他方案。

var scale: CGFloat

switch name {
    case "iPhoneX":         scale = 0.025
    case "iPhone6s":        scale = 0.025
    case "iPhone7":         scale = 0.0001
    case "iPhone8":         scale = 0.000008
    case "iPhone8Plus":     scale = 0.000008
    case "iPad4":           scale = 0.0006
    case "MacBookPro13":    scale = 0.0029
    case "iMacPro":         scale = 0.0245
    case "AppleWatch":      scale = 0.0000038
    default:                scale = 1
}

在返回之前将模型缩放到我们之前分配的值。

model.scale = SCNVector3(scale, scale, scale)

场景模型

知道我们在场景中有多少模型会很高兴。在ViewController.swift中,将一个新的类变量声明为一个节点数组,我们将其初始化为空。

var modelsInTheScene: Array<SCNNode> = []

返回ViewController + ObjectAddition.swift,并在addObjectButtonTapped操作方法的末尾您添加的每个模型追加到数组modelsInTheScene中。然后,打印该数组的计数

modelsInTheScene.append(model)
print("Currently have \(modelsInTheScene.count) model(s) in the scene")

我们如何运行应用程序并坚果?

焦点方块隐藏/显示选项

当我们在屏幕上显示模型时,我们仍然看到焦点方块干扰了我们漂亮的模型。如果我们在安置后隐藏它,你怎么说?

FocusSquare类中,让我们创建一个函数来为焦点方块的表示设置动画。将隐藏和显示两种情况,因此隐藏值是布尔值。然后我们声明一个SCNAction用于淡入淡出,淡出用于隐藏和淡入显示。这些行动将运行根据是否隐藏是真还是假,一前一后。为此目的使用序列

func setHidden(to hidden: Bool) {
    var fadeTo: SCNAction
        
    if hidden {
        fadeTo = .fadeOut(duration: 0.5)
    } else {
        fadeTo = .fadeIn(duration: 0.5)
    }
        
    let actions = [fadeTo, .run({ (focusSquare: SCNNode) in
        focusSquare.isHidden = hidden
    })]
    runAction(.sequence(actions))
}

视图观点

下一步将有点棘手。如果我们看到模型,我们希望隐藏焦点方块,对吧?但是,如果我们在屏幕上看不到任何内容呢?我们再次需要它来选择下一个位置。我们在屏幕上看到的是不断变化的,所以我们需要在updateFocusSquare()中实现它。在那里,让我们将pointOfView设置为场景视图的视角。

guard let pointOfView = sceneView.pointOfView else {return}

然后,让我们将firstVisibleModel的定义作为场景中的第一个模型。我们正在使用第一个返回满足条件的第一个元素的方法。如果节点从视角可见,它将返回true或false 。

let firstVisibleModel = modelsInTheScene.first { (node) -> Bool in
    return sceneView.isNode(node, insideFrustumOf: pointOfView)
}

隐藏/显示焦点方块

现在,如果第一个模型是可见的而不是零,则模型将在视图中可见。请记住,如果显示模型,我们将隐藏焦点方块,反之亦然。如果这两个因子的值不相等,我们将改变焦点平方的isHidden值。

let modelsAreVisible = firstVisibleModel != nil

if modelsAreVisible != focusSquareLocal.isHidden {
    focusSquareLocal.setHidden(to: modelsAreVisible)
}

实际上,这一切都令人困惑。我们实际上没有选择,因为节点具有isHidden的属性,并且不显示一个for。好吧,不是我所知道的。

那么,让我们来看看这两个场景。如果modelsAreVisible为true且focusSquareLocal.isHidden为false,则表示两者都可见,然后使setHidden为true(与modelsAreVisible值相同)以隐藏焦点方块。另一方面,如果modelsAreVisible为false且focusSquareLocal.isHidden为true,则两者都无处可见,然后setHidden为false以显示焦点方块。听起来很合乎逻辑。有了它,让我们最后一次运行应用程序。

结论

经过漫长的旅程,我们终于将我们的模型添加到我们的环境中,好像它们属于它。我们在本节中也学到了其他有用的概念。我们在故事板中定制了我们的视图,并在代码中播放动画。在下一课中,我们将使用虚拟对象本身。敬请关注。

原文: https://designcode.io/arkit-adding-models

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

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券