anvaka/isect

GitHub: anvaka/isect

一个JavaScript线段交叉点检测库,提供扫描线、暴力、空间索引三种算法以适应不同数据密度和性能需求。

Stars: 279 | Forks: 18

# isect - 交叉点检测库 [![build status](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/242f15059d034944.svg)](https://github.com/anvaka/isect/actions/workflows/tests.yaml) 该库允许您在给定的线段集合中找出所有交叉点。 [![demo](https://i.imgur.com/XiQ45h7.gif)](https://anvaka.github.io/isect/) [在线试用演示](https://anvaka.github.io/isect/) # 算法 该库实现了三种算法 ## Bentley-Ottmann 扫描线算法 该算法的性能为 `O(n*log(n) + k*log(n))`,其中 `n` 是线段数量,`k` 是交叉点数量。 当您拥有大量线段且交叉点不多时(更具体地说是 `k = o(n^2/log(n))`),首选此方法。 该算法遵循 Mark de Berg、Otfried Cheong、Marc van Kreveld 和 Mark Overmars 合著的《Computation Geometry, Algorithms and Applications》一书。它确实支持退化情况,但请阅读限制部分以了解更多信息。 [![demo](https://i.imgur.com/dQrGKTt.gif)](https://anvaka.github.io/isect/?isAsync=true&p0=12&p1=40&generator=complete&algorithm=sweep&stepsPerFrame=1) ## 暴力算法 这是一个“朴素”的实现,即把每条线段与所有其他线段进行比较,因此性能为 O(n*n)。 尽管它很朴素,但在只有几千条线段却有数百万个交叉点的情况下,它比 Bentley-Ottmann 算法快得多。这种场景在力导向图绘制中很常见,即“毛球”由几千条线组成。 [![demo](https://i.imgur.com/SUKRHt4.gif)](https://anvaka.github.io/isect/?isAsync=true&p0=12&p1=40&generator=complete&algorithm=brute&stepsPerFrame=1) ## "Bush" 算法 该算法由 [@mourner](https://twitter.com/mourner/status/1049325199617921024) 和 [@dy](https://github.com/anvaka/isect/issues/1) 建议。 它使用 [mourner/flatbush](https://github.com/mourner/flatbush) 作为线段的空间索引,然后遍历每条线段,检查重叠的包围盒。 直观地看,该算法的最坏情况性能与暴力算法相当。当每条线段都与其他所有线段重叠时,我们应该预期会有 `O(n^2)` 次操作。然而,在实践中,该算法 beats 了 `Bentley-Ottman` 和 `Brute force` 方法。 它的美在于其简单性。它能很好地适应稀疏和密集的线段分布。 您也可以在下面找到性能测试套件,以便您自行决定。我绝对会将此算法作为我的默认选择。 ## 性能 基准测试代码[在此处可用](https://github.com/anvaka/isect/blob/master/perf/index.js)。每秒操作数越高越好! ### K12 图 [![K12 graph](https://i.imgur.com/PTXwvd3m.png)](https://anvaka.github.io/isect/?isAsync=false&p0=12&p1=40&generator=complete&algorithm=brute&stepsPerFrame=1) * Sweep: Circular lines 12x40 x 1,069 ops/sec ±1.98% (91 runs sampled) * **Brute: Circular lines 12x40 x 7,463 ops/sec ±3.01% (76 runs sampled)** * Bush: Circular lines 12x40 x 5,678 ops/sec ±2.20% (80 runs sampled) 该图只有 `66` 条唯一线段和 `313` 个唯一交叉点。暴力算法比扫描线快 7 倍,紧随其后的是 ### 100 条随机线 [![100 random lines](https://i.imgur.com/ytOEsyNm.png)](https://anvaka.github.io/isect/?isAsync=false&p0=100&p1=40&generator=random&algorithm=brute&stepsPerFrame=1) 在此演示中,100 条线是在边长为 42px 的方框内随机采样的。 * Sweep: 100 Random lines lines in 42px box x 277 ops/sec ±1.20% (87 runs sampled) * **Brute: 100 Random lines lines in 42px box x 3,606 ops/sec ±3.61% (74 runs sampled)** * Bush: 100 Random lines in 42px box x 3,314 ops/sec ±2.66% (83 runs sampled) 再一次,暴力算法胜出。暴力算法与 Bush 之间的差距缩小了。扫描线排在最后。 ### 稀疏交叉点 [![sparse](https://i.imgur.com/ZkzZS9sm.png)](https://anvaka.github.io/isect/?isAsync=false&p0=50&p1=40&generator=sparse&algorithm=sweep&stepsPerFrame=1) * Sweep: 2,500 sparse lines x 156 ops/sec ±0.97% (80 runs sampled) * Brute: 2,500 sparse lines x 13.62 ops/sec ±0.91% (38 runs sampled) * **Bush: 2,500 sparse lines x 592 ops/sec ±1.05% (93 runs sampled)** 现在 Bush 算法以巨大优势胜出。Bentley-Ottman 位居第二,暴力算法排在最后。 ### 平行倾斜线 [![slanted](https://i.imgur.com/vYAZzNvm.png)](https://anvaka.github.io/isect/?isAsync=false&p0=1000&p1=40&generator=parallelSlanted&algorithm=sweep&stepsPerFrame=1) * **Sweep: 1000 slanted, not intersect x 622 ops/sec ±1.23% (91 runs sampled)** * Brute: 1000 slanted, not intersect x 230 ops/sec ±2.37% (87 runs sampled) * Bush: 1000 slanted, not intersect x 243 ops/sec ±1.07% (87 runs sampled) 在这个例子中,线太多,且它们都不相交。此外,大多数矩形包围盒确实相交,这给 `bush` 算法增加了更多工作。 # 用法 从 npm 安装该模块: ``` npm i isect ``` 或从 CDN 下载: ``` ``` 如果您从 CDN 下载,该库将在 `isect` 全局名称下可用。 ## 基本用法 以下代码检测数组中线段之间的所有交叉点: ``` var isect = require('isect'); // Prepare the library to detect all intersection var detectIntersections = isect.bush([{ from: {x: 0, y: 0}, to: {x: 10, y: 10} }, { from: {x: 0, y: 10}, to: {x: 10, y: 0} }]); // Detect them all, operation is synchronous. var intersections = detectIntersections.run(); console.log(intersections); // Prints: // // [ { point: { x: 5, y: 5 }, segments: [ [Object], [Object] ] } ] // // array of segments contain both segments. ``` ## 暴力算法与扫描线算法 您也可以使用不同的算法运行上述示例。只需将 `.bush()` 更改为 `.sweep()`(以运行 Bentley-Ottman)或 `.brute()`(以尝试暴力算法): ``` var isect = require('isect'); // Prepare the library to detect all intersection var bruteForce = isect.brute([{ from: {x: 0, y: 0}, to: {x: 10, y: 10} }, { from: {x: 0, y: 10}, to: {x: 10, y: 0} }]); var intersections = bruteForce.run(); console.log(intersections); // do the same with sweep line: var sweepLine = isect.sweep([{ from: {x: 0, y: 0}, to: {x: 10, y: 10} }, { from: {x: 0, y: 10}, to: {x: 10, y: 0} }]); var intersections = sweepLine.run(); console.log(intersections); ``` 所有算法都有相同的 API。在下面的每个示例中,您可以将 `.bush()` 替换为 `.sweeep()` 或 `.brute()` —— 只需注意那些指出 API 差异的说明。 ## 提前停止 如果您不关心所有交叉点,但想知道是否至少有一个交叉点,您可以传递一个 `onFound()` 回调并请求库在找到交叉点后立即停止: ``` var intersections = isect.bush([/* array of segments */], { onFound(point, segments) { // `point` is {x, y} of the intersection, // `segments` are intersecting segments. // If you return true from this method, no further processing will be done: return true; // yes, stop right now! } }); ``` ## 异步工作流 如果您想让浏览器有时间处理用户输入,将算法分成块可能是理想的(这样 UI 线程就不会被淹没)。您可以通过调用算法实例的 `.step()` 方法来做到这一点: ``` var detector = isect.bush([/* array of segments */]); // instead of detector.run(), we do: var isDone = detector.step() // isDone will be set to true, once the algorithm is completed. ``` 这正是我如何进行算法逐步动画演示的方式: [![demo](https://i.imgur.com/dQrGKTt.gif)](https://anvaka.github.io/isect/?isAsync=true&p0=12&p1=40&generator=complete&algorithm=sweep&stepsPerFrame=1) [点击这里](https://anvaka.github.io/isect/?isAsync=true&p0=12&p1=40&generator=complete&algorithm=sweep&stepsPerFrame=1)查看其实际效果。 ## 限制 扫描线算法易受浮点舍入误差的影响。有可能构造一个包含近乎水平线的示例,导致其失败。 虽然扫描线实现检测 `point-segment`(点-线段)重叠,但我没有实现 `point-point`(点-点)重叠。即,输入数组中不与任何线段重叠的相同点不会被报告。 # 其他 * 演示的源代码[在此处可用](https://github.com/anvaka/isect/tree/master/demo/interactive)。 * 扫描线算法需要一个二叉搜索树。为此我使用了 [w8r/splay-tree](https://github.com/w8r/splay-tree)。非常喜欢这个库! 我也尝试过 AVL 树,但发现其性能比伸展树差。 * 如果您需要更高精度的扫描线,请考虑移植此库以使用 [decimal.js](https://github.com/MikeMcl/decimal.js-light)。 * 我非常希望在 JavaScript 中实现更快的交叉算法。 如果您知道任何算法 - 请分享。特别是这篇论文 [An optimal algorithm for finding segments intersections](http://club.pdmi.ras.ru/moodle/file.php/15/Materials/p211-balaban.pdf) 看起来非常有前途! 它们的运行时间是 `O(n * log(n) + k)`,应该比 Bentley-Ottmann 更快。 # 许可证 MIT # 感谢! 希望您喜欢这个库。如果您有任何反馈,请随时 ping 我 (anvaka@gmail.com 或 https://twitter.com/anvaka)。
标签:Bentley-Ottmann算法, Flatbush, JavaScript库, MITM代理, Webhook, 二维几何, 力导向图, 图形学, 扫描线算法, 数学库, 数据可视化, 暴力算法, 碰撞检测, 空间索引, 算法库, 线段相交检测, 自定义脚本, 计算几何