使用 Swift 在 iPhone 上进行 CoreMotion 控制的 3D 草图绘制

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

我最近在 Creative Applications 中读到的 InkScape 演示给我留下了深刻的印象。 InkScape 是一款 Android 应用程序,它允许用户在由设备的加速度计控制的 3D 空间中绘制草图。它受到 Rhonda 的启发,Rhonda 先于加速度计并使用轨迹球代替。


当然,我的第一个想法是,“ 我怎样才能在 Swift 中做到这一点? ”。我以前从未使用 CoreMotion 做过任何工作,所以这是学习一些新东西的好机会。我的第一个停靠点是 NSHipster 上关于 iOS 动作的优秀文章


我对该应用程序的计划是让一个 SceneKit 场景和一个运动控制的相机围绕 SceneKit 世界中心的偏移轴心点旋转。对于每个 touchesBegan() ,我会在屏幕中央创建一个与相机对齐的新平面框,在 touchesMoved() 上,我会使用触摸位置附加到我绘制到我将用作新创建的几何体的漫反射材料的 CAShapeLayer


简单的!让我们分解一下:

创建相机

我希望相机始终指向世界中心并围绕世界中心旋转,同时稍微偏离它。有助于此的两件事是相机的 枢轴 属性和使用“查看约束”。首先,我创建一个节点来代表世界的中心和相机本身:



 let centreNode = SCNNode()
centreNode.position = SCNVector3(x: 0, y: 0, z: 0)

scene.rootNode.addChildNode(centreNode)





let camera = SCNCamera()

camera.xFov = 20



camera.yFov = 20



接下来, SCNLookAtConstraint 意味着无论我如何平移相机,它总是指向中心:


 let centreNode = SCNNode()
centreNode.position = SCNVector3(x: 0, y: 0, z: 0)

scene.rootNode.addChildNode(centreNode)





let camera = SCNCamera()

camera.xFov = 20



camera.yFov = 20


...最后,设置相机的 枢轴 将重新定位它,但让它围绕世界中心旋转:


 let centreNode = SCNNode()
centreNode.position = SCNVector3(x: 0, y: 0, z: 0)

scene.rootNode.addChildNode(centreNode)





let camera = SCNCamera()

camera.xFov = 20



camera.yFov = 20


处理 iPhone 运动

接下来是处理 iPhone 的运动以旋转相机。记住 iPhone 的 滚动 是它沿前后轴的旋转,它的 俯仰 是它沿左右轴的旋转:



...我将使用这些属性来控制相机的 x y 欧拉角。


第一步是创建 CMMotionManager 的实例并确保它可用并正常工作(因此此代码不会在模拟器上运行):




 let centreNode = SCNNode()
centreNode.position = SCNVector3(x: 0, y: 0, z: 0)

scene.rootNode.addChildNode(centreNode)





let camera = SCNCamera()

camera.xFov = 20



camera.yFov = 20



接下来,我使用每次更新都会调用的一小段代码启动运动管理器。我使用一个元组来存储 iPhone 的初始姿态,并简单地使用初始值和当前姿态之间的差异来设置相机的欧拉角:



 let centreNode = SCNNode()
centreNode.position = SCNVector3(x: 0, y: 0, z: 0)

scene.rootNode.addChildNode(centreNode)





let camera = SCNCamera()

camera.xFov = 20



camera.yFov = 20


3D绘图

因为我知道我的相机的角度,所以在 touchesBegan() 方法上对齐目标几何图形以进行绘制非常简单 - 它只是共享相同的态度:


 let centreNode = SCNNode()
centreNode.position = SCNVector3(x: 0, y: 0, z: 0)

scene.rootNode.addChildNode(centreNode)





let camera = SCNCamera()

camera.xFov = 20



camera.yFov = 20


同时,我创建了一个新的 CAShapeLayer ,它将包含一个跟随用户手指的描边路径:


 let centreNode = SCNNode()
centreNode.position = SCNVector3(x: 0, y: 0, z: 0)

scene.rootNode.addChildNode(centreNode)





let camera = SCNCamera()

camera.xFov = 20



camera.yFov = 20



touchesMoved() 上,我需要将主视图中的位置转换为几何体上的位置。由于此几何体的大小为 1 x 1(从两个方向的 -0.5 到 0.5),我需要将其转换为我的 CAShapeLayer 中的坐标(任意设置为 512 x 512)以在其路径中添加点。


有几个步骤可以做到这一点,获取触摸集中第一个项目的 locationInView() ,我将它传递到我的 SceneKit 场景中的 hitTest() 。这将为触摸下的所有几何体返回一个 SCNHitTestResults 数组,我为当前几何体过滤,然后简单地重新缩放结果的 localCoordinates 以找到当前 CAShapeLayer 上的坐标:



 let centreNode = SCNNode()
centreNode.position = SCNVector3(x: 0, y: 0, z: 0)

scene.rootNode.addChildNode(centreNode)





let camera = SCNCamera()

camera.xFov = 20



camera.yFov = 20


...就是这样!


该项目的源代码可 在我的 GitHub 存储库中获取。 它是在 Xcode 7 beta 5 下开发的,并在我运行 iOS 8.4.1 的 iPhone 6 上进行了测试。