Xcode 12.5.1 SwiftUI ARKit RealityKit MacOS 11.5.1

本demo只是为了学习realitykit的使用,实际如果制作AR测距,推荐使用SceneKit来描绘点和线与文字。

效果如图

SwiftUI+RealityKit实现简单AR测距

首先在Xcode新建一个工程,选择Augmented Reality App.建好之后,会有默认的一个3d模型文件和一段默认的ARViewContainer的代码,运行后,当屏幕中心点找到水平面后,会显示一个立方体模型。

我们可以不用这个模型,用我们自己的代码替代

struct ARViewContainer: UIViewRepresentable {@EnvironmentObject var vm: ViewModelfunc makeUIView(context: Context) -> some UIView {return vm.arView}func updateUIView(_ uiView: UIViewType, context: Context) {}
}

还是用熟悉的MVVM模式来设计我们的app,新建一个swift文件,起名为ViewModel.swift

import SwiftUI
import RealityKit
import ARKitclass ViewModel: ObservableObject {//  static let shared = ViewModel()@Published var arView: ARView!@Published var position: CGPoint = .zero// MARK: - 记录屏幕上设置的点的位置@Published var simd_position: [simd_float4x4] = []// MARK: - 测量的距离@Published var distance: Float = 0.0// MARK: - 移动屏幕时定时发送过来的点,在有新的点时,旧的点要remove(anchor)var moveAnchorEntity: AnchorEntity?// MARK: - 移动屏幕时画的线,在有新的点时,旧的线要remove(anchor)var lineAnchorEntity: AnchorEntity?var sphere: ModelEntity?init() {arView = ARView(frame: .zero)let configuration = ARWorldTrackingConfiguration()configuration.planeDetection = [.horizontal, .vertical]//.horizontalarView.session.run(configuration)}
}

arView是ARKit的主视图,在初始化时初始化为可跟踪镜头的配置模式

测距我采用的方式是,第一个点使用手机屏幕的中心点,设置完第一个点后,启动定时器,实时将屏幕中心点传递给ViewModel,让ARKit计算实际物理世界中的点,并显示在arView中,同时画出两个点的连线。

苹果自带的测距仪App我不知道是不是用这种原理实现,因为我也是初次接触ARKit,所以只能用这种笨方法。

以下是View视图ContentView的源码

import SwiftUI
import RealityKit
import Combinestruct ContentView : View {@StateObject var vm: ViewModel = ViewModel()@State var timer = Timer.publish(every: 0.1, on: .main, in: .common)@State var cancellable: Cancellable? = nilvar body: some View {ZStack {ARViewContainer().edgesIgnoringSafeArea(.all).environmentObject(vm)VStack {HStack {Spacer()ClearButton.padding()}Spacer()Text("测距: \(vm.distance) 米").foregroundColor(.white).font(.title).bold()AddButton}}.onReceive(timer, perform: timerHandler())}
}extension ContentView {// MARK: - 清除按钮var ClearButton: some View {Button(action: {cancellable?.cancel()cancellable = nilvm.clearScreen()}, label: {Image(systemName: "trash.circle").resizable().aspectRatio(contentMode: .fit).frame(width: 50, height: 50, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/).foregroundColor(.white)})}// MARK: - 加号按钮var AddButton: some View {Button(action: {if cancellable == nil {// 第一次: 设置起始点,开始计时器vm.addStartNode(CGPoint(x: UIScreen.main.bounds.midX, y: UIScreen.main.bounds.midY))timer = Timer.publish(every: 0.1, on: .main, in: .common)cancellable = timer.connect()} else {// 第二次: 设置结束点,停止计时器cancellable?.cancel()cancellable = nil}}, label: {Image(systemName: "plus.circle.fill").resizable().aspectRatio(contentMode: .fit).frame(width: 40, height: 40).foregroundColor(.white)})}// MARK: - 计时器处理方法func timerHandler() -> (Timer.TimerPublisher.Output) -> Void {return { _ invm.moveNode(CGPoint(x: UIScreen.main.bounds.midX, y: UIScreen.main.bounds.midY))}}
}struct ARViewContainer: UIViewRepresentable {@EnvironmentObject var vm: ViewModelfunc makeUIView(context: Context) -> some UIView {return vm.arView}func updateUIView(_ uiView: UIViewType, context: Context) {}
}#if DEBUG
struct ContentView_Previews : PreviewProvider {static var previews: some View {ContentView()}
}
#endif

以下是ViewModel的源码:

