业务场景
公司业务需要在三维模型上进行取点,支持线段取点和区域取点功能。线段取点就是在模型上选取两个点形成一条线段,在线段内按相同的间隔取点;区域取点就是在模型上选取一个区域(不规则多边形),在多边形内部均匀的取点。
![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d030ba7ac6854870acbd2f4b4f1dafd4~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp)
由于模型上的点可以转换成平面坐标,所以这个问题可以在canvas进行验证,在canvas中实现线段取点和区域取点功能。效果如下:(Demo在线调试)
线段取点:
![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/230e6312ae3e401788eb4c604f97774e~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp)
区域取点:
![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/64707613f3d94b988e0084210062229d~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp)
线段取点
实现思路
线段取点很简单,使用三角函数关系就可以算出间隔点p0坐标值
求出起始点**(x1, y1)与终止点(x2, y2)**间的长度c,即: c = Math.sqrt((x2-x1)² + (y2 - y1)²)
根据取点间隔长度i,求得在线段上可取几个点n,即:n = Math.floor(c / i)
根据三角函数关系求出每个间隔点坐标
![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c7723eaf599945539e301d3b8f82b90f~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp)
取点代码
/**
* 获取线段中的点
*/
function getLineSegmentPoint(lineSegment, interval) {
try {
if (interval && lineSegment && lineSegment.length === 2) {
const point1 = lineSegment[0];
const point2 = lineSegment[1];
const a = point2.y - point1.y;
const b = point2.x - point1.x;
const c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
const n = Math.floor(c / interval);
const p = [];
for (let i = 1; i 8...→1, 那么组成多边形的边(线段1→5, 5→8)的左边都是多边形内部
所以我们需要先判断每条线段的哪一边是多边形内部
```
/**
* 求出多边形内部在边的哪一边
*/
function getDirection() {
for (let i = 0; i < points.length; i++) {
const startIndex = i;
const endIndex = i + 1 >= points.length ? 0 : i + 1;
const lineStartPoint = points[startIndex];
const lineEndPoint = points[endIndex];
// 不处理平行线
if (lineStartPoint.y === lineEndPoint.y) continue;
const midPoint = {
x: (lineStartPoint.x + lineEndPoint.x) / 2,
y: (lineStartPoint.y + lineEndPoint.y) / 2,
};
const midRightPoint = {
...midPoint,
x: midPoint.x + 0.1,
};
// const midLeftPoint = {
// ...midPoint,
// x: midPoint.x - 0.1,
// }
const isRightInPoly = rayCasting(midRightPoint, points) === 'in';
// const isLeftPoint = rayCasting(midLeftPoint, points) === 'in'
// 判断该线段的方向是向下还是向上
if (lineEndPoint.y < lineStartPoint.y) {
// 向上
return isRightInPoly ? 'right' : 'left';
} // 向下
else {
return isRightInPoly ? 'left' : 'right';
}
}
}
```
然后我们在根据这个点的左右是否是多边形内部来判断改点是否要被移除。
情况一:p点和nextVertex是水平线,多边形边的右侧是多边形内部。当previousVertex点在P点下方时,p点左右都不是多边形内部,需要移除;当previousVertex点在P点上方时,p点左侧是多边形内部,需要保留
![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b7c40bce345b4140a1c163439eb7ee5c~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp)
情况二:p点和nextVertex是水平线,多边形边的左侧是多边形内部。当previousVertex点在P点下方时,p点左侧是多边形内部,需要保留;当previousVertex点在P点上方时,p点左右都不是多边形内部,需要移除
![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2b6d95d954434f02848fc19bc71aae10~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp)
情况三:p点和perviousVertex是水平线,多边形边的右侧是多边形内部。当nextVertex点在P点上方时,p点右侧是多边形内部,需要保留;当previousVertex点在P点下方时,p点左右都不是多边形内部,需要移除;
![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bda9035d323a4e29a2d16be05ee50925~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp)
情况四:p点和nextVertex是水平线,多边形边的左侧是多边形内部。当nextVertex点在P点上方时,p点左右都不是多边形内部,需要移除;当previousVertex点在P点下方时,p点右侧是多边形内部,需要保留
![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fc5ac91ffe5b4a4abf5125489dc76b70~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp)
3.最后一种情况,当点p的前后两条线段都是水平线时,p点左右都不是多边形内部,需要移除
![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/46efdab2e4f14e60972b0dec4b83a0b5~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp)
/**
* 检测多边形顶点是否要去除
*/
function checkPointNeedRemove(index, direction) {
const {
currentVertex: p,
previousVertex,
nextVertex,
} = getNeighboringPoints(index);
const isTangencyPoint =
Math.sign(previousVertex.y - p.y) === Math.sign(nextVertex.y - p.y); // 前后两个点在同一侧,说明该顶点与横向栅格线相切
if (isTangencyPoint) {
return true;
}
// 改点的前后两段都是水平线
if (p.y === previousVertex.y && p.y === nextVertex.y) {
return true;
}
let pointNeedRemove = false;
// 第一段是水平线
if (p.y === previousVertex.y) {
if (p.x > previousVertex.x) {
// 线段从左往右 start ---->p
// \
// \
if (
(closingDirection === 'right' && nextVertex.y > p.y) ||
(closingDirection === 'left' && nextVertex.y < p.y)
) {
pointNeedRemove = true;
}
} else {
// 线段从右往左 p p.y)
) {
pointNeedRemove = true;
}
}
}
// 第二段是水平线
if (p.y === nextVertex.y) {
if (p.x < nextVertex.x) {
// 线段从左往右 p----> end
// /
// /
if (
(closingDirection === 'right' && previousVertex.y > p.y) ||
(closingDirection === 'left' && previousVertex.y < p.y)
) {
pointNeedRemove = true;
}
} else {
// 线段从右往左 end p.y)
) {
pointNeedRemove = true;
}
}
}
return pointNeedRemove;
}
在线调试
完整代码在这里 DrawDemo。
|