Canvas 垂直于线的点
Canvas perpendicular points to line
我正在使用 Konva 库在 HTML5 canvas 上绘制一些东西。
我通过鼠标点击从用户交互中得到了 2 分:
var A={x:'',y:''};
var B={x:'',y:''};
1) 这条线怎么画?
我的问题是:
1) 如何在每个区间得到垂直线?
2) 如何获取A点到B点的距离?
3)如何获取从A点到B点的所有点?
4) 如何获得红点?
我们有 A 点和 B 点。差异向量
D.X = B.X - A.X
D.Y = B.Y - A.Y
Length = Sqrt(D.X * D.X + D.Y * D.Y)
normalized (unit) vector
uD.X = D.X / Length
uD.Y = D.Y / Length
perpendicular unit vector
P.X = - uD.Y
P.Y = uD.X
some red point:
R.X = A.X + uD.X * Dist + P.X * SideDist * SideSign
R.Y = A.Y + uD.Y * Dist + P.Y * SideDist * SideSign
where Dist is in range 0..Length
Dist = i / N * Length for N equidistant points
SideSign is +/- 1 for left and right side
你没有解释你的线是什么所以我假设它是正弦波(虽然图像看起来像圆圈粘在一起???)
由于 MBo 已经给出了基础知识,所以这只是将其应用于波浪线。
// normalize a vector
function normalize(vec){
var length = Math.sqrt(vec.x * vec.x + vec.y * vec.y);
vec.x /= length;
vec.y /= length;
return vec;
}
// creates a wavy line
function wavyLine(start, end, waves, amplitude){
return ({
start,
end,
waves,
amplitude,
update(){
if(this.vec === undefined){
this.vec = {};
this.norm = {};
}
this.vec.x = this.end.x - this.start.x;
this.vec.y = this.end.y - this.start.y;
this.length = Math.sqrt(this.vec.x * this.vec.x + this.vec.y * this.vec.y);
this.norm.x = this.vec.x / this.length;
this.norm.y = this.vec.y / this.length;
return this;
}
}).update();
}
// draws a wavy line
function drawWavyLine(line) {
var x, stepSize, i, y, phase, dist;
ctx.beginPath();
stepSize = ctx.lineWidth;
ctx.moveTo(line.start.x, line.start.y);
for (i = stepSize; i < line.length; i+= stepSize) {
x = line.start.x + line.norm.x * i; // get point i pixels from start
y = line.start.y + line.norm.y * i; // get point i pixels from start
phase = (i / (line.length / line.waves)) * Math.PI * 2; // get the wave phase at this point
dist = Math.sin(phase) * line.amplitude; // get the distance from the line to the point on the wavy curve
x -= line.norm.y * dist;
y += line.norm.x * dist;
ctx.lineTo(x, y);
}
phase = line.waves * Math.PI * 2; // get the wave phase at this point
dist = Math.sin(phase) * line.amplitude; // get the distance from the line to the point on the wavy curve
ctx.lineTo(line.end.x - line.norm.y * dist, line.end.y + line.norm.x * dist);
ctx.stroke();
}
// find the closest point on a wavy line to a point returns the pos on the wave, tangent and point on the linear line
function closestPointOnLine(point,line){
var x = point.x - line.start.x;
var y = point.y - line.start.y;
// get the amount the line vec needs to be scaled so tat point is perpendicular to the line
var l = (line.vec.x * x + line.vec.y * y) / (line.length * line.length);
x = line.vec.x * l; // scale the vec
y = line.vec.y * l;
return pointAtDistance(Math.sqrt(x * x + y * y), line);
}
// find the point at (linear) distance along wavy line and return coordinate, coordinate on wave, and tangent
function pointAtDistance(distance,line){
var lenScale = line.length / line.waves; // scales the length into radians
var phase = distance * Math.PI * 2 / lenScale; // get the wave phase at this point
var dist = Math.sin(phase) * line.amplitude; // get the distance from the line to the point on the wavy curve
var slope = Math.cos(phase ) * Math.PI * 2 * line.amplitude / lenScale; // derivitive of sin(a*x) is -a*cos(a*x)
// transform tangent (slope) into a vector along the line. This vector is not a unit vector so normalize it
var tangent = normalize({
x : line.norm.x - line.norm.y * slope,
y : line.norm.y + line.norm.x * slope
});
// move from the line start to the point on the linear line at distance
var linear = {
x : line.start.x + line.norm.x * distance,
y : line.start.y + line.norm.y * distance
}
// move out perpendicular to the wavy part
return {
x : linear.x - line.norm.y * dist,
y : linear.y + line.norm.x * dist,
tangent,linear
};
}
// create a wavy line
var wLine = wavyLine({x:10,y:100},{x:300,y:100},3,50);
// draw the wavy line and show some points on it
function display(timer){
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0,0,w,h);
var radius = Math.max(ch,cw);
// set up the wavy line
wLine.waves = Math.sin(timer / 10000) * 6;
wLine.start.x = Math.cos(timer / 50000) * radius + cw;
wLine.start.y = Math.sin(timer / 50000) * radius + ch;
wLine.end.x = -Math.cos(timer / 50000) * radius + cw;
wLine.end.y = -Math.sin(timer / 50000) * radius + ch ;
wLine.update();
// draw the linear line
ctx.lineWidth = 0.5;
ctx.strokeStyle = "blue";
ctx.beginPath();
ctx.moveTo(wLine.start.x, wLine.start.y);
ctx.lineTo(wLine.end.x, wLine.end.y);
ctx.stroke();
// draw the wavy line
ctx.lineWidth = 2;
ctx.strokeStyle = "black";
drawWavyLine(wLine);
// find point nearest mouse
var p = closestPointOnLine(mouse,wLine);
ctx.lineWidth = 1;
ctx.strokeStyle = "red";
ctx.beginPath();
ctx.arc(p.x,p.y,5,0,Math.PI * 2);
ctx.moveTo(p.x + p.tangent.x * 20,p.y + p.tangent.y * 20);
ctx.lineTo(p.x - p.tangent.y * 10,p.y + p.tangent.x * 10);
ctx.lineTo(p.x + p.tangent.y * 10,p.y - p.tangent.x * 10);
ctx.closePath();
ctx.stroke();
// find points at equal distance along line
ctx.lineWidth = 1;
ctx.strokeStyle = "blue";
ctx.beginPath();
for(var i = 0; i < w; i += w / 10){
var p = pointAtDistance(i,wLine);
ctx.moveTo(p.x + 5,p.y);
ctx.arc(p.x,p.y,5,0,Math.PI * 2);
ctx.moveTo(p.x,p.y);
ctx.lineTo(p.linear.x,p.linear.y);
ctx.moveTo(p.x + p.tangent.x * 40, p.y + p.tangent.y * 40);
ctx.lineTo(p.x - p.tangent.x * 40, p.y - p.tangent.y * 40);
}
ctx.stroke();
}
/******************************************************************************
The code from here down is generic full page mouse and canvas boiler plate
code. As I do many examples which all require the same mouse and canvas
functionality I have created this code to keep a consistent interface. The
Code may or may not be part of the answer.
This code may or may not have ES6 only sections so will require a transpiler
such as babel.js to run on legacy browsers.
*****************************************************************************/
// V2.0 ES6 version for Whosebug and Groover QuickRun
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0;
// You can declare onResize (Note the capital R) as a callback that is also
// called once at start up. Warning on first call canvas may not be at full
// size.
;(function(){
const RESIZE_DEBOUNCE_TIME = 100;
var resizeTimeoutHandle;
var firstRun = true;
function createCanvas () {
var c,cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
function resizeCanvas () {
if (canvas === undefined) { canvas = createCanvas() }
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals() }
if (typeof onResize === "function") {
clearTimeout(resizeTimeoutHandle);
if (firstRun) { onResize() }
else { resizeTimeoutHandle = setTimeout(onResize, RESIZE_DEBOUNCE_TIME) }
firstRun = false;
}
}
function setGlobals () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
}
mouse = (function () {
var m; // alias for mouse
var mouse = {
x : 0, y : 0, // mouse position and wheel
buttonRaw : 0,
buttonOnMasks : [0b1, 0b10, 0b100], // mouse button on masks
buttonOffMasks : [0b110, 0b101, 0b011], // mouse button off masks
bounds : null,
eventNames : "mousemove,mousedown,mouseup".split(","),
event(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left - scrollX;
m.y = e.pageY - m.bounds.top - scrollY;
if (t === "mousedown") { m.buttonRaw |= m.buttonOnMasks[e.which - 1] }
else if (t === "mouseup") { m.buttonRaw &= m.buttonOffMasks[e.which - 1] }
},
start(element) {
m.element = element === undefined ? document : element;
m.eventNames.forEach(name => document.addEventListener(name, mouse.event) );
},
}
m = mouse;
return mouse;
})();
function update(timer) { // Main update loop
globalTime = timer;
display(timer); // call demo code
requestAnimationFrame(update);
}
setTimeout(function(){
canvas = createCanvas();
mouse.start(canvas);
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(update);
},0);
})();
我正在使用 Konva 库在 HTML5 canvas 上绘制一些东西。
我通过鼠标点击从用户交互中得到了 2 分:
var A={x:'',y:''};
var B={x:'',y:''};
1) 这条线怎么画?
我的问题是:
1) 如何在每个区间得到垂直线?
2) 如何获取A点到B点的距离?
3)如何获取从A点到B点的所有点?
4) 如何获得红点?
我们有 A 点和 B 点。差异向量
D.X = B.X - A.X
D.Y = B.Y - A.Y
Length = Sqrt(D.X * D.X + D.Y * D.Y)
normalized (unit) vector
uD.X = D.X / Length
uD.Y = D.Y / Length
perpendicular unit vector
P.X = - uD.Y
P.Y = uD.X
some red point:
R.X = A.X + uD.X * Dist + P.X * SideDist * SideSign
R.Y = A.Y + uD.Y * Dist + P.Y * SideDist * SideSign
where Dist is in range 0..Length
Dist = i / N * Length for N equidistant points
SideSign is +/- 1 for left and right side
你没有解释你的线是什么所以我假设它是正弦波(虽然图像看起来像圆圈粘在一起???)
由于 MBo 已经给出了基础知识,所以这只是将其应用于波浪线。
// normalize a vector
function normalize(vec){
var length = Math.sqrt(vec.x * vec.x + vec.y * vec.y);
vec.x /= length;
vec.y /= length;
return vec;
}
// creates a wavy line
function wavyLine(start, end, waves, amplitude){
return ({
start,
end,
waves,
amplitude,
update(){
if(this.vec === undefined){
this.vec = {};
this.norm = {};
}
this.vec.x = this.end.x - this.start.x;
this.vec.y = this.end.y - this.start.y;
this.length = Math.sqrt(this.vec.x * this.vec.x + this.vec.y * this.vec.y);
this.norm.x = this.vec.x / this.length;
this.norm.y = this.vec.y / this.length;
return this;
}
}).update();
}
// draws a wavy line
function drawWavyLine(line) {
var x, stepSize, i, y, phase, dist;
ctx.beginPath();
stepSize = ctx.lineWidth;
ctx.moveTo(line.start.x, line.start.y);
for (i = stepSize; i < line.length; i+= stepSize) {
x = line.start.x + line.norm.x * i; // get point i pixels from start
y = line.start.y + line.norm.y * i; // get point i pixels from start
phase = (i / (line.length / line.waves)) * Math.PI * 2; // get the wave phase at this point
dist = Math.sin(phase) * line.amplitude; // get the distance from the line to the point on the wavy curve
x -= line.norm.y * dist;
y += line.norm.x * dist;
ctx.lineTo(x, y);
}
phase = line.waves * Math.PI * 2; // get the wave phase at this point
dist = Math.sin(phase) * line.amplitude; // get the distance from the line to the point on the wavy curve
ctx.lineTo(line.end.x - line.norm.y * dist, line.end.y + line.norm.x * dist);
ctx.stroke();
}
// find the closest point on a wavy line to a point returns the pos on the wave, tangent and point on the linear line
function closestPointOnLine(point,line){
var x = point.x - line.start.x;
var y = point.y - line.start.y;
// get the amount the line vec needs to be scaled so tat point is perpendicular to the line
var l = (line.vec.x * x + line.vec.y * y) / (line.length * line.length);
x = line.vec.x * l; // scale the vec
y = line.vec.y * l;
return pointAtDistance(Math.sqrt(x * x + y * y), line);
}
// find the point at (linear) distance along wavy line and return coordinate, coordinate on wave, and tangent
function pointAtDistance(distance,line){
var lenScale = line.length / line.waves; // scales the length into radians
var phase = distance * Math.PI * 2 / lenScale; // get the wave phase at this point
var dist = Math.sin(phase) * line.amplitude; // get the distance from the line to the point on the wavy curve
var slope = Math.cos(phase ) * Math.PI * 2 * line.amplitude / lenScale; // derivitive of sin(a*x) is -a*cos(a*x)
// transform tangent (slope) into a vector along the line. This vector is not a unit vector so normalize it
var tangent = normalize({
x : line.norm.x - line.norm.y * slope,
y : line.norm.y + line.norm.x * slope
});
// move from the line start to the point on the linear line at distance
var linear = {
x : line.start.x + line.norm.x * distance,
y : line.start.y + line.norm.y * distance
}
// move out perpendicular to the wavy part
return {
x : linear.x - line.norm.y * dist,
y : linear.y + line.norm.x * dist,
tangent,linear
};
}
// create a wavy line
var wLine = wavyLine({x:10,y:100},{x:300,y:100},3,50);
// draw the wavy line and show some points on it
function display(timer){
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0,0,w,h);
var radius = Math.max(ch,cw);
// set up the wavy line
wLine.waves = Math.sin(timer / 10000) * 6;
wLine.start.x = Math.cos(timer / 50000) * radius + cw;
wLine.start.y = Math.sin(timer / 50000) * radius + ch;
wLine.end.x = -Math.cos(timer / 50000) * radius + cw;
wLine.end.y = -Math.sin(timer / 50000) * radius + ch ;
wLine.update();
// draw the linear line
ctx.lineWidth = 0.5;
ctx.strokeStyle = "blue";
ctx.beginPath();
ctx.moveTo(wLine.start.x, wLine.start.y);
ctx.lineTo(wLine.end.x, wLine.end.y);
ctx.stroke();
// draw the wavy line
ctx.lineWidth = 2;
ctx.strokeStyle = "black";
drawWavyLine(wLine);
// find point nearest mouse
var p = closestPointOnLine(mouse,wLine);
ctx.lineWidth = 1;
ctx.strokeStyle = "red";
ctx.beginPath();
ctx.arc(p.x,p.y,5,0,Math.PI * 2);
ctx.moveTo(p.x + p.tangent.x * 20,p.y + p.tangent.y * 20);
ctx.lineTo(p.x - p.tangent.y * 10,p.y + p.tangent.x * 10);
ctx.lineTo(p.x + p.tangent.y * 10,p.y - p.tangent.x * 10);
ctx.closePath();
ctx.stroke();
// find points at equal distance along line
ctx.lineWidth = 1;
ctx.strokeStyle = "blue";
ctx.beginPath();
for(var i = 0; i < w; i += w / 10){
var p = pointAtDistance(i,wLine);
ctx.moveTo(p.x + 5,p.y);
ctx.arc(p.x,p.y,5,0,Math.PI * 2);
ctx.moveTo(p.x,p.y);
ctx.lineTo(p.linear.x,p.linear.y);
ctx.moveTo(p.x + p.tangent.x * 40, p.y + p.tangent.y * 40);
ctx.lineTo(p.x - p.tangent.x * 40, p.y - p.tangent.y * 40);
}
ctx.stroke();
}
/******************************************************************************
The code from here down is generic full page mouse and canvas boiler plate
code. As I do many examples which all require the same mouse and canvas
functionality I have created this code to keep a consistent interface. The
Code may or may not be part of the answer.
This code may or may not have ES6 only sections so will require a transpiler
such as babel.js to run on legacy browsers.
*****************************************************************************/
// V2.0 ES6 version for Whosebug and Groover QuickRun
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0;
// You can declare onResize (Note the capital R) as a callback that is also
// called once at start up. Warning on first call canvas may not be at full
// size.
;(function(){
const RESIZE_DEBOUNCE_TIME = 100;
var resizeTimeoutHandle;
var firstRun = true;
function createCanvas () {
var c,cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
function resizeCanvas () {
if (canvas === undefined) { canvas = createCanvas() }
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals() }
if (typeof onResize === "function") {
clearTimeout(resizeTimeoutHandle);
if (firstRun) { onResize() }
else { resizeTimeoutHandle = setTimeout(onResize, RESIZE_DEBOUNCE_TIME) }
firstRun = false;
}
}
function setGlobals () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
}
mouse = (function () {
var m; // alias for mouse
var mouse = {
x : 0, y : 0, // mouse position and wheel
buttonRaw : 0,
buttonOnMasks : [0b1, 0b10, 0b100], // mouse button on masks
buttonOffMasks : [0b110, 0b101, 0b011], // mouse button off masks
bounds : null,
eventNames : "mousemove,mousedown,mouseup".split(","),
event(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left - scrollX;
m.y = e.pageY - m.bounds.top - scrollY;
if (t === "mousedown") { m.buttonRaw |= m.buttonOnMasks[e.which - 1] }
else if (t === "mouseup") { m.buttonRaw &= m.buttonOffMasks[e.which - 1] }
},
start(element) {
m.element = element === undefined ? document : element;
m.eventNames.forEach(name => document.addEventListener(name, mouse.event) );
},
}
m = mouse;
return mouse;
})();
function update(timer) { // Main update loop
globalTime = timer;
display(timer); // call demo code
requestAnimationFrame(update);
}
setTimeout(function(){
canvas = createCanvas();
mouse.start(canvas);
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(update);
},0);
})();