微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在手绘线/ Konva.Line形状上找到最接近x和y点的位置,到画布上的一个点

如何解决在手绘线/ Konva.Line形状上找到最接近x和y点的位置,到画布上的一个点

我需要找到Konva.Line形状上最接近画布上任意点的点。请参见下面的示例,其中鼠标指针是任意点,彩色线是Konva.Line。我特别需要Konvajs实现。

这是一个自我解答的问题,请参阅下面的我的解决方案。我愿意接受任何更好的建议。

Example of finding nearest point on a line

解决方法

经过一些网络研究,我发现了一种用于查找路径上最近点的常用算法。参见mbostock的文章。只需很少的更改即可按我的需要进行操作-请参见下面的代码段。

这可以通过使用SVG样式的路径定义,使用get-path-length函数来实现(我坚持使用伪命名,因为您的lib的确切命名可能有所不同,请参见Konva版本的代码段),然后迭代一堆长度上的点的获取(通过获取长度函数获得),以通过简单的数学计算每个点到任意点的距离。因为这样做有处理成本的开销,所以它使用粗步长法来近似,然后使用更精细的二进制方法来快速获得最终结果。结果是一个点-路径上到给定任意点的最近点。

所以-在Konva中启用此功能...注意目标是一条手绘线...

第一个问题是在Konva的上下文中在画布上绘制自由线,您可以使用Line形状。线形具有一系列点,这些点给出沿线的点的坐标。您给它加分,Konva用笔触将这些点连成一条线。通过在每次鼠标移动事件中将线条前进到鼠标指针的位置,即可轻松创建徒手绘制的线条(请参见代码段)。但是,线的点数组没有路径测量功能,因此我们必须将Konva.Line转换为Konva.Path形状,因为该DOES具有所需的路径功能。

将点转换为路径很简单。点数组的布局为[x1,y1,x2,y2,... xn,yn],而路径是一个字符串,布局为“ M x1,y1 L x2,y2 ... L xn,yn” 。它们都可能比这更复杂,但是坚持此简单的连接点线即可满足此要求。该代码段包含pointsToPath()函数。

现在已经找到创建Konva的路径。路径形状很简单。

  // use the pointsToPath fn to prepare a path from the line points.
  thePath = pointsToPath(lineShape.points());
  
  // Make a path shape tracking the lineShape because path has more options for measuring.
  pathShape = new Konva.Path({
    stroke: 'cyan',strokeWidth: 5,data: thePath            
  });
  layer.add(pathShape);

在代码段中,我将线条形状替换为路径形状,但是可能甚至不将形状添加到画布上,而是实例化它以用于最近的点处理。

所以-有了路径,我们可以调用mostestPoint()函数,为它提供鼠标位置和路径形状,以便该函数可以根据需要调用测量和定长获取功能。

  // showing nearest point -  link mouse pointer to the closest point on the line
  const closestPt = closestPoint(pathShape,{x: mousePos.x,y: mousePos.y});
  connectorLine.points([closestPt.x,closestPt.y,mousePos.x,mousePos.y]);

剩下的就是根据需要使用最近的值。在代码段中,我从鼠标指针到手绘线的最近点画了一条红线。

数学运算效率很高,并且可以在移动鼠标时实时进行该过程。请参见摘要。

let isDrawing = false;

  // Set up a stage
  stage = new Konva.Stage({
    container: 'container',width: window.innerWidth,height: window.innerHeight
  }),// add a layer to draw on
  layer = new Konva.Layer(),mode = 'draw',// state control,draw = drawing line,measuring = finding nearest point
  lineShape = null,// the line shape that we draw
  connectorLine = null,// link between mouse and nearest point
  pathShape = null;  // path element


// Add the layer to the stage
stage.add(layer);

// On this event,add a line shape to the canvas - we will extend the points of the line as the mouse moves.   
stage.on('mousedown touchstart',function (e) {
  reset();
  var pos = stage.getPointerPosition();

  if (mode === 'draw'){ // add the line that follows the mouse
    lineShape = new Konva.Line({
      stroke: 'magenta',points: [pos.x,pos.y],draggable: true
    });
    layer.add(lineShape);
  }
});

