3D 布局小练习:如何在 AR 中给地球贴瓷砖?

1,002

说明

本文完成一个球形布局,借此说明 Xcode 中 3D 编辑器用法,及坐标转换 API 的使用。最终效果如图,将 48 个方形平面贴在球体表面:

3D 编辑器

首先,创建一个球体,半径为 2 米,供我们参考。(其实不创建也没关系)

然后添加第一块平面,修改它的大小和位置,将它放置在赤道位置,与 z 轴交点 2 米处,这样就完成了第一块平面的布局。

接下来的倾斜平面的放置有两种方式:模型坐标拖动平移借助子结点坐标转换

方案一:模型坐标拖动平移

思路是:

  • 先计算好下一块要放置的角度,设置好角度;
  • 然后直接拖动模型坐标轴,将其平移到与球面相切的位置。

水平角度,可以根据一圈要放置的数量来计算,这里我们选择 16 个,即 360 / 16 = 22.5,即每个小平面相差 22.5 度。竖直角度也是类似,这里演示第一块暂定为 0,即也放在赤道上。

非赤道位置,可以调整 x 轴欧拉角,如图:

这种方式简单快捷,但缺点也如图所示,手动拖动难以控制位置,想要精准放置在赤道处非常困难。除非提前计算好该角度下,(x,y,z)坐标的位置:当角度确定时,(x,y,z) 相互之间的比例是确定的,再配合三维距离 x*x + y*y + z*z = 2*2,可以精确求解出。

但这样又太过于复杂了,所以在 3D 编辑器中,还可以用第二种方案,借助子结点进行转换。

方案二:借助子结点坐标转换

思路是:

  • 先计算好下一块要放置的角度,设置好角度;
  • 然后借助子结点,调整其位置到与球面相交的位置上(沿平面坐标系 z 轴平移 2 米);
  • 记录子结点的 WorldPosition ,即子结点相对整个场景根结点坐标系的位置,也是子结点相对于平面坐标系的位置;
  • 将平面的 LocalPositon 移动到这个位置上(子结点原来的位置)。

这个方案,可以实现将平面移动到与球体相切的位置,缺点是借助了子结点(图中黑色立方体),有多余步骤。

代码编写

理解了上面的两种思路,我们完成可以通过代码调用 API 的方式,来避免 3D 编辑器中的弊端。

方案一:模型坐标API平移

思路是:

  • 先计算好下一块要放置的角度,设置好角度;
  • 然后 API 移动模型坐标轴,将平面沿自己 z 轴,平移到与球面相切的位置。 核心代码:
for (int i = 0; i < 48; i++) {
// 计算行号和列号,类似 9 宫格布局
    CGFloat size = 0.7;//默认尺寸
    NSUInteger num = i % 16;//第几个
    NSUInteger line = i / 16;//第几行
    if (line == 0 || line == 2) {//最上面一行,最下面一行缩小一些
        size = 0.6;
    }

// 创建平面
    SCNPlane *plane = [SCNPlane planeWithWidth: size height:size];
    plane.firstMaterial.doubleSided = YES;
    plane.firstMaterial.diffuse.contents = [UIColor colorWithWhite:1 alpha:0.9];
    plane.cornerRadius = 0.08;
    SCNNode *planeNode = [SCNNode nodeWithGeometry:plane];
    [self addChildNode:planeNode];
    
// 计算角度
    simd_float3 angles = simd_make_float3((line * 22.0 - 22.0)/180.0*M_PI, 22.5 * num /180.0*M_PI, 0);
    planeNode.simdEulerAngles = angles;

// 将planeNode沿自己坐标系的(0,0, 2)方向平移 2 米
    [planeNode simdLocalTranslateBy:simd_make_float3(0,0, 2)];
}

方案二:借助API坐标转换

  • 先计算好下一块要放置的角度,设置好角度;
  • 然后借助API,算出 z 轴 2 米的位置坐标(平面的 z 轴 2 米处在世界坐标系的坐标);
  • 将平面移动到算出来的位置上;

核心代码:

for (int i = 0; i < 48; i++) {
// 计算行号和列号,类似 9 宫格布局
    CGFloat size = 0.7;//默认尺寸
    NSUInteger num = i % 16;//第几个
    NSUInteger line = i / 16;//第几行
    if (line == 0 || line == 2) {//最上面一行,最下面一行缩小一些
         size = 0.6;
    }
// 创建平面
    SCNPlane *plane = [SCNPlane planeWithWidth: size height:size];
    plane.firstMaterial.doubleSided = YES;
    plane.firstMaterial.diffuse.contents = [UIColor colorWithWhite:1 alpha:0.9];
    plane.cornerRadius = 0.08;
    SCNNode *planeNode = [SCNNode nodeWithGeometry:plane];
    [self addChildNode:planeNode];
    
// 计算角度
    simd_float3 angles = simd_make_float3((line * 22.0 - 22.0)/180.0*M_PI, 22.5 * num /180.0*M_PI, 0);
    planeNode.simdEulerAngles = angles;
// 计算位置,planeNode坐标下(0,0, 2)点在平面父结点坐标中的位置
    simd_float3 position = [planeNode simdConvertPosition:simd_make_float3(0,0, 2) toNode: planeNode.parentNode];
    planeNode.simdPosition = position;

}

代码最终效果图: