
最近,我们将旧技术与现代技术结合在一起,其中的内容被删减了。
增强现实
作为城市指南的增强现实应用程序是一个众所周知的主题,并由许多开发人员实施。 使用AR的方向是第一个,因为它允许您使用增强现实的所有显而易见的可能性:向用户显示有关建筑物的信息,提供有关机构工作的信息并熟悉景点。 在公司内部举行的最后一次黑客马拉松中,提出了几个使用增强现实的项目,我们想到了创建一个AR应用程序的想法,该应用程序将显示过去的地标或历史位置。 为此,请将现代增强现实技术与旧照片结合起来。 例如,面对圣以撒大教堂,您可以将智能手机摄像头对准他,并查看他的第一座木制建筑物,该建筑物于1715年被拆毁。
工作原理如下:该应用程序在地图上显示城市的给定历史景点和景点,显示有关城市的简要信息,并通过通知的方式通知用户他离有趣的地方不远。 当一个人走近40米的历史古迹时,AR模式可用。 同时,相机打开,有关对象的简要信息直接显示在用户周围的空间中。 后者具有与虚拟对象进行交互的能力:通过触摸历史位置的卡片,您可以继续查看带有图像的相册。
看起来该应用程序非常简单,但是即使在这里也存在一些陷阱。 我不会讲一个琐碎的事情的故事,例如从服务器下载数据或在地图上显示点,我将直接介绍引起问题的功能。
问题1.浮点数
因此,首先要做的是根据历史位置相对于当前位置的实际位置以及用户注视的方向将标记点放置在空间中。
首先,我们决定使用已经为iOS准备的库:
ARKit-CoreLocation 。 该项目位于公共领域的GitHub上,除主要类的代码外,还包含集成示例,并使我们能够在几个小时内完成我们感兴趣的任务。 只需要向库提供点的坐标和用作标记的图像。
毫不奇怪,这种轻松必须付出。 标记点不断在太空中漂浮:它们要么爬到天花板上,要么被绘制在脚下的某个地方。 并非每个用户都同意在几分钟内抓住AR对象的焦点,以熟悉他感兴趣的信息。
事实证明,许多人都遇到了该库错误,但尚未找到解决方案。 不幸的是,GitHub上的代码没有更新超过六个月,所以我不得不绕过它。
我们尝试使用海拔高度而不是坐标中的固定海拔高度,LocationManager会针对用户的当前位置返回该海拔高度。 但是,这并不能完全消除问题。 用手扭转设备后,来自位置管理器的数据就开始以高达60米的距离跳跃。 结果,图片变得不稳定,这当然不适合我们。
结果,决定放弃ARKit-CoreLocation库,并自行在空间中放置点。 Christopher Web-Orenstein撰写的文章ARKit和CoreLocation对此起到了很大作用。 我不得不花更多的时间并刷新内存中的一些数学方面,但是结果却是值得的:AR对象终于可以代替它们了。 之后,仅需沿Y轴散布它们即可使标签和点更易于阅读,并在从当前位置到该点的距离与AR对象的Z坐标之间建立对应关系,从而使有关最近历史位置的信息成为前景。
有必要计算新的SCNNode在空间中的位置,着重于坐标:
let place = PlaceNode() let locationTransform = MatrixHelper.transformMatrix(for: matrix_identity_float4x4, originLocation: curUserLocation, location: nodeLocation, yPosition: pin.yPos, shouldScaleByDistance: false) let nodeAnchor = ARAnchor(transform: locationTransform) scene.session.add(anchor: nodeAnchor) scene.scene.rootNode.addChildNode(place)
以下函数已添加到MatrixHelper类:
class MatrixHelper { static func transformMatrix(for matrix: simd_float4x4, originLocation: CLLocation, location: CLLocation, yPosition: Float) -> simd_float4x4 { let distanceToPoint = Float(location.distance(from: originLocation)) let distanceToNode = (10 + distanceToPoint/1000.0) let bearing = GLKMathDegreesToRadians(Float(originLocation.coordinate.direction(to: location.coordinate))) let position = vector_float4(0.0, yPosition, -distanceToNode, 0.0) let translationMatrix = MatrixHelper.translationMatrix(with: matrix_identity_float4x4, for: position) let rotationMatrix = MatrixHelper.rotateAroundY(with: matrix_identity_float4x4, for: bearing) let transformMatrix = simd_mul(rotationMatrix, translationMatrix) return simd_mul(matrix, transformMatrix) } static func translationMatrix(with matrix: matrix_float4x4, for translation : vector_float4) -> matrix_float4x4 { var matrix = matrix matrix.columns.3 = translation return matrix } static func rotateAroundY(with matrix: matrix_float4x4, for degrees: Float) -> matrix_float4x4 { var matrix : matrix_float4x4 = matrix matrix.columns.0.x = cos(degrees) matrix.columns.0.z = -sin(degrees) matrix.columns.2.x = sin(degrees) matrix.columns.2.z = cos(degrees) return matrix.inverse } }
为了计算方位角,添加了扩展
CLLocationCoordinate2D extension CLLocationCoordinate2D { func calculateBearing(to coordinate: CLLocationCoordinate2D) -> Double { let a = sin(coordinate.longitude.toRadians() - longitude.toRadians()) * cos(coordinate.latitude.toRadians()) let b = cos(latitude.toRadians()) * sin(coordinate.latitude.toRadians()) - sin(latitude.toRadians()) * cos(coordinate.latitude.toRadians()) * cos(coordinate.longitude.toRadians() - longitude.toRadians()) return atan2(a, b) } func direction(to coordinate: CLLocationCoordinate2D) -> CLLocationDirection { return self.calculateBearing(to: coordinate).toDegrees() } }
问题2:过多的AR对象
我们遇到的下一个问题是大量的AR对象。 我们城市中有许多历史古迹和景点,因此骰子与信息融合并相互爬行。 用户很难辨认出部分铭文,这可能会给人留下令人反感的印象。 协商后,我们决定限制同时显示的AR对象的数量,只保留距当前位置500米半径范围内的点。
但是,在某些地区,积分的集中度仍然很高。 因此,为了增加可见性,他们决定使用群集。 在地图屏幕上,由于MapKit中嵌入了逻辑,因此默认情况下此功能可用,但是在AR模式下,必须手动实现。
聚类基于从当前位置到目标的距离。 因此,如果该点落入半径等于用户与列表中先前吸引点之间距离的一半的区域中,则该点将简单地隐藏起来并成为群集的一部分。 当用户接近它时,距离减小,并且群集区域的半径相应减小,因此位于附近的景点不会合并到群集中。 为了在视觉上区分单个点的聚类,我们决定更改标记的颜色,并显示AR中的对象数而不是地名。