// when we finish drawing switch mode to measuring 
stage.on('mouseup touchend',function () {

  // use the pointsToPath fn to prepare a path from the line points.
  thePath = pointsToPath(lineShape.points());
  
  // Make a path shape tracking the lineShape because path has more options for measuring.
  pathShape = new Konva.Path({
    stroke: 'cyan',data: thePath            
  });
  layer.add(pathShape);

  lineShape.destroy(); // remove the path shape from the canvas as we are done with it
  
  layer.batchDraw();

  mode='measuring'; // switch the mode
});

// As the mouse is moved we aer concerned first with drawing the line,then measuring the nearest point from the mouse pointer on the line
stage.on('mousemove touchmove',function (e) {

  // get position of mouse pointer 
  const mousePos = stage.getPointerPosition();
  
  if (mode === 'draw' ){
    if (lineShape) { // on first move we will not yet have this shape!
      // drawing the line - extend the line shape by adding the mouse pointer position to the line points array
      const newPoints = lineShape.points().concat([mousePos.x,mousePos.y]);
      lineShape.points(newPoints); // update the line points array
    }
  }
  else {
    // showing nearest point -  link mouse pointer to the closest point on the line
    const closestPt = closestPoint(pathShape,y: mousePos.y});
    connectorLine.points([closestPt.x,mousePos.y]);
  }

  layer.batchDraw();

});


// Function to make a Konva path from the points array of a Konva.Line shape. 
// Returns a path that can be given to a Konva.Path as the .data() value.
// Points array is as [x1,y1,x2,y2,... xn,yn]
// Path is a string as "M x1,y1 L x2,y2...L xn,yn"
var pointsToPath = function(points){

  let path = '';
  
  for (var i = 0; i < points.length;  i = i + 2){

    switch (i){
        
      case 0:  // move to 
        
        path = path + 'M ' + points[i] + ',' + points[i + 1] + ' ';
        
        break;
        
     default: 
        
        path = path + 'L ' + points[i] + ',' + points[i + 1] + ' ';
        
        break;
  
    }
  }
  return path;
}

// reset the canvas & shapes as needed for a clean restart
function reset() {
  mode = 'draw';
  layer.destroyChildren();
  layer.draw();

  connectorLine = new Konva.Line({
    stroke: 'red',strokeWidth: 1,points: [0,-100,-100]
  })
  layer.add(connectorLine);
}
 
// reset when the user asks
$('#reset').on('click',function(){
  reset();
})
reset(); // reset at startup to prepare state

// From article by https://bl.ocks.org/mbostock at https://bl.ocks.org/mbostock/8027637
// modified as prefixes (VW)
function closestPoint(pathNode,point) {

  var pathLength = pathNode.getLength(),// (VW) replaces pathNode.getTotalLength(),precision = 8,best,bestLength,bestDistance = Infinity;

  // linear scan for coarse approximation
  for (var scan,scanLength = 0,scanDistance; scanLength <= pathLength; scanLength += precision) {
    if ((scanDistance = distance2(scan = pathNode.getPointAtLength(scanLength))) < bestDistance) {
      best = scan,bestLength = scanLength,bestDistance = scanDistance;
    }
  }

  // binary search for precise estimate
  precision /= 2;
  while (precision > 0.5) {
    var before,after,beforeLength,afterLength,beforeDistance,afterDistance;
    if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = pathNode.getPointAtLength(beforeLength))) < bestDistance) {
      best = before,bestLength = beforeLength,bestDistance = beforeDistance;
    } else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = pathNode.getPointAtLength(afterLength))) < bestDistance) {
      best = after,bestLength = afterLength,bestDistance = afterDistance;
    } else {
      precision /= 2;
    }
  }

  best = {x: best.x,y: best.y}; // (VW) converted to object instead of array,personal choice
  best.distance = Math.sqrt(bestDistance);
  return best;

  function distance2(p) {
    var dx = p.x - point.x,// (VW) converter to object from array 
        dy = p.y - point.y;
    return dx * dx + dy * dy;
  }
}
body {
  margin: 10;
  padding: 10;
  overflow: hidden;
  background-color: #f0f0f0;
}
#container {
  width: 600px;
  height: 400px;
  border: 1px solid silver;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://unpkg.com/konva@^3/konva.min.js"></script>
<p>Draw a line by click + drag. Move mouse to show nearest point on line function. </p>
<p>
  <button id = 'reset'>Reset</button></span>
</p>
<div id="container"></div>

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。