Canvas 中的徒手捕捉角度 pen/pencil 工具
Snap to angle freehand pen/pencil tool in Canvas
我现在在做什么
我正在尝试在 HTML5 Canvas 中构建手绘铅笔工具(使用 Paper.js 作为 Canvas 包装器)。
我想做什么
我想允许用户在绘图时绘制直线(例如当按下 Shift 键时)。理想情况下,这条直线应该 "snap" 到 8 方向对齐 "radius"。
我试过的(这不是我需要的)。
我已经尝试了一个非常简单的解决方案,我将鼠标点捕捉到一个接近圆形的点。这在某种程度上工作得很好,但它不完全是一个捕捉到角度的工具,它更像是捕捉到一个不可见的网格类型的工具。
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
点的解决方案。
将绘图限制为直线的基础非常简单——双关语:-)
当用户拖动鼠标时,以您想要的角度计算假想线上的最近点运行。
下面是计算直线上距离鼠标最近的点的方法:
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>
如果您只是更改代码中的一两行,您 get something 可能接近您想要的内容:
// ...
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);
}
}
// ...
您可以轻松modify this to snap the angle代替位置
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);
}
}
我现在在做什么
我正在尝试在 HTML5 Canvas 中构建手绘铅笔工具(使用 Paper.js 作为 Canvas 包装器)。
我想做什么
我想允许用户在绘图时绘制直线(例如当按下 Shift 键时)。理想情况下,这条直线应该 "snap" 到 8 方向对齐 "radius"。
我试过的(这不是我需要的)。
我已经尝试了一个非常简单的解决方案,我将鼠标点捕捉到一个接近圆形的点。这在某种程度上工作得很好,但它不完全是一个捕捉到角度的工具,它更像是捕捉到一个不可见的网格类型的工具。
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
点的解决方案。
将绘图限制为直线的基础非常简单——双关语:-)
当用户拖动鼠标时,以您想要的角度计算假想线上的最近点运行。
下面是计算直线上距离鼠标最近的点的方法:
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>
如果您只是更改代码中的一两行,您 get something 可能接近您想要的内容:
// ...
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);
}
}
// ...
您可以轻松modify this to snap the angle代替位置
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);
}
}