版权声明:Davidwang原创文章,严禁用于任何商业途径,授权后方可转载。
智能移动设备的手势操作是使用者接受并已习惯的操作方式,在移动端AR应用中,对虚拟物体的操作也基本通过手势操作完成,本节我们将学习一些手势检测基础知识。需要注意的是本节中描述的手势检测是指用户在手机屏幕上的手指操作检测,不是指利用图像技术对使用者手部运动的检测。
(一)手势检测定义
手势检测是指通过检测使用者在手机屏幕上的手指触控运动来判断其操作意图的技术,如单击、双击、缩放、滑动等等,常见的手势操作如下图所示。
(二)ARKit中的手势检测
ARKit提供了对触控设备底层API的访问权限和高级手势检测功能,可以满足不同的手势定制需求。底层API访问能够获取手指点击的原始位置、压力值、速度信息,高级手势检测功能则借助手势识别器(Gesture Recognizer)来识别预设手势(包括单击、双击、长按、滑动、缩放、平移等)。
在AR应用中,对虚拟物体最常见的3种操控方式分别为平移、缩放、旋转,为简化手势使用难度,RealityKit使用installGestures()方法对单物体操控提供快捷支持,该方法原型为:
@discardableResult func installGestures(_ gestures: ARView.EntityGestures = .all, for entity: HasCollision) -> [EntityGestureRecognizer]
其中参数gestures为ARView.EntityGestures枚举类型,用于指定可执行的手势操作,entity指定需要使用手势操作的实体(Entity)。ARView.EntityGestures枚举包含all、rotation、scale、translation4个枚举值,涵盖了最常见的旋转、缩放、平移操作。使用installGestures()方法为entity添加手势操作时,entity需要遵循HasCollision协议,简单讲就是虚拟物体必须要有碰撞器(CollisionShapes),因为本质上手势操作也首先要用射线检测进行碰撞检查,不带碰撞器的虚拟元素无法参与碰撞检测。在如下代码清单中,我们创建了一个正方体,然后通过程序的方式生成碰撞器,再调用installGestures()方法,允许用户对该立方体进行操控。
func createPlane(){
let planeAnchor = AnchorEntity(plane:.horizontal)
do {
let cubeMesh = MeshResource.generateBox(size: 0.1)
var cubeMaterial = SimpleMaterial(color:.white,isMetallic: false)
cubeMaterial.baseColor = try .texture(.load(named: "Box_Texture.jpg"))
let cubeEntity = ModelEntity(mesh:cubeMesh,materials:[cubeMaterial])
cubeEntity.generateCollisionShapes(recursive: false)
planeAnchor.addChild(cubeEntity)
self.scene.addAnchor(planeAnchor)
self.installGestures(.all,for:cubeEntity)
} catch {
print("找不到文件")
}
}
RealityKit还定义了EntityRotationGestureRecognizer、EntityScaleGestureRecognizer、EntityTranslationGestureRecognizer 3个专门用于实体操作的手势识别器,利用这3个手势识别器可以非常方便的对实体进行旋转、缩放、平移操作,如下代码清单所示。
var cubeEntity : ModelEntity?
var gestureStartLocation: SIMD3<Float>?
extension ARView :ARSessionDelegate{
func createPlane(){
let planeAnchor = AnchorEntity(plane:.horizontal)
do {
let cubeMesh = MeshResource.generateBox(size: 0.1)
var cubeMaterial = SimpleMaterial(color:.white,isMetallic: false)
cubeMaterial.baseColor = try .texture(.load(named: "Box_Texture.jpg"))
cubeEntity = ModelEntity(mesh:cubeMesh,materials:[cubeMaterial])
cubeEntity!.generateCollisionShapes(recursive: false)
cubeEntity?.name = "this is a cube"
planeAnchor.addChild(cubeEntity!)
self.scene.addAnchor(planeAnchor)
self.installGestures(.all,for:cubeEntity!).forEach{
$0.addTarget(self, action: #selector(handleModelGesture))
}
} catch {
print("找不到文件")
}
}
@objc func handleModelGesture(_ sender: Any) {
switch sender {
case let rotation as EntityRotationGestureRecognizer:
print("Rotation and name :\(rotation.entity!.name)")
rotation.isEnabled = false
case let translation as EntityTranslationGestureRecognizer:
print("translation and name \(translation.entity!.name)")
if translation.state == .ended || translation.state == .cancelled {
gestureStartLocation = nil
return
}
guard let gestureCurrentLocation = translation.entity?.transform.translation else { return }
guard let _ = gestureStartLocation else {
gestureStartLocation = gestureCurrentLocation
return
}
let delta = gestureStartLocation! - gestureCurrentLocation
let distance = ((delta.x * delta.x) + (delta.y * delta.y) + (delta.z * delta.z)).squareRoot()
print("startLocation:\(String(describing: gestureStartLocation)),currentLocation:\(gestureCurrentLocation),the distance is \(distance)")
case let Scale as EntityScaleGestureRecognizer:
Scale.removeTarget(nil, action: nil)
Scale.addTarget(self, action: #selector(handleScaleGesture))
default:
break
}
}
@objc func handleScaleGesture(_ sender : EntityScaleGestureRecognizer){
print("in scale")
}
}
在上面代码清单中,我们演示了如何取消原手势的执行,如何获取实体对象真实移动距离,以及如何将手势处理转发到另一个处理方法中。
除了RealityKit自定义的EntityGestureRecognizer,所有的UIGestureRecognizer也都可以使用,包括UITapGestureRecognizer (轻点手势识别器)、UIPinchGestureRecognizer( 捏合手势识别器)、UIRotationGestureRecognizer( 旋转手势识别 器)、UISwipeGestureRecognizer (滑动手势识别器)、UIPanGestureRecognizer (拖动手势识别器)、UIScreenEdgePanGestureRecognizer (屏幕边缘拖动手势识别器)、UILongPressGestureRecognizer (长按手势识别器)等等。但因为这类手势并非专为实体操作而设计,因此开发者需要自行处理手势操作对象行为。
在RealityKit中,手势识别器一般都会提供手势操作不同状态的方法或者事件,方便开发者调用,具体如下表所示。
功能 | 描述 |
---|---|
touchesBegan(Set, with: UIEvent) | 当用户用一个或几个手指触控屏幕时 |
touchesMoved(Set, with: UIEvent) | 当用户用一个或几个手指在屏幕上滑动时 |
touchesCancelled(Set, with: UIEvent) | 当用户在进行操作时,一个系统事件的发生中断了用户的操作(如突然来电话)时 |
touchesEnded(Set, with: UIEvent) | 当用户一个或多个手指从屏幕上抬起时 |