在3D空间中验证一个点是否在圆锥内部

7
考虑以下内容:
  • X(x1,y1,z1)是需要验证是否在锥体内的点。
  • M(x2,y2,z2)是锥体的顶点。(锥体的顶部点)
  • N(x3,y3,z3)是锥体底部中心点。

我发现如果点X在锥体上,则需要验证以下方程:

cos(alfa) * ||X-M|| * ||N|| = dot(X-M,N)

其中,dot是两个向量的数量积,alfa是这两个向量之间的夹角。

根据公式,我计算出:

X-M = (x1-x2,y1-y2,z1-z2)

所以,
cos(alfa)
  * Math.sqrt((x1-x2)^2+(y1-y2)^2+(z1-z2)^2)
  * Math.sqrt(x3^2 + y3^2+z3^2)
= x3(x1-x2) + y3(y1-y2) + z3(z1-z2)

不幸的是以上计算似乎给我错误的结果。我做错了什么?

此外,我怀疑为了检查X是否在圆锥体内,我必须在公式中使用<=而不是=。这是正确的吗?

它的应用是:我开发了一个游戏,当物体出现在机枪的“视野”中时,机枪必须开始射击。这个视野将是一个圆锥体。圆锥体的顶点将位于机枪上,圆锥体的底部将在某个已知距离前面。任何进入该圆锥体的物体,机枪都会射击。

2个回答

11

我完全同意Tim的观点:我们需要圆锥的“角度”(孔径)才能得出答案。

那么让我们开始编写代码吧!我将使用这里的一些术语

结果输出函数:

/**
 * @param x coordinates of point to be tested 
 * @param t coordinates of apex point of cone
 * @param b coordinates of center of basement circle
 * @param aperture in radians
 */
static public boolean isLyingInCone(float[] x, float[] t, float[] b, 
                                    float aperture){

    // This is for our convenience
    float halfAperture = aperture/2.f;

    // Vector pointing to X point from apex
    float[] apexToXVect = dif(t,x);

    // Vector pointing from apex to circle-center point.
    float[] axisVect = dif(t,b);

    // X is lying in cone only if it's lying in 
    // infinite version of its cone -- that is, 
    // not limited by "round basement".
    // We'll use dotProd() to 
    // determine angle between apexToXVect and axis.
    boolean isInInfiniteCone = dotProd(apexToXVect,axisVect)
                               /magn(apexToXVect)/magn(axisVect)
                                 >
                               // We can safely compare cos() of angles 
                               // between vectors instead of bare angles.
                               Math.cos(halfAperture);


    if(!isInInfiniteCone) return false;

    // X is contained in cone only if projection of apexToXVect to axis
    // is shorter than axis. 
    // We'll use dotProd() to figure projection length.
    boolean isUnderRoundCap = dotProd(apexToXVect,axisVect)
                              /magn(axisVect)
                                <
                              magn(axisVect);
    return isUnderRoundCap;
}

以下是我对基本函数的快速实现,这些函数由上层代码来操作向量。
static public float dotProd(float[] a, float[] b){
    return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];
}

static public float[] dif(float[] a, float[] b){
    return (new float[]{
            a[0]-b[0],
            a[1]-b[1],
            a[2]-b[2]
    });
}

static public float magn(float[] a){
    return (float) (Math.sqrt(a[0]*a[0]+a[1]*a[1]+a[2]*a[2]));
}

玩得开心!


这是一个很棒的答案,更接近于我期望的生产解决方案的样子! - Tim
谢谢furikuretsu!非常好用。 - Mihai si atat

1

您需要检查差向量(X-M)和中心向量(N)之间的夹角是否小于或等于锥体的角度(问题中未指定)。这将告诉您位置向量(X)是否在无限圆锥内,然后您还可以检查距离(如果需要)。因此,

float theta = PI/6; //half angle of cone
if (acos(dot(X-M, N)/(norm(X-M)*norm(N)) <= theta) doSomething();

为了提高性能,您还可以将N进行归一化(将其转换为长度为1的向量)并单独存储长度。然后,您可以将norm(X-M)与长度进行比较,从而得到一个圆锥体底部(我确定有一个名称存在,但我不知道它是什么)。

编辑:忘记了反余弦,点积等于norm(U)* norm(V)* cos(角度),因此我们必须反转该操作以比较角度。在这种情况下,acos应该很好,因为我们希望正角度和负角度平等地进行比较,但要注意。

编辑:弧度。


30弧度似乎对于圆锥的半角来说有点大。 - Andrew Morton
@AndrewMorton 谢谢!今天我戴上了我的思考帽。 - Tim
你的公式是否考虑了一个由两部分构成的圆锥体?想必枪不会同时向后开火;) - Andrew Morton
@AndrewMorton 我不得不编写一个测试脚本来确保它可以正确处理锥体相反方向的位置。我对三角函数不是很擅长,但我相信acos在需要区分第一和第四象限或第二和第三象限时是有歧义的,而在这种情况下我们不需要区分。 - Tim
如果您使用 cos( PI/6 ) 并缓存该值,就不需要调用 acos 函数,这样可以节省一些反三角函数的计算。 - bobobobo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接