返回
AR模型零件点击显示名称:RealityKit碰撞检测方案
IOS
2024-12-30 23:05:44
模型零件点击显示名称
针对AR应用开发中,点击三维模型的特定零件显示其名称的问题,核心挑战是如何精确地识别用户点击的模型部位,并呈现相应的提示信息。下面将探讨几种可行的方案及实现步骤。
基于碰撞检测和实体名称
最直观的方式是利用RealityKit的碰撞检测功能,并结合模型实体的命名,完成交互。每个子零件在3D建模阶段,都应该被赋予清晰、有意义的名称。这些名称会在导入 RealityKit 时被保留下来。通过捕获用户点击,遍历射线击中的实体,可以获取被点击部分的模型实体。
原理: 当用户点击屏幕时,会从摄像机视角向屏幕点击位置发出一条“射线”。此射线与场景中的模型相交时,就会产生碰撞。 通过遍历所有碰撞实体,找到被点击的对象,再通过检查该对象的 name
属性即可获取相应零件名称。
实现步骤:
- 给模型零件命名: 确保USDZ模型中的每个零件都具有有意义的名称,方便在代码中识别。建模时就应当妥善完成此项操作。
- 捕获屏幕点击事件: 创建一个手势识别器(如
UITapGestureRecognizer
)添加到 ARView 中。 - 射线投射与碰撞检测: 在手势事件回调中,使用
arView.ray(through:)
方法, 从点击位置向场景投射射线,然后利用arView.scene.raycast(from:to:)
获取碰撞结果。 - 解析碰撞实体: 从碰撞结果中提取出
Entity
对象(例如ModelEntity
) , 并读取其name
属性,得到零件名称。 - 显示零件名称: 使用
UILabel
等UI元素在屏幕上显示该名称。可以考虑用一个临时的提示框或者浮窗显示信息。
代码示例:
import ARKit
import RealityKit
import UIKit
class ARViewController: UIViewController {
let arView = ARView(frame: .zero)
var popupLabel: UILabel!
var modelEntity: ModelEntity?
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(arView)
arView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
arView.topAnchor.constraint(equalTo: view.topAnchor),
arView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
arView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
arView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
// 创建模型并放置
setupModel()
// 初始化显示标签
setupPopupLabel()
// 配置手势识别器
setupTapGesture()
// 配置AR场景
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
arView.session.run(configuration)
}
func setupModel() {
//创建模型
modelEntity = try! ModelEntity.loadModel(named: "tsmile1.usdz")
// 设置碰撞形状
modelEntity?.generateCollisionShapes(recursive: true)
let planeAnchor = AnchorEntity(world: SIMD3(0, 0, 0))
planeAnchor.addChild(modelEntity!)
arView.scene.addAnchor(planeAnchor)
}
func setupPopupLabel() {
popupLabel = UILabel()
popupLabel.backgroundColor = .white.withAlphaComponent(0.8)
popupLabel.layer.cornerRadius = 8
popupLabel.layer.masksToBounds = true
popupLabel.textColor = .black
popupLabel.textAlignment = .center
popupLabel.numberOfLines = 0 // allow multi-line text
popupLabel.alpha = 0 // initially invisible
popupLabel.font = .systemFont(ofSize: 16)
popupLabel.translatesAutoresizingMaskIntoConstraints = false
arView.addSubview(popupLabel)
NSLayoutConstraint.activate([
popupLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
popupLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor) , //居中显示
popupLabel.widthAnchor.constraint(lessThanOrEqualToConstant: 250), //限制宽度
popupLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 40) //至少40高度
])
}
func setupTapGesture() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
arView.addGestureRecognizer(tapGesture)
}
@objc func handleTap(_ gesture: UITapGestureRecognizer) {
let tapLocation = gesture.location(in: arView)
guard let raycastQuery = arView.makeRaycastQuery(from: tapLocation, allowing: .estimatedPlane, alignment: .any)
else{
return
}
guard let raycastResult = arView.session.raycast(raycastQuery).first else{
print("未检测到实体碰撞")
dismissPopupLabel() // remove it when click anywhere
return
}
//从碰撞结果中获取Entity
if let entity = raycastResult.entity {
if let modelEntity = entity as? ModelEntity, let name = modelEntity.name{
displayPartName(partName:name)
}
}
}
func displayPartName(partName: String) {
// Adjust text display here.
popupLabel.text = partName
popupLabel.alpha = 1 // set it to visible
// Add animation here if needed
}
func dismissPopupLabel()
{
popupLabel.alpha = 0 //隐藏
}
}
注意: 此方案的成败很大程度上依赖于模型的质量和建模时的规范,若模型未正确命名或者子模型层级过于复杂,实现难度也会增大。
其他注意事项
- 性能优化: 高精细度的模型碰撞检测计算量可能较大,应当考虑优化模型复杂度。对于较远的零件可以设置碰撞检测的距离限制。
- 容错处理: 处理点击落空,以及识别失败的场景,使用户获得恰当的反馈。
- UI呈现: 使用合适的用户界面组件显示零件名称,并确保它们在 AR 环境中清晰可见,并易于交互。
这种方案具有简单直观的特点,但在复杂模型场景下可能会有性能瓶颈,需要进行进一步的性能测试和优化。