You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Google Geospatial示例中,如何检测HitTest是否命中指定Mesh对象?

解决Google Geospatial中自定义Mesh的HitTest命中检测问题

问题根源

你当前的代码逻辑存在两个核心问题:

  1. ARCore的frame.hitTest()仅检测现实世界的Trackable对象(比如平面、特征点),你自己创建的自定义3D Mesh不属于ARCore的Trackable范畴,所以不会出现在hitResultList中。
  2. 直接用hit.equals(virtualObjectMesh)对比完全无效——HitResult是ARCore返回的现实空间检测结果,和你的Mesh对象不是同一类型,不可能相等。

正确解决方案

要检测触摸是否命中自定义Mesh,需要通过屏幕射线与Mesh空间范围的相交检测来实现,步骤如下:

1. 提前记录Mesh的关键信息

创建Mesh时,保存关联的EarthAnchor(用于获取世界坐标),以及Mesh的包围盒(用于快速碰撞检测):

// 自定义数据类存储Mesh相关信息
data class TrackedVirtualMesh(
    val earthAnchor: EarthAnchor,
    val mesh: Mesh,
    // 预计算Mesh的局部包围盒(AABB)
    val localBoundingBox: BoundingBox
)

// 创建Mesh时初始化
val virtualMesh = TrackedVirtualMesh(
    earthAnchor = yourEarthAnchor,
    mesh = yourVirtualMesh,
    localBoundingBox = calculateMeshBoundingBox(yourVirtualMesh)
)

2. 触摸事件中执行射线相交检测

将屏幕触摸坐标转换为世界空间射线,再判断射线是否与Mesh的世界空间包围盒(或三角面)相交:

if (motionEvent != null) {
    // 从触摸位置创建世界空间射线
    val ray = frame.createRay(motionEvent.x, motionEvent.y)
    
    // 遍历所有自定义Mesh
    for (trackedMesh in yourVirtualMeshList) {
        // 获取Anchor的世界姿态,将Mesh局部包围盒转换到世界空间
        val anchorPose = trackedMesh.earthAnchor.pose
        val worldBoundingBox = trackedMesh.localBoundingBox.transform(anchorPose)
        
        // 先做快速包围盒检测
        if (ray.intersects(worldBoundingBox)) {
            // 可选:执行高精度三角面相交检测(需要遍历Mesh的三角面计算)
            if (rayIntersectsMesh(ray, trackedMesh.mesh, anchorPose)) {
                Log.d(TAG, "命中自定义Mesh!")
                break
            }
        }
    }
    motionEvent = null
}

3. 辅助函数实现

  • 计算Mesh局部包围盒:
private fun calculateMeshBoundingBox(mesh: Mesh): BoundingBox {
    var minX = Float.MAX_VALUE
    var minY = Float.MAX_VALUE
    var minZ = Float.MAX_VALUE
    var maxX = Float.MIN_VALUE
    var maxY = Float.MIN_VALUE
    var maxZ = Float.MIN_VALUE
    
    // 遍历Mesh所有顶点,计算最小/最大坐标
    val vertices = mesh.vertices
    for (i in 0 until vertices.size step 3) {
        val x = vertices[i]
        val y = vertices[i+1]
        val z = vertices[i+2]
        minX = min(minX, x)
        minY = min(minY, y)
        minZ = min(minZ, z)
        maxX = max(maxX, x)
        maxY = max(maxY, y)
        maxZ = max(maxZ, z)
    }
    return BoundingBox(minX, minY, minZ, maxX, maxY, maxZ)
}
  • 射线与三角面相交检测(可选,用于高精度场景):
private fun rayIntersectsMesh(ray: Ray, mesh: Mesh, pose: Pose): Boolean {
    val vertices = mesh.vertices
    val indices = mesh.indices
    // 遍历所有三角面
    for (i in 0 until indices.size step 3) {
        val v0Idx = indices[i] * 3
        val v1Idx = indices[i+1] * 3
        val v2Idx = indices[i+2] * 3
        
        // 获取局部顶点坐标,转换到世界空间
        val v0 = pose.transformPoint(vertices[v0Idx], vertices[v0Idx+1], vertices[v0Idx+2])
        val v1 = pose.transformPoint(vertices[v1Idx], vertices[v1Idx+1], vertices[v1Idx+2])
        val v2 = pose.transformPoint(vertices[v2Idx], vertices[v2Idx+1], vertices[v2Idx+2])
        
        // 射线与三角面相交检测(使用Möller-Trumbore算法)
        if (rayIntersectsTriangle(ray, v0, v1, v2)) {
            return true
        }
    }
    return false
}

// Möller-Trumbore射线三角相交算法实现
private fun rayIntersectsTriangle(ray: Ray, v0: FloatArray, v1: FloatArray, v2: FloatArray): Boolean {
    // 标准算法实现:计算射线与三角面的交点参数,判断是否在三角面范围内
    val edge1 = floatArrayOf(v1[0]-v0[0], v1[1]-v0[1], v1[2]-v0[2])
    val edge2 = floatArrayOf(v2[0]-v0[0], v2[1]-v0[1], v2[2]-v0[2])
    val h = crossProduct(ray.direction, edge2)
    val a = dotProduct(edge1, h)
    if (a > -0.00001 && a < 0.00001) return false
    val f = 1/a
    val s = floatArrayOf(ray.origin[0]-v0[0], ray.origin[1]-v0[1], ray.origin[2]-v0[2])
    val u = f * dotProduct(s, h)
    if (u < 0 || u > 1) return false
    val q = crossProduct(s, edge1)
    val v = f * dotProduct(ray.direction, q)
    if (v < 0 || u + v > 1) return false
    val t = f * dotProduct(edge2, q)
    return t > 0.00001
}

// 辅助:计算向量叉乘
private fun crossProduct(a: FloatArray, b: FloatArray): FloatArray {
    return floatArrayOf(
        a[1]*b[2] - a[2]*b[1],
        a[2]*b[0] - a[0]*b[2],
        a[0]*b[1] - a[1]*b[0]
    )
}

// 辅助:计算向量点乘
private fun dotProduct(a: FloatArray, b: FloatArray): Float {
    return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
}

补充说明

如果使用Filament(Google推荐的AR渲染引擎),可以直接使用Filament的Raycaster类来简化Mesh碰撞检测,无需手动实现三角面相交逻辑,效率和准确性更高。

内容的提问来源于stack exchange,提问作者Luca Rocchi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 11:43:19