为了确保AR对象的交互性,将UITapGestureRecognizer挂在ARSCNView上,并使用hitTest方法在处理程序中检查用户单击了哪个SCNNode对象。 如果结果是附近景点的照片,则该应用程序以全屏模式打开相应的相册。
问题3。雷达
在实施该应用程序期间,有必要在小型雷达上显示这些点。 从理论上讲,不应该有任何误解,因为我们已经计算了到该点的方位角和距离,甚至设法将它们转换为3D坐标。 只需要将点放置在屏幕上的二维空间中即可。

为了避免重蹈覆辙,他们转向了
Radar库,该库的开源代码发布在GitHub上。 该示例生动生动的预览和灵活的设置令人鼓舞,但实际上,这些点相对于空间中的真实位置是偏移的。 在花了一些时间尝试校正公式之后,我们转向了
iPhone Augmented Reality Toolkit中描述的不太美观但可靠的选项:
func place(dot: Dot) { var y: CGFloat = 0.0 var x: CGFloat = 0.0 if degree < 0 { degree += 360 } let bearing = dot.bearing.toRadians() let radius: CGFloat = 60.0 // radius of the radar view if (bearing > 0 && bearing < .pi / 2) { //the 1 quadrant of the radar x = radius + CGFloat(cosf(Float((.pi / 2) - bearing)) * Float(dot.distance)) y = radius - CGFloat(sinf(Float((.pi / 2) - bearing)) * Float(dot.distance)) } else if (bearing > .pi / 2.0 && bearing < .pi) { //the 2 quadrant of the radar x = radius + CGFloat(cosf(Float(bearing - (.pi / 2))) * Float(dot.distance)) y = radius + CGFloat(sinf(Float(bearing - (.pi / 2))) * Float(dot.distance)) } else if (bearing > .pi && bearing < (3 * .pi / 2)) { //the 3 quadrant of the radar x = radius - CGFloat(cosf(Float((3 * .pi / 2) - bearing)) * Float(dot.distance)) y = radius + CGFloat(sinf(Float((3 * .pi / 2) - bearing)) * Float(dot.distance)) } else if (bearing > (3 * .pi / 2.0) && bearing < (2 * .pi)) { //the 4 quadrant of the radar x = radius - CGFloat(cosf(Float(bearing - (3 * .pi / 2))) * Float(dot.distance)) y = radius - CGFloat(sinf(Float(bearing - (3 * .pi / 2))) * Float(dot.distance)) } else if (bearing == 0) { x = radius y = radius - CGFloat(dot.distance) } else if (bearing == .pi / 2) { x = radius + CGFloat(dot.distance) y = radius } else if (bearing == .pi) { x = radius y = radius + CGFloat(dot.distance) } else if (bearing == 3 * .pi / 2) { x = radius - CGFloat(dot.distance) y = radius } else { x = radius y = radius - CGFloat(dot.distance) } let newPosition = CGPoint(x: x, y: y) dot.layer.position = newPosition
后端
仍然需要解决存储点和照片的问题。 为此,决定使用Contentful,在目前的项目实施中,他完全适合我们。


在开发移动应用程序时,所有竞标者都参与了商业项目,并且有条件地允许他们提供几个小时的服务:
- 移动开发人员-便捷的后端
- 内容管理器-方便的管理区域,用于填充数据
后端马拉松的类似实现最初是由参加黑客马拉松的团队使用的(在本文开头提到),这再次证明了黑客马拉松之类的东西使您能够摆脱解决项目中紧迫的问题,使重建和尝试某些东西成为可能。全新的。
结论
开发
AR应用程序非常有趣,在此过程中,我们尝试了几个现成的库,但是我们还必须记住数学并自己编写很多东西。
乍一看,尽管我们使用了Apple的标准SDK,但该项目需要大量的工作时间来实现和完善算法。
我们最近将应用程序发布到
AppStore中 。 这是工作时的样子。
到目前为止,在我们的数据库中,只有Taganrog才有积分,但是每个人都可以参与“覆盖区域”的扩展。