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);
})();