在画布中使用免费手绘钢笔/铅笔工具时吸附到角度

3

我正在做什么

我正在尝试在HTML5画布中构建一个自由手绘铅笔工具(使用Paper.js作为画布包装器)。

我想做什么

我希望允许用户在绘图时绘制直线(例如按下Shift键),这条直线应该“捕捉”到8方向的“半径”。

我尝试过的解决方案(并不完全符合我的需求)。

我尝试了一个非常简单的解决方案,其中将鼠标点捕捉到附近的圆角点。这个方法有些有效,但它并不完全是一个捕捉到角度的工具,更像是一个捕捉到无形网格的工具。

  mousedrag: function(event) {
    var snapped = {x: snap(event.point.x), y: snap(event.point.y)};

    // add "snapped" drag point
    path.add(snapped.x, snapped.y);
  }

  // convert a number to a rounded snap
  function snap(x, div) { 
    return Math.round(x/div)*div; 
  };

这是我目前正在做的互动Sketch(按住Shift键可以将图形对齐到网格上,释放后恢复到自由手绘模式)


有人可以给我指示如何进行角度捕捉,而不是网格吗?

注:

  • 尽管我正在使用Canvas/Paper.js,但我理解解决问题的方法独立于我使用的渲染技术,因此任何基于JS的解决方案(无论是SVG还是Canvas,包装器或无包装器)都应该为我提供一些良好的基础。

  • 我有一种感觉,解决方案可能涉及Math.atan()或类似的东西,而不是我的解决方案,其中我会捕捉到一个四舍五入的Math.round点。

2个回答

4
如果您只更改了代码中的一两行,您可能会得到一些接近您想要的东西
// ...

function onMouseDrag(event) {
    // if shift is down we transform the mousepoint
    // to a "snapped point", else add the mousepoint as it is.
    if(shiftDown)
    {
        var snapPoint = new Point(snap(event.point.x), snap(event.point.y));
        myPath.lastSegment.point = snapPoint;
    }
    else 
    {
        var snapPoint = event.point;
        myPath.add(snapPoint);
    }
}

// ...

你可以轻松地将修改此代码以捕捉角度而不是位置。
function onMouseDrag(event) {
    // if shift is down we transform the mousepoint
    // to a "snapped point", else add the mousepoint as it is.
    if(shiftDown)
    {
        var vector = event.point - myPath.lastSegment.previous.point;
        vector.angle = Math.round(vector.angle/angleSnap)*angleSnap;
        myPath.lastSegment.point = myPath.lastSegment.previous.point + vector;
    }
    else 
    {
        var snapPoint = event.point;
        myPath.add(snapPoint);
    }
}

看起来很整洁 - 在 mouseDown 处理程序中添加 myPath.add(event.point) 也将允许开始绘制直线。如果我加上这个,你介意吗? - nicholaswmin
@NicholasKyriakides 请开始! - arthur.sw

3

限制绘图为直线的基础是相当简单的 - 意在双关 :-)

当用户拖动鼠标时,计算与所需角度上运行的虚拟直线上最近的点。

以下是计算最靠近鼠标的直线上的点的方法:

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
  var BB=canvas.getBoundingClientRect();
  offsetX=BB.left;
  offsetY=BB.top;        
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }

var lineStart={x:50,y:50};
var lineEnd={x:250,y:250};
var cr=15;

draw({x:-20,y:-20},'green');

$("#canvas").mousemove(function(e){handleMouseMove(e);});

//////////////////////////////////

function draw(pt,fill){
  ctx.clearRect(0,0,cw,ch);
  ctx.beginPath();
  ctx.moveTo(lineStart.x,lineStart.y);
  ctx.lineTo(lineEnd.x,lineEnd.y);
  ctx.strokeStyle='black';
  ctx.stroke();
  ctx.beginPath();
  ctx.arc(pt.x,pt.y,5,0,Math.PI*2);
  ctx.closePath();
  ctx.fillStyle=fill;
  ctx.fill();
}

// Find line segment point closest to source point
// [x0,y0] to [x1,y1] define a line segment
// [cx,cy] is source point 
function calcClosestPtOnSegment(x0,y0,x1,y1,cx,cy,cr){

  // calc delta distance: source point to line start
  var dx=cx-x0;
  var dy=cy-y0;

  // calc delta distance: line start to end
  var dxx=x1-x0;
  var dyy=y1-y0;

  // Calc position on line normalized between 0.00 & 1.00
  // == dot product divided by delta line distances squared
  var t=(dx*dxx+dy*dyy)/(dxx*dxx+dyy*dyy);

  // calc nearest pt on line
  var x=x0+dxx*t;
  var y=y0+dyy*t;

  // clamp results to being on the segment
  if(t<0){x=x0;y=y0;}
  if(t>1){x=x1;y=y1;}

  return({ 
    x:x, y:y, 
    isColliding:((cx-x)*(cx-x)+(cy-y)*(cy-y)) < cr*cr,
    isOnSegment:(t>=0 && t<=1),
  });
}

function handleMouseMove(e){
  // tell the browser we're handling this event
  e.preventDefault();
  e.stopPropagation();

  mouseX=parseInt(e.clientX-offsetX);
  mouseY=parseInt(e.clientY-offsetY);

  var p=calcClosestPtOnSegment(
    lineStart.x,lineStart.y,lineEnd.x,lineEnd.y,
    mouseX,mouseY,cr);
  var fill=(p.isOnSegment)?'green':'red';

  draw(p,fill);

  ctx.beginPath();
  ctx.arc(mouseX,mouseY,cr,0,Math.PI*2);
  ctx.closePath();
  ctx.strokeStyle=p.isColliding?'green':'blue';
  ctx.stroke();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Move mouse to see closest point on angled line segment.<br>Closest point is green (or red if beyond segment) </h4>
<canvas id="canvas" width=300 height=300></canvas>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接