import Foundation
import SwiftUI
import RealityKit
import ARKit
import TextEntityclass ViewModel: ObservableObject {//  static let shared = ViewModel()@Published var arView: ARView!@Published var position: CGPoint = .zero// MARK: - 记录屏幕上设置的点的位置@Published var simd_position: [simd_float4x4] = []// MARK: - 测量的距离@Published var distance: Float = 0.0// MARK: - 移动屏幕时定时发送过来的点,在有新的点时,旧的点要remove(anchor)var moveAnchorEntity: AnchorEntity?// MARK: - 移动屏幕时画的线,在有新的点时,旧的线要remove(anchor)var lineAnchorEntity: AnchorEntity?var sphere: ModelEntity?init() {arView = ARView(frame: .zero)let configuration = ARWorldTrackingConfiguration()configuration.planeDetection = [.horizontal, .vertical]//.horizontalarView.session.run(configuration)}
}// MARK: - Public Extension
extension ViewModel {/// 放置初始点/// - 放置后开始追踪屏幕中心点,并连线/// - Parameters:///   - p: CGPoint 屏幕坐标/// - Returns: 无public func addStartNode(_ p: CGPoint) {// MARK: - 设置初始点前先清空可能存在的上一对连接点和线clearScreen()// MARK: - 转换屏幕坐标为物理世界坐标let query = arView.makeRaycastQuery(from: p, allowing: .existingPlaneInfinite, alignment: .any)if let firstResult = query {let results = arView.session.raycast(firstResult)if let position = results.first?.worldTransform {drawNode(position)// MARK: - 存储到simd_positionsimd_position.append(position)}}}/// 放置结束点/// - 在初始点放置后,可以放置结束点,放置后显示两点连线/// - Parameters:///   - p: CGPoint 屏幕坐标/// - Returns: 无public func addEndNode(_ p: CGPoint) {// MARK: - 转换屏幕坐标为物理世界坐标let query = arView.makeRaycastQuery(from: p, allowing: .existingPlaneInfinite, alignment: .any)if let firstResult = query {let results = arView.session.raycast(firstResult)if let position = results.first?.worldTransform {drawNode(position)// MARK: - 存储到simd_positionsimd_position.append(position)}}}/// 移动结束点/// - 初始点设置完后,如果屏幕中心点变化,在物理世界中追踪这个变化/// - Parameters:///   - p: simd_float4x4 物理世界坐标/// - Returns: 无public func moveNode(_ p: CGPoint) {let query = arView.makeRaycastQuery(from: p, allowing: .existingPlaneInfinite, alignment: .any)if let firstResult = query {let results = arView.session.raycast(firstResult)if let position = results.first?.worldTransform {let mesh = MeshResource.generatePlane(width: 0.005, depth: 0.005, cornerRadius: 1)let material = SimpleMaterial(color: .orange, isMetallic: false)let modelEntity = ModelEntity(mesh: mesh, materials: [material])if moveAnchorEntity != nil {arView.scene.removeAnchor(moveAnchorEntity!)}moveAnchorEntity = AnchorEntity(world: position)moveAnchorEntity!.addChild(modelEntity)arView.scene.addAnchor(moveAnchorEntity!)self.drawMoveLine(simd_position[0], position)}}}/// 清除已有的点和线/// - 清空对象:simd_position数组 arView.scene.anchors序列/// - Returns: 无public func clearScreen() {simd_position.removeAll()arView.scene.anchors.removeAll()}
}// MARK: - Private Extension
extension ViewModel {/// 在物理世界画一个点/// - 用generatePlane + SimpleMaterial方式只能设置为灰色 ???/// - Parameters:///   - p: simd_float4x4 物理世界坐标/// - Returns: 无private func drawNode(_ p: simd_float4x4) {let mesh = MeshResource.generatePlane(width: 0.005, depth: 0.005, cornerRadius: 1)let material = SimpleMaterial(color: .orange, isMetallic: true)let modelEntity = ModelEntity(mesh: mesh, materials: [material])let anchorEntity = AnchorEntity(world: p)anchorEntity.addChild(modelEntity)arView.scene.addAnchor(anchorEntity)}/// 根据给定的两点画线/// ```/// - 屏幕只保留一条线,之前画的线在新画前要清除/// -- startPoint: simd_float4x4 起始点/// -- endPoint:   simd_float4x4 结束点private func drawMoveLine(_ startPoint: simd_float4x4, _ endPoint: simd_float4x4) {let s = startPointlet e = endPointlet start = startPoint.columns.3let end = endPoint.columns.3/// 判断当前的anchor集合中是否包含lineAnchorEntity/// 如果包含则removelet _ = arView.scene.anchors.contains { anchor inif anchor == lineAnchorEntity {arView.scene.removeAnchor(anchor)return true}return false}// MARK: box的深度,深度为两个点的距离,依次来形成线let meters = simd_distance(start, end)// MARK: 连线,用box形式显示let rectangle = ModelEntity(mesh: .generateBox(width: 0.0008, height: 0.0008, depth: meters), materials: [SimpleMaterial(color: UIColor(.orange), isMetallic: false)])// MARK: 两点的中心点let middlePoint : simd_float3 = simd_float3((start.x + end.x)/2, (start.y + end.y)/2, (start.z + end.z)/2)lineAnchorEntity = AnchorEntity()lineAnchorEntity!.position = middlePointlet startPoint = SIMD3(start.x, start.y, start.z)// MARK: Positions and orients the entity to look at a target from a given position.//       定位和定向实体以从给定位置看目标。 ( ??? )lineAnchorEntity!.look(at: startPoint, from: middlePoint, relativeTo: nil)lineAnchorEntity!.addChild(rectangle)arView.scene.addAnchor(lineAnchorEntity!)calcuteMoveDistance(s, e)}private func addLineNode(start: simd_float4, end: simd_float4) {// MARK: box的深度,深度为两个点的距离,依次来形成线let meters = simd_distance(start, end)// MARK: 连线,用box形式显示let rectangle = ModelEntity(mesh: .generateBox(width: 0.0008, height: 0.0008, depth: meters), materials: [SimpleMaterial(color: UIColor(.orange), isMetallic: false)])// MARK: 两点的中心点let middlePoint : simd_float3 = simd_float3((start.x + end.x)/2, (start.y + end.y)/2, (start.z + end.z)/2)lineAnchorEntity = AnchorEntity()lineAnchorEntity!.position = middlePointlet startPoint = SIMD3(start.x, start.y, start.z)// MARK: Positions and orients the entity to look at a target from a given position.//       定位和定向实体以从给定位置看目标。 ( ??? )lineAnchorEntity!.look(at: startPoint, from: middlePoint, relativeTo: nil)lineAnchorEntity!.addChild(rectangle)arView.scene.addAnchor(lineAnchorEntity!)}/// 计算物理世界两点距离/// ```/// - 无/// -- startPoint: simd_float4x4 起始点/// -- endPoint:   simd_float4x4 结束点func calcuteMoveDistance(_ startPoint: simd_float4x4, _ endPoint: simd_float4x4) {let start = startPointlet end = endPointdistance = sqrt(pow(end.columns.3.x - start.columns.3.x, 2) +pow(end.columns.3.y - start.columns.3.y, 2) +pow(end.columns.3.z - start.columns.3.z, 2))}/// 根据给定的两点画线/// ```/// - startPoint: simd_float4x4 起始点/// - endPoint:   simd_float4x4 结束点private func drawLine(_ startPoint: simd_float4x4, _ endPoint: simd_float4x4) {let start = startPoint.columns.3let end = endPoint.columns.3// MARK: box的深度,深度为两个点的距离,依次来形成线let meters = simd_distance(start, end)// MARK: 连线,用box形式显示let rectangle = ModelEntity(mesh: .generateBox(width: 0.003, height: 0.003, depth: meters), materials: [SimpleMaterial(color: UIColor(.blue), isMetallic: false)])// MARK: 两点的中心点let middlePoint : simd_float3 = simd_float3((start.x + end.x)/2, (start.y + end.y)/2, (start.z + end.z)/2)let lineAnchor = AnchorEntity()lineAnchor.position = middlePointlet startPoint = SIMD3(start.x, start.y, start.z)// MARK: Positions and orients the entity to look at a target from a given position.//       定位和定向实体以从给定位置看目标。 ( ??? )lineAnchor.look(at: startPoint, from: middlePoint, relativeTo: nil)lineAnchor.addChild(rectangle)arView.scene.addAnchor(lineAnchor)}}

需要注意的一点是,我们需要增加一个Launch Screen.storyboard,否则AR无法全屏显示。

SwiftUI + RealityKit 实现简单AR测距相关推荐

