面试题 16.03. 交点 - 计算几何

面试题 16.03. 交点


给定两条线段(表示为起点start = {X1, Y1}和终点end = {X2, Y2}),如果它们有交点,请计算其交点,没有交点则返回空值。
要求浮点型误差不超过10^-6。若有多个交点(线段重叠)则返回 X 值最小的点,X 坐标相同则返回 Y 值最小的点。

输入:
line1 = {0, 0}, {1, 0}
line2 = {1, 1}, {0, -1}
输出: {0.5, 0}

来源:https://leetcode-cn.com/problems/intersection-lcci/
题解:

  1. 排除界限不重合的情况
if (Math.max(a[0], b[0]) < Math.min(c[0], d[0]) ||
    Math.max(c[0], d[0]) < Math.min(a[0], b[0]) ||
    Math.max(a[1], b[1]) < Math.min(c[1], d[1]) ||
    Math.max(c[1], d[1]) < Math.min(a[1], b[1])) {
    return [];
}
  1. 根据叉乘的正负判断两个点是否在一条线段的两侧,原理见下图
    相交
    不相交
/* 获取向量叉乘值 sx x sy */
function getCross(s, x, y) {
    let x1, y1, x2, y2;
    [x1, y1] = [x[0] - s[0], x[1] - s[1]];
    [x2, y2] = [y[0] - s[0], y[1] - s[1]];
    return x1 * y2 - x2 * y1;
}
let acb = getCross(a, c, b);//ac x ab
let adb = getCross(a, d, b);//ad x ab
let cad = getCross(c, a, d);//ca x cd
let cbd = getCross(c, b, d);//cb x cd
  1. 确保有交点后根据y=kx+b根据数学公式获取两条线段的k和b,提前排序是为了垂直时后面可以直接取到最小值
/* 统一端点顺序 */
if (a[0] > b[0] || a[0] == b[0] && a[1] > b[1]) [a, b] = [b, a];
if (c[0] > d[0] || c[0] == d[0] && c[1] > d[1]) [c, d] = [d, c];
/* k = (y2 - y1) / (x2 - x1) */
let k1 = (b[1] - a[1]) / (b[0] - a[0]);
let k2 = (d[1] - c[1]) / (d[0] - c[0]);
/* b = (x2 * y1 - x1 * y2) / (x2 - x1) */
let b1 = (b[0] * a[1] - a[0] * b[1]) / (b[0] - a[0]);
let b2 = (d[0] * c[1] - c[0] * d[1]) / (d[0] - c[0]);
  1. 对于共线情况,遍历4个点,先判断是否是公共点,再根据题目要求选x最小的或x相等y最小的
/* 判断斜率相同情况下x,y是否在两条线段上 */
function isInLine(x, y) {
    for (let [s, e] of [[a, b], [c, d]]) {
        /* 求边界值 */
        let [maxx, maxy] = [Math.max(s[0], e[0]), Math.max(s[1], e[1])];
        let [minx, miny] = [Math.min(s[0], e[0]), Math.min(s[1], e[1])];
        if (x < minx || x > maxx || y < miny || y > maxy)
            return false;
    }
    return true;
}
/* 共线 包括垂直的情况 */
if (k1 == k2) {
    let x = null, y = null;
    for (let [xx, yy] of [a, b, c, d]) {
        if (isInLine(xx, yy)) {
            if (x == null && y == null ||
                xx < x || xx == x && yy < y) {
                x = xx; y = yy;
            }
        }
    }
    return [x, y];
}
  1. 若只有一条线垂直,直接取垂直线的横坐标,计算y值,若没有垂直的线,带公式求x,y即可
else if (k1 === Infinity) {//ab垂直
    return [a[0], k2 * a[0] + b2];
}
else if (k2 === Infinity) {//cd垂直
    return [c[0], k1 * c[0] + b1];
}
else {//正常相交情况
    let x = (b2 - b1) / (k1 - k2);
    return [x, x * k1 + b1];
}
  1. 完整代码
var intersection = function (a, b, c, d) {
    /* 排除区间不相交的情况 */
    if (Math.max(a[0], b[0]) < Math.min(c[0], d[0]) ||
        Math.max(c[0], d[0]) < Math.min(a[0], b[0]) ||
        Math.max(a[1], b[1]) < Math.min(c[1], d[1]) ||
        Math.max(c[1], d[1]) < Math.min(a[1], b[1])) {
        return [];
    }
    /* 获取向量叉乘值 sx x sy */
    function getCross(s, x, y) {
        let x1, y1, x2, y2;
        [x1, y1] = [x[0] - s[0], x[1] - s[1]];
        [x2, y2] = [y[0] - s[0], y[1] - s[1]];
        return x1 * y2 - x2 * y1;
    }
    let acb = getCross(a, c, b);//ac x ab
    let adb = getCross(a, d, b);//ad x ab
    let cad = getCross(c, a, d);//ca x cd
    let cbd = getCross(c, b, d);//cb x cd
    /*有交点 b,c在ab两侧 ab在cd两侧*/
    if (acb * adb <= 0 && cad * cbd <= 0) {
        /* 统一端点顺序 */
        if (a[0] > b[0] || a[0] == b[0] && a[1] > b[1]) [a, b] = [b, a];
        if (c[0] > d[0] || c[0] == d[0] && c[1] > d[1]) [c, d] = [d, c];
        /* k = (y2 - y1) / (x2 - x1) */
        let k1 = (b[1] - a[1]) / (b[0] - a[0]);
        let k2 = (d[1] - c[1]) / (d[0] - c[0]);
        /* b = (x2 * y1 - x1 * y2) / (x2 - x1) */
        let b1 = (b[0] * a[1] - a[0] * b[1]) / (b[0] - a[0]);
        let b2 = (d[0] * c[1] - c[0] * d[1]) / (d[0] - c[0]);
        /* 判断斜率相同情况下x,y是否在两条线段上 */
        function isInLine(x, y) {
            for (let [s, e] of [[a, b], [c, d]]) {
                /* 求边界值 */
                let [maxx, maxy] = [Math.max(s[0], e[0]), Math.max(s[1], e[1])];
                let [minx, miny] = [Math.min(s[0], e[0]), Math.min(s[1], e[1])];
                if (x < minx || x > maxx || y < miny || y > maxy)
                    return false;
            }
            return true;
        }
        /* 共线 包括垂直的情况 */
        if (k1 == k2) {
            let x = null, y = null;
            for (let [xx, yy] of [a, b, c, d]) {
                if (isInLine(xx, yy)) {
                    if (x == null && y == null ||
                        xx < x || xx == x && yy < y) {
                        x = xx; y = yy;
                    }
                }
            }
            return [x, y];
        }
        else if (k1 === Infinity) {//ab垂直
            return [a[0], k2 * a[0] + b2];
        }
        else if (k2 === Infinity) {//cd垂直
            return [c[0], k1 * c[0] + b1];
        }
        else {//正常相交情况
            let x = (b2 - b1) / (k1 - k2);
            return [x, x * k1 + b1];
        }
    }
    /* 没有交点 */
    return [];
};
posted @ 2020-05-04 10:11  aeipyuan  阅读(280)  评论(0编辑  收藏  举报