-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* fix: tolerate async function #95 * feat: add polygon intersect test function #94 * chore: bump v3.3.0
- Loading branch information
Showing
9 changed files
with
329 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { isPointInPolygon } from '../../../src/math'; | ||
|
||
describe('point in polygon', () => { | ||
it('in polygon', () => { | ||
const points = [ | ||
[0, 0], | ||
[0, 100], | ||
[30, 100], | ||
[30, 0], | ||
]; | ||
|
||
expect(isPointInPolygon(points, 0, 0)).toEqual(true); // 顶点 | ||
expect(isPointInPolygon(points, 0, 10)).toEqual(true); // 边上 | ||
expect(isPointInPolygon(points, 0, -1)).toEqual(false); // 顶点 | ||
|
||
expect(isPointInPolygon(points, 10, 10)).toEqual(true); // 边上 | ||
expect(isPointInPolygon(points, 30, 0)).toEqual(true); | ||
expect(isPointInPolygon(points, 30, 10)).toEqual(true); | ||
}); | ||
|
||
it('other polygon', () => { | ||
const points = [ | ||
[0, 0], | ||
[100, 0], | ||
[0, 100], | ||
[100, 100], | ||
]; | ||
expect(isPointInPolygon(points, 50, 50)).toEqual(true); | ||
expect(isPointInPolygon(points, 50, 40)).toEqual(true); | ||
expect(isPointInPolygon(points, 40, 50)).toEqual(false); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { isPolygonsIntersect } from '../../../src/math'; | ||
|
||
describe('is polygons intersect', () => { | ||
const points = [ | ||
[0, 0], | ||
[0, 100], | ||
[20, 100], | ||
[20, 0], | ||
]; | ||
|
||
it('length < 2', () => { | ||
expect(isPolygonsIntersect(points, [])).toEqual(false); | ||
expect(isPolygonsIntersect([], points)).toEqual(false); | ||
expect(isPolygonsIntersect([[0, 0]], points)).toEqual(false); | ||
}); | ||
|
||
it('length = 2', () => { | ||
const points1 = [ | ||
[0, 0], | ||
[20, 0], | ||
]; | ||
expect(isPolygonsIntersect(points, points1)).toEqual(true); | ||
}); | ||
|
||
it('no intersect', () => { | ||
const points1 = [ | ||
[10, 120], | ||
[10, 120], | ||
[30, 98], | ||
]; | ||
expect(isPolygonsIntersect(points, points1)).toEqual(false); | ||
expect(isPolygonsIntersect(points1, points)).toEqual(false); | ||
}); | ||
|
||
it('intersect', () => { | ||
const points1 = [ | ||
[10, 120], | ||
[10, 120], | ||
[30, 98], | ||
[-10, 98], | ||
]; | ||
expect(isPolygonsIntersect(points, points1)).toEqual(true); | ||
expect(isPolygonsIntersect(points1, points)).toEqual(true); | ||
}); | ||
|
||
it('one in one', () => { | ||
const points1 = [ | ||
[1, 10], | ||
[8, 20], | ||
[14, 10], | ||
]; | ||
expect(isPolygonsIntersect(points, points1)).toEqual(true); | ||
expect(isPolygonsIntersect(points1, points)).toEqual(true); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,6 @@ | ||
/** | ||
* 是否为函数 | ||
* @param {*} fn 对象 | ||
* @return {Boolean} 是否函数 | ||
* @see https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_isfunction | ||
*/ | ||
import isType from './is-type'; | ||
|
||
export default (value: any): value is Function => { | ||
return isType(value, 'Function'); | ||
return typeof value === 'function'; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { isPointInPolygon } from './is-point-in-polygon'; | ||
export { isPolygonsIntersect } from './is-polygons-intersect'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// 多边形的射线检测,参考:https://blog.csdn.net/WilliamSun0122/article/details/77994526 | ||
const tolerance = 1e-6; | ||
// 三态函数,判断两个double在eps精度下的大小关系 | ||
function dcmp(x: number) { | ||
if (Math.abs(x) < tolerance) { | ||
return 0; | ||
} | ||
|
||
return x < 0 ? -1 : 1; | ||
} | ||
|
||
// 判断点Q是否在p1和p2的线段上 | ||
function onSegment(p1: number[], p2: number[], q: number[]) { | ||
if ( | ||
(q[0] - p1[0]) * (p2[1] - p1[1]) === (p2[0] - p1[0]) * (q[1] - p1[1]) && | ||
Math.min(p1[0], p2[0]) <= q[0] && | ||
q[0] <= Math.max(p1[0], p2[0]) && | ||
Math.min(p1[1], p2[1]) <= q[1] && | ||
q[1] <= Math.max(p1[1], p2[1]) | ||
) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
// 判断点P在多边形内-射线法 | ||
export function isPointInPolygon(points: number[][], x: number, y: number) { | ||
let isHit = false; | ||
const n = points.length; | ||
if (n <= 2) { | ||
// svg 中点小于 3 个时,不显示,也无法被拾取 | ||
return false; | ||
} | ||
for (let i = 0; i < n; i++) { | ||
const p1 = points[i]; | ||
const p2 = points[(i + 1) % n]; | ||
if (onSegment(p1, p2, [x, y])) { | ||
// 点在多边形一条边上 | ||
return true; | ||
} | ||
// 前一个判断min(p1[1],p2[1])<P.y<=max(p1[1],p2[1]) | ||
// 后一个判断被测点 在 射线与边交点 的左边 | ||
if ( | ||
dcmp(p1[1] - y) > 0 !== dcmp(p2[1] - y) > 0 && | ||
dcmp(x - ((y - p1[1]) * (p1[0] - p2[0])) / (p1[1] - p2[1]) - p1[0]) < 0 | ||
) { | ||
isHit = !isHit; | ||
} | ||
} | ||
return isHit; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import { isPointInPolygon } from './is-point-in-polygon'; | ||
|
||
type Point = { | ||
x: number; | ||
y: number; | ||
}; | ||
|
||
type Line = { | ||
from: Point; | ||
to: Point; | ||
}; | ||
|
||
const isBetween = (value: number, min: number, max: number) => value >= min && value <= max; | ||
function getLineIntersect(p0: Point, p1: Point, p2: Point, p3: Point): Point | null { | ||
const tolerance = 0.001; | ||
const E: Point = { | ||
x: p2.x - p0.x, | ||
y: p2.y - p0.y, | ||
}; | ||
const D0: Point = { | ||
x: p1.x - p0.x, | ||
y: p1.y - p0.y, | ||
}; | ||
const D1: Point = { | ||
x: p3.x - p2.x, | ||
y: p3.y - p2.y, | ||
}; | ||
const kross: number = D0.x * D1.y - D0.y * D1.x; | ||
const sqrKross: number = kross * kross; | ||
const sqrLen0: number = D0.x * D0.x + D0.y * D0.y; | ||
const sqrLen1: number = D1.x * D1.x + D1.y * D1.y; | ||
let point: Point | null = null; | ||
if (sqrKross > tolerance * sqrLen0 * sqrLen1) { | ||
const s = (E.x * D1.y - E.y * D1.x) / kross; | ||
const t = (E.x * D0.y - E.y * D0.x) / kross; | ||
if (isBetween(s, 0, 1) && isBetween(t, 0, 1)) { | ||
point = { | ||
x: p0.x + s * D0.x, | ||
y: p0.y + s * D0.y, | ||
}; | ||
} | ||
} | ||
return point; | ||
} | ||
|
||
function parseToLines(points: number[][]): Line[] { | ||
const lines = []; | ||
const count = points.length; | ||
for (let i = 0; i < count - 1; i++) { | ||
const point = points[i]; | ||
const next = points[i + 1]; | ||
lines.push({ | ||
from: { | ||
x: point[0], | ||
y: point[1], | ||
}, | ||
to: { | ||
x: next[0], | ||
y: next[1], | ||
}, | ||
}); | ||
} | ||
if (lines.length > 1) { | ||
const first = points[0]; | ||
const last = points[count - 1]; | ||
lines.push({ | ||
from: { | ||
x: last[0], | ||
y: last[1], | ||
}, | ||
to: { | ||
x: first[0], | ||
y: first[1], | ||
}, | ||
}); | ||
} | ||
return lines; | ||
} | ||
|
||
function lineIntersectPolygon(lines: Line[], line: Line) { | ||
let isIntersect = false; | ||
lines.forEach((l) => { | ||
if (getLineIntersect(l.from, l.to, line.from, line.to)) { | ||
isIntersect = true; | ||
return false; | ||
} | ||
}); | ||
return isIntersect; | ||
} | ||
|
||
type BBox = { | ||
minX: number; | ||
minY: number; | ||
maxX: number; | ||
maxY: number; | ||
}; | ||
|
||
function getBBox(points: number[][]): BBox { | ||
const xArr = points.map((p) => p[0]); | ||
const yArr = points.map((p) => p[1]); | ||
return { | ||
minX: Math.min.apply(null, xArr), | ||
maxX: Math.max.apply(null, xArr), | ||
minY: Math.min.apply(null, yArr), | ||
maxY: Math.max.apply(null, yArr), | ||
}; | ||
} | ||
|
||
function intersectBBox(box1: BBox, box2: BBox) { | ||
return !(box2.minX > box1.maxX || box2.maxX < box1.minX || box2.minY > box1.maxY || box2.maxY < box1.minY); | ||
} | ||
|
||
/** | ||
* @see https://stackoverflow.com/questions/753140/how-do-i-determine-if-two-convex-polygons-intersect | ||
*/ | ||
export function isPolygonsIntersect(points1: number[][], points2: number[][]) { | ||
// 空数组,或者一个点返回 false | ||
if (points1.length < 2 || points2.length < 2) { | ||
return false; | ||
} | ||
|
||
const bbox1 = getBBox(points1); | ||
const bbox2 = getBBox(points2); | ||
// 判定包围盒是否相交,比判定点是否在多边形内要快的多,可以筛选掉大多数情况 | ||
if (!intersectBBox(bbox1, bbox2)) { | ||
return false; | ||
} | ||
|
||
let isIn = false; | ||
// 判定点是否在多边形内部,一旦有一个点在另一个多边形内,则返回 | ||
points2.forEach((point) => { | ||
if (isPointInPolygon(points1, point[0], point[1])) { | ||
isIn = true; | ||
return false; | ||
} | ||
}); | ||
if (isIn) { | ||
return true; | ||
} | ||
// 两个多边形都需要判定 | ||
points1.forEach((point) => { | ||
if (isPointInPolygon(points2, point[0], point[1])) { | ||
isIn = true; | ||
return false; | ||
} | ||
}); | ||
if (isIn) { | ||
return true; | ||
} | ||
|
||
const lines1 = parseToLines(points1); | ||
const lines2 = parseToLines(points2); | ||
let isIntersect = false; | ||
lines2.forEach((line) => { | ||
if (lineIntersectPolygon(lines1, line)) { | ||
isIntersect = true; | ||
return false; | ||
} | ||
}); | ||
return isIntersect; | ||
} |