  1. ARFoundation系列讲解 - 71 AR测距一

    (图片来源于网络) 一.案例工程开发环境(建议保持一致) 系统环境:Mac.Win 开发软件:Unity 2019.2.4f1.Xcode 10.3(10G8) 测试环境:支持ARCore的安卓手机. ...

  2. 2021-01-05 Halcon初学者知识【7】 最简单的测距问题-点与点测距

    Halcon初学者知识[7] 最简单的测距问题-点与点测距 点与点测距的思路,是建立在两个不同坐标点上的距离计算问题.由于在现实中,需要测定的是几何上的点距,比如,两个圆的圆心距离.一个线与另一条线的 ...

  3. Unity3d学习之路-简单AR游戏

    简单AR游戏 简单AR游戏 游戏规则 游戏实现 游戏场景的搭建 游戏逻辑的实现 游戏脚本挂载 游戏打包到安卓平台 实现效果 小结 游戏规则 识别指定图片,显示玩家和防御塔,点击按键对玩家进行上下左右移 ...

  4. 手机相机识别实现ar测距(AR尺子)

    [实例简介]手机相机识别实现ar测距,至少三年以上开发经验才能跑通 [实例截图] 文件:590m.com/f/25127180-490561034-8afed0(访问密码:551685) [核心代码] ...

