在 NativeScript 4.1.0 之前,创建 AR(增强现实)应用程序是可能的,但由于不支持 iOS 向量类型,因此受到了一些限制。我不会详细介绍 AR 以及如何使用 NativeScript 创建增强现实应用程序(关于这个主题已经有一些很棒的文章 - https://www.nativescript.org/blog/getting-started-with-augmented-reality-in-nativescript),而是向您展示如何使用 ARKit 提供的向量类型值。
矩阵是 AR 和所有与计算机图形相关事物的基本组成部分,因为它们是表示图像信息的绝佳方法。虽然向量可以定义为数字列表,但 n 矩阵是有序排列在行和列中的数字集合。每列都由一个向量表示,因此 Nx1 矩阵与具有 N 个元素的向量相同。
例如,如果我们有两个向量 a = (6, 4, 24) 和 b = (1, -9, 8),我们可以将它们放在矩阵的列中
行和列决定矩阵的维数 - 我们刚画出的矩阵是 2x3 矩阵。维数非常重要,因为某些操作只能在具有相同维数的矩阵上进行。它们可以相加、相减、乘以标量或相互之间相乘。但是,这种操作可能需要相当长的时间,这就是 SIMD 的作用所在。
SIMD (https://en.wikipedia.org/wiki/SIMD)(单指令多数据)指令集描述了微处理器中的一种扩展,它允许它们并行操作数据。单核实现并行的方法称为 **向量化**。SIMD 指令基本上做的就是同时对多条数据进行操作。而 **SISD**(单指令单数据)对每个要添加的值使用单独的寄存器(例如加法运算),SIMD 指令会将一个寄存器填充所有值(“打包”寄存器)。下图应该说明了这个例子
让我们用一些 C 代码来阐释这个概念。
//SISD code
float x[3] = {6, 4, 24};
float y[3] = {1, -9, 8};
float z[3];
for (int i = 0; i < 3; i++) {
z[i] = x[i] + y[i];
}
遍历每个数组元素会非常慢,尤其是在处理多个数组时。我们可以用一条指令代替所有这些迭代
//SIMD code
simd_float3 x = simd_make_float3(6, 4, 24);
simd_float3 y = simd_make_float3(1, -9, 8);
simd_float3 z = x + y;
这对多媒体应用非常有用,因为它们需要大量的处理速度才能进行视频处理、语音识别、MP3 等。SIMD 寄存器和操作是 SIMD 编程的低级成分。在这些成分之上构建了更高层的抽象,例如 **simd_float4x4**、**simd_float3x3** 等(**matrix_float** 和 **matrix_double** 类型只是基于 **simd_float** 和 **simd_double** 的 **typedef**)在 iOS 框架中。有关详细的 SIMD 库文档,请参阅 https://developer.apple.com/documentation/accelerate/simd。
现在这些理论就足够了(虽然我们稍后还会有一些)。我将在本文底部列出一些关于此主题的有用资源。
为了撰写本文,我基于 Medium 用户 chriswebb09 的教程构建了一个简单的 ARKit 应用程序 - https://medium.com/journey-of-one-thousand-apps/aarkit-adventures-697dfbe7779e。我将通过添加一个显示我们与演示无人机之间距离的标签来演示矩阵的使用。
**注意**:您需要 安装 NativeScript CLI 并拥有物理 iOS 设备才能运行此演示。
让我们先克隆仓库
git clone -b simd-tutorial https://github.com/tdermendjiev/nativescript-ardrone.git ARDrone
cd ARDrone
现在我们可以在设备上运行应用程序(它无法在模拟器上运行,因为我们需要相机访问权限)
tns run ios
到目前为止一切顺利。让我们添加显示距离的标签。
<Label row="1" col="1" id="distanceLabel" textWrap="true"/>
main-page.xml
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded" class="page" xmlns:DroneSceneView="./DroneSceneView">
<StackLayout>
<iOS>
<DroneSceneView:DroneSceneView height="100%"
arLoaded="arLoaded" id="scene">
<GridLayout columns="auto, *" rows="auto" width="100%" class="slider" verticalAlignment="bottom">
<Slider row="0" col="0" id="heightSlider" width="200" height="100" loaded="onSliderLoaded" value="0.5" minValue="0" maxValue="1"/>
<GridLayout row="0" col="1" columns="50,50,50" rows="50,50,50" width="300" top="20">
<Image row="0" col="1" tap="moveForward" src="~/arrow.png"></Image>
<Image row="1" col="0" tap="moveLeft" id="leftArrow" src="~/arrow.png"></Image>
<Label row="1" col="1" id="distanceLabel" textWrap="true"/>
<Image row="1" col="2" tap="moveRight" id="rightArrow" src="~/arrow.png"></Image>
<Image row="2" col="1" tap="moveBack" id="backArrow" src="~/arrow.png"></Image>
</GridLayout>
</GridLayout>
</DroneSceneView:DroneSceneView>
</iOS>
</StackLayout>
</Page>
接下来,让我们给它一个漂亮的 NativeScript 颜色和样式
app.css
#distanceLabel {
background-color: #3f56f7;
font-size: 15;
color: white;
border-radius: 5;
width: 100;
text-align: center;
}
现在回到向量。我们使用勾股定理来推导出一个公式,用于查找二维空间中两点 A 和 B 之间的距离
AB = ((x2 - x1)2 + (y2 - y1)2)1/2
因此,对于三个维度,我们只需添加第三个(z)轴,公式就变为
AB = ((x2 - x1)2 + (y2 - y1)2 + (z2 - z1)2)1/2
很酷,但我们如何找到两个物体 - 相机和无人机的坐标呢?
对于相机,我们将使用 ARCamera 的 transform
属性
此变换为相机创建了一个局部坐标空间,该空间相对于设备方向是恒定的。在相机空间中,当设备处于 landscapeRight
方向时,x 轴指向右侧 - 也就是说,x 轴始终沿设备的长轴,从前置摄像头指向主页按钮。y 轴指向上方(相对于 landscapeRight
方向),z 轴指向远离设备的屏幕侧。
无人机的坐标由其 position
属性表示。
让我们创建负责距离计算的方法
function calculateDistanceFromCamera(scene, to) {
var camera = scene.sceneView.session.currentFrame.camera;//1
var transform = camera.transform;//2
var dx = to.position.x - transform.columns[3][0];//3
var dy = to.position.y - transform.columns[3][1];
var dz = to.position.z - transform.columns[3][2];
var meters = Math.sqrt(dx * dx + dy * dy + dz * dz);//4
return meters;
}
currentFrame
属性。transform
。transform
属性推导出,如下所示var x = transform.columns[3][0];
var y = transform.columns[3][1];
var z = transform.columns[3][2];
4x4 矩阵的第 4 行是坐标行,其前三个元素分别是 x、y 和 z 坐标。您可以通过阅读有关 **变换矩阵** 的信息来了解更多内容 https://en.wikipedia.org/wiki/Transformation_matrix。
dx、dy 和 dz 是每个坐标之间的距离。
假设每次无人机沿任何方向移动时都会更新距离。在每个操纵杆按钮回调中添加对 updateDistance()
方法的调用
main-page.ios.ts
export function moveForward(args: any) {
let scene = args.object.parent.parent.parent;
let dronePos = scene.helicopterNode.position
scene.moveDrone(dronePos.x, dronePos.y, dronePos.z + 0.5);
updateDistance(scene);
}
export function moveBack(args: any) {
let scene = args.object.parent.parent.parent;
let dronePos = scene.helicopterNode.position
scene.moveDrone(dronePos.x, dronePos.y, dronePos.z - 0.5);
updateDistance(scene);
}
export function moveLeft(args: any) {
let scene = args.object.parent.parent.parent;
let dronePos = scene.helicopterNode.position
scene.moveDrone(dronePos.x - 0.5, dronePos.y, dronePos.z);
updateDistance(scene);
}
export function moveRight(args: any) {
let scene = args.object.parent.parent.parent;
let dronePos = scene.helicopterNode.position
scene.moveDrone(dronePos.x + 0.5, dronePos.y, dronePos.z);
updateDistance(scene);
}
现在实现 updateDistance()
方法
function updateDistance(scene: any) {
let distanceLabel = scene.parent.getViewById("distanceLabel");
var distance = calculateDistanceFromCamera(scene, scene.helicopterNode);
if (distance && distanceLabel) {
distanceLabel.text = distance.toFixed(2);
}
}
如果我们再次运行应用程序,每次我们移动无人机时,标签都会更新。
计算 3D 空间中物体之间的距离只是使用矩阵数据所能做的一切的皮毛。好消息是现在可以使用 NativeScript 完成,所以去开发一些 AR 吧!
参考资料和有用资源