功能实现的数学模型选择

  在实现很多数学相关的功能的时候, 数学模型的选择尤为重要, 一个是在准确性上, 一个是在扩展性上.

比如最近我要计算一个射线跟一个线段的交点, 初中的时候就学过, 直线和直线外一点的交点怎样计算, 这里

直接上已经写完的两段代码.

简图

  因为是计算机, 肯定是通过两点来确定直线方程了, 首先是用点斜式的计算方法( 原始方程 y = kx + b, 推导过程略 ):

    /// <summary>
    /// 点斜式推理出的交点方程
    /// </summary>
    /// <param name="ray"></param>
    /// <param name="p1"></param>
    /// <param name="p2"></param>
    /// <param name="point"></param>
    /// <returns></returns>
    public static bool IntersectRayAndLineSegment_XOZ_PointSlope(Ray ray, Vector3 p1, Vector3 p2, out Vector3 point)
    {
        float k = (p2.z - p1.z) / (p2.x - p1.x);
        float x = (p2.z - p1.z + k * p1.x + p2.x / k) / (k + 1.0f / k);
        float z = p1.z + k * (x - p1.x);

        point = new Vector3(x, 0, z);

        bool intersect = Vector3.Dot(new Vector3(x, 0, z) - ray.origin, ray.direction) > 0;
        if(intersect)
        {
            intersect = (x >= Mathf.Min(p1.x, p2.x) && x <= Mathf.Max(p1.x, p2.x)) && (z >= Mathf.Min(p1.z, p2.z) && z <= Mathf.Max(p1.z, p2.z));
        }
        return intersect;
    }

  然后是两点式的计算方法( 原始方程 (y-y1)/(x-x1) = (y-y2)/(x-x2), 推导过程略 ):

    /// <summary>
    /// 两点式推出的交点方程
    /// </summary>
    /// <param name="ray"></param>
    /// <param name="p1"></param>
    /// <param name="p2"></param>
    /// <param name="point"></param>
    /// <returns></returns>
    public static bool IntersectRayAndLineSegment_XOZ_TwoPoint(Ray ray, Vector3 p1, Vector3 p2, out Vector3 point)
    {
        point = Vector3.zero;
        Vector3 p3 = ray.origin;
        Vector3 p4 = ray.GetPoint(1.0f);

        float a1, b1, c1;
        PointToLineEquation_2D_XOZ(p1, p2, out a1, out b1, out c1);
        float a2, b2, c2;
        PointToLineEquation_2D_XOZ(p3, p4, out a2, out b2, out c2);

        float D = a1 * b2 - a2 * b1;
        if(D == 0)
        {
            return false;
        }
        float D1 = -c1 * b2 + c2 * b1;
        float D2 = -c1 * a1 + a2 * c1;
        point = new Vector3(D1 / D, 0, D2 / D);
        var intersect = (point.x >= Mathf.Min(p1.x, p2.x) && point.x <= Mathf.Max(p1.x, p2.x)) && (point.z >= Mathf.Min(p1.z, p2.z) && point.z <= Mathf.Max(p1.z, p2.z));
        return intersect;
    }
    public static void PointToLineEquation_2D_XOZ(Vector3 p1, Vector3 p2, out float a, out float b, out float c)
    {
        float x1 = p1.x;
        float y1 = p1.z;

        float x2 = p2.x;
        float y2 = p2.z;

        a = y2 - y1;
        b = x1 - x2;
        c = (y1 * x2) - (y2 * x1);
    }

  在大部分情况下它们的结果是正确的, 可是在线段是平行于坐标轴的情况下, 点斜式的结果就不对了, 往回看计算过程:

        float k = (p2.z - p1.z) / (p2.x - p1.x);
        float x = (p2.y - p1.y + k * p1.x + p2.x / k) / (k + 1.0f / k);

  点斜式在刚开始就计算了斜率k, 然后k被作为了分母参与计算, 这样在平行于x轴的时候斜率就是0, 被除之后得到无穷大(或无穷小), 在平行y轴的时候( 两点的x相等 ), k直接就是无穷大(或无穷小).

所以点斜式在计算平行于坐标轴的情况就不行了. 点斜式的问题就在于过早进行了除法计算, 而且分母还可能为0, 这在使用计算机的系统里面天生就存在重大隐患.

  然后是两点式, 它的过程是由两点式推算出一般方程的各个变量( 一般方程 ax + by + c = 0 ), 然后联立两个一般方程来进行求解, 它的稳定性和可扩展性就体现在这里:

        a = y2 - y1;
        b = x1 - x2;
        c = (y1 * x2) - (y2 * x1);

  它的初始计算完全不涉及除法, 第一步天生就是计算上稳定的. 然后是计算联立方程的方法, 这里直接用了二元一次线性方程组的求解

        float D = a1 * b2 - a2 * b1;
        if(D == 0)
        {
            return false;
        }
        float D1 = -c1 * b2 + c2 * b1;
        float D2 = -c1 * a1 + a2 * c1;
        point = new Vector3(D1 / D, 0, D2 / D);

  使用行列式计算的好处是很容易判断出是否有解, D==0 时就是无解, 这也是计算稳定性的保证, 然后这里是只计算x, z平面的, 也就是2D的,

如果要计算3D的只需要把行列式和一般方程封装成任意元的多元方法即可, 例如一般方程为( ax + by + cz + d = 0 )甚至更高阶的(  ax + by + cz + dw......+n = 0  ),

然后行列式求多元线性方程组的解就更简单了, 这就提供了很强大的扩展性, 不仅2维, 3维, 甚至N维都能提供解. 

  所以在做功能前仔细想好怎样做, 慎重选择数学模型是非常重要的. 虽然这些活看似初中生也能做, 大学生也能做, 差距还是一目了然的吧.

  PS: 两点式没有考虑射线的方向, 直接抄来用结果肯定是错的 have fun

猜你喜欢

转载自www.cnblogs.com/tiancaiwrk/p/11039595.html