  5. 安卓手机测评_iPhone有的AR测距功能,安卓也得安排上

    「本期内容标签」安卓 iOS 电脑 小程序 网站 游戏 教程相信不少iPhone用户自带一项AR功能,可以用手机相机来测量各种物体的长度或者高度,而此功能对于安卓用户来说,其实也是可以有的.谷歌的AR ...

  6. Unity3D之简单AR游戏

    使用Unity开发AR游戏的基础配置与操作可以参考老师的博客,里面写的非常详细,很适合入门. 但是老师的博客里面介绍的是旧版本的unity配置,现在到官网下载安装 Vuforia 插件时步骤有些不同, ...

  7. [Android] 分享一款国产安卓AR测距app 非常准 功能强大

    前两天看到有人分分享了谷歌的测距app. 搜索了下好像论坛没人发布过这款国产的app. app完全无广告.作者也是非常良心永久免费. 下载地址:https://www.90pan.com/b18025 ...

  8. Unity实现简单AR功能

    一.首先进入Vuforia官网:Home | Vuforia EDP,也可以直接百度,第一条就是,点击进入,注册登录. 二.点击develop进行许可管理注册,依次点击两个蓝色框框中的按钮,获取一个K ...

  9. 分贝测试发型软件,‎测量工具-分贝噪音测试尺子角度AR测距 im App Store

    产品功能介绍: 1·AR测量平面 不仅仅针对一个平面,本功能可以针对物体多角度进行测量. 2·AR测量高度 透过AR测量高度可以多维度全方面同时多点位进行测量,一目了然. 3·AR测量宽度 拖过AR工 ...

最新文章

  1. 搭建Docker私有仓库--自签名方式
  2. hive的multi-distinct可能带来性能恶化
  3. app:compileDebugJavaWithJavac
  4. hive 常见面试题
  5. 百度资源管理平台 站长工具 批量添加主站域名 子站域名 域名主动推送
  6. c#开发中程序集调用时容易忽略的问题
  7. 如何通过数据包套接字攻击Linux内核
  8. 2019CCPC湖南全国邀请赛-Chika and Friendly Pairs- 莫队+树状数组+离散化
  9. 厦门高桔科技有限公司
  10. Linux自动注销登录的帐户
  11. pcl1.8.1+vs2017
  12. qs.stringify()详情
  13. c语言四则混合运算可以带括号,带小括号的四则混合运算听后感
  14. jquery中的各种动画效果
  15. 蓝桥杯_大胖子走迷宫
  16. 从模拟信号到数字信号
  17. http网页返回状态码含义
  18. 美剧推荐:abc新剧Traveler
  19. Hadoop 调优之Linux操作系统调优篇
  20. 计算机电源大小,电源功率到底选多大?老司机告诉你电源功率怎么选?

热门文章

  1. Java+SSM农场信息管理系统(含源码+论文+答辩PPT等)
  2. 都说我像孙俪,真的像吗?素颜生活照,求达人鉴定!!
  3. i5 10210u和i5 8265u差别大吗
  4. PDA连接打印机使用说明
  5. android类似苹果clips,【赛后】苹果发布会汇总:越来越像安卓的 iOS 14,还有憋出一个 ARM 大招...
  6. achartengine一个布局中多条动态折线图实时更新效果
  7. 计算机网络遵循,计算机网络原理 OSI参考模型遵循原则
  8. win10 20H2 下QQ,TIM 无法访问个人文件夹 个人文件夹将被保存到“我的文档
  9. 这篇文章告诉你视频配音制作的方法
  10. Android: 创建一个AlertDialog对话框,必须按确定或取消按钮才能关闭对话框,禁止按[返回键]或[搜索键]关闭...