macOS RealityKit应用hitTest结果不准且raycast不可用问题求助
我用RealityKit和SwiftUI开发macOS平台的非AR 3D应用,要通过onContinuousHover的结果修改物体材质,实现鼠标悬停的盒子面高亮,但目前没法精准获取模型位置。
为排查问题,我创建了一个平面并放在世界原点的AnchorEntity上,代码如下:
var mesh: MeshResource = MeshResource.generatePlane(width: 10.0, depth: 10.0) // material is a grid, to help visualize where hits are taking place model = ModelComponent(mesh: mesh, materials: [material]) generateCollisionShapes(recursive: true) collision = CollisionComponent( shapes: [ShapeResource.generateConvex(from: model!.mesh)], mode: .default )
调用hitTest(返回[CollisionCastHit]而非弃用的[ARHitTestResult])得到的结果存在两个问题:
- Y坐标偏移:世界原点中心的X-Z平面上,命中点y坐标始终为0.1,实际应该是0.0,固定偏差10厘米;偏移平面后,y值仍保持0.1的偏差。
- Z坐标符号反转:悬停在世界坐标(0.0, 0.0, 0.1)的小球时,返回结果是(0.0, 0.0, -0.1);悬停在(0.0, 0.0, -0.1)的小球时,返回(0.0, 0.0, 0.1),平面所有点都有这个问题。
另外,尝试使用文档标注macOS 10.15+可用的arview.raycast(from: allowing: alignment:)时,报错“Value of type 'ARView' has no member 'raycast'”,RealityKit头文件里也搜不到raycast相关方法。
一、修复hitTest的坐标偏差问题
1. 统一碰撞形状生成逻辑
你同时调用了generateCollisionShapes(recursive: true)和手动创建CollisionComponent,两者可能产生冲突。建议二选一:
- 若依赖自动生成:删除手动创建的
CollisionComponent,仅保留generateCollisionShapes(recursive: true); - 若手动创建:替换凸包生成方式,用平面专属的碰撞形状避免计算误差:
collision = CollisionComponent( shapes: [ShapeResource.generatePlane(width: 10.0, depth: 10.0)], mode: .default )
2. 修正坐标系转换
macOS RealityKit的Z轴方向与iOS存在差异,可通过坐标转换验证并修正:
if let hitResult = arView.hitTest(mouseLocation).first { // 将命中点从世界空间转成模型本地空间,再转回世界空间验证 let localPos = hitResult.entity.convert(hitResult.position, from: .world, to: hitResult.entity) let correctedWorldPos = hitResult.entity.convert(localPos, from: hitResult.entity, to: .world) print("修正后坐标:\(correctedWorldPos)") }
同时打印anchorEntity.transform,确认锚点的变换确实是恒等矩阵,排除父节点意外偏移的可能。
3. 排查Y轴偏移根源
- 检查平面的
ModelComponent是否被设置了offset属性,若有则重置为SIMD3(0,0,0); - 打印ARView相机的初始位置:
print(arView.cameraTransform.translation),若相机Y轴位置为0.1,会导致射线与平面的交点产生偏移,可调整相机位置抵消该偏差。
二、解决raycast方法不存在的问题
arview.raycast(from: allowing: alignment:)是ARKit专属方法,仅适用于iOS的AR场景,macOS非AR应用中不存在。替代方案是使用macOS RealityKit支持的射线检测API:
// 方式1:直接用鼠标位置做hitTest let mouseLocation = NSPoint(x: ..., y: ...) // 从onContinuousHover获取的坐标 let hitResults = arView.hitTest(mouseLocation) // 方式2:手动创建射线进行检测 let rayOrigin = arView.cameraTransform.translation let rayDirection = arView.unproject(CGPoint(x: mouseLocation.x, y: mouseLocation.y), ontoPlaneAt: SIMD3<Float>(0,0,0), orientation: .init()) let hitResults = arView.castRay(from: rayOrigin, to: rayOrigin + rayDirection * 1000)
三、高亮盒子面的简化方案
如果精准获取命中面有困难,可采用更直接的实现方式:
- 给盒子的每个面单独创建子Entity,每个子Entity配备独立的碰撞形状和材质;
- 这样
hitTest能直接命中对应面的子Entity,无需计算位置,直接修改该子Entity的材质即可。
内容的提问来源于stack exchange,提问作者dang

