计算静态线和移动线之间最近的垂直点

Calculate the closest perpendicular point between a static line and a movable line

描述

给定的是一条蓝线,可以任意放置,可以鼠标移动。 通过移动线条,从原始位置绘制渐变线(此处为浅粉色)。现在这一行有以下要求:

假设蓝线的左边缘是点 1(红圈)并且 假设蓝线原始位置的左边缘为点 2(石灰圆) 假设蓝线的右侧是点 3(绿色圆圈) 从点 1 到 point2/point3.

的角度需要为 90 度或 -90 度

我相信这个术语是:渐变的粉色线和蓝色线要垂直。 蓝线的方向是不变的,只是它的位置!

我的问题

通过移动蓝线,我可以计算出其原始位置的度数并绘制渐变的亮粉色线。然而,我无法计算出离蓝线最近的点,这将使渐变热粉色线垂直于原始蓝线和蓝线的新位置。如果有人能指出我正确的公式或正确的术语来解决这个问题,我将不胜感激。

视觉示例(已删除 代码部分)

以下是我一起删除的一个简单示例。可以移动蓝线,但我不能强制点 1 与 point2/point3.

保持一定的 90/-90 度角

//REM: Current moving element
var _currentElement = null;

//REM: Drawing for quicker access
var _Drawing = null;

//REM: Starting the drag
function _onDown(event){
  if(event.target && event.target.tagName === 'line'){
    let tMatrix = _getMatrix(event.target);
    
    _currentElement = {
      Element: event.target,
      startX: event.clientX,
      startY: event.clientY,
      startE: tMatrix.e,
      startF: tMatrix.f,
      X1: Number(event.target.getAttribute('x1')),
      Y1: Number(event.target.getAttribute('y1')),
      X2: Number(event.target.getAttribute('x2')),
      Y2: Number(event.target.getAttribute('y2')),
      Ratio: 0.4
    }
  }
}

//REM: Dragging
function _onMove(event){
  if(_currentElement){
    _currentElement.endE = _currentElement.startE + ((event.clientX - _currentElement.startX) * _currentElement.Ratio);
    _currentElement.endF = _currentElement.startF + ((event.clientY - _currentElement.startY) * _currentElement.Ratio);

    console.log(
      'Angle (3)',
      _getAngleBetweenThreePoints(
        {x: _currentElement.X1 + _currentElement.endE, y: _currentElement.Y1 + _currentElement.endF},
        {x: _currentElement.X1, y: _currentElement.Y1},
        {x: _currentElement.X2 + _currentElement.endE, y: _currentElement.Y2 + _currentElement.endF}
      )
    );

    _setMatrix(_currentElement.Element, 1, 0, 0, 1, _currentElement.endE, _currentElement.endF)
  }
}

//REM: Ending the drag
function _onUp(){
  _currentElement = null
}

//REM: Returns the elements matrix
function _getMatrix(element){
  if(element){
    return element.transform.baseVal.numberOfItems ?
    element.transform.baseVal.getItem(0).matrix :
    element.transform.baseVal.appendItem(_Drawing.createSVGTransform()).matrix
  }
}

//REM: Sets the elements matrix
function _setMatrix(element, a, b, c, d, e, f){
  if(element){
    let tMatrix = _getMatrix(element);
    if(tMatrix){
      tMatrix.a = (typeof a === 'number') ? a : tMatrix.a;
      tMatrix.b = (typeof b === 'number') ? b : tMatrix.b;
      tMatrix.c = (typeof c === 'number') ? c : tMatrix.c;
      tMatrix.d = (typeof d === 'number') ? d : tMatrix.d;
      tMatrix.e = (typeof e === 'number') ? e : tMatrix.e;
      tMatrix.f = (typeof f === 'number') ? f : tMatrix.f;

      element.transform.baseVal.getItem(0).setMatrix(tMatrix)
    }
  }
}

//REM: Transforms client-coords to svg-coords
function _getSVGCoords(clientX, clientY){
    var tCTM = _Drawing.getScreenCTM();
    return tCTM ? {x: (clientX - tCTM.e) / tCTM.a, y: (clientY - tCTM.f ) / tCTM.d} : {x: clientX, y: clientY}
}

//REM: Returns angle from p1 to p2 and p3
function _getAngleBetweenThreePoints(p1, p2, p3){
  let tAngle = 0;

  if(p1 && p2 && p3){
    let tTemplate = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    tTemplate.setAttribute('r', 1);
    
    let tC1 = _Drawing.appendChild(tTemplate.cloneNode(false));
    tC1.setAttribute('fill', 'red');
    tC1.setAttribute('cx', p1.x);
    tC1.setAttribute('cy', p1.y);
    
    let tC2 = _Drawing.appendChild(tTemplate.cloneNode(false));
    tC2.setAttribute('fill', 'lime');
    tC2.setAttribute('cx', p2.x);
    tC2.setAttribute('cy', p2.y);
    
    let tC3 = _Drawing.appendChild(tTemplate.cloneNode(false));
    tC3.setAttribute('fill', 'green');
    tC3.setAttribute('cx', p3.x);
    tC3.setAttribute('cy', p3.y);
    
    let tLine = document.getElementById('line1');
    if(!tLine){
      tLine = _Drawing.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'line'));
      tLine.id = 'line1';
      tLine.setAttribute('stroke-dasharray', '5,5')
    };
    
    tLine.setAttribute('x1', p1.x);
    tLine.setAttribute('y1', p1.y);
    tLine.setAttribute('x2', p2.x);
    tLine.setAttribute('y2', p2.y);

    tAngle = (Math.atan2(p3.y - p1.y, p3.x - p1.x) - Math.atan2(p2.y - p1.y, p2.x - p1.x)) * 180 / Math.PI
  }

  return tAngle
};

//REM: Assiging events
window.onload = function(){
  _Drawing = document.querySelector('svg');

  document.body.addEventListener('mousedown', _onDown, false);
  document.body.addEventListener('mousemove', _onMove, false);
  document.body.addEventListener('mouseup', _onUp, false);
  document.body.addEventListener('mouseleave', _onUp, false)
};
line,
circle{
  pointer-events: none;
  stroke-width: 1
}

#movable{
  pointer-events: all;
  stroke-width: 10
}

#line1{
  stroke: hotpink
}
<svg xmlns = 'http://www.w3.org/2000/svg' viewBox = '0 0 300 300'>
  <line x1 = '50' y1 = '50' x2 = '160' y2 = '110' stroke = 'blue' id = 'movable'></line>
</svg>

预期结果

通过移动蓝线,我画了一条渐变的亮粉色线。该渐变线必须垂直于蓝线的原始位置以及蓝线的新位置。蓝线本身的方向可能不会改变,只是它的位置。如果线是水平的,则只允许直线向上和直线向下。如果线是垂直的,则只允许向左和向右笔直。如果该线是对角线,则仅允许沿该线对齐的直线运动(如铁路)。因此,例如,通过我的对角线蓝线并直线向上移动,我想计算离左边缘最近的位置,这将使渐变热粉色线垂直于蓝线的原始位置和蓝线的新位置.

这是一种与 Adobe PDF Measuring Tool 非常相似的行为。

要使运动垂直于蓝线,请执行以下操作:

  1. 获取蓝线方向的向量。
  2. 将其旋转 90 度(垂直于其表面)。
  3. 规范化,例如使其长度等于 1(称之为 n)。
  4. 现在获取鼠标的移动向量(current - start,称之为m)。
  5. 现在计算n * dot(n,m)。那是你在 n 方向的运动矢量。

我制作了一个 class 可以为您提供需要放置蓝线的位置的 x、y。

class cTracker {
constructor(x1, y1, x2, y2) {
    x1 = parseInt(x1);
    y1 = parseInt(y1);
    x2 = parseInt(x2);
    y2 = parseInt(y2);
    
    const extendLine = 10000;
    
    let blueLineRadian = Math.atan2(x1 - x2, y1 - y2);
    this.m_blueLineRadian = blueLineRadian;
    
    
    let magentaLineRadian = blueLineRadian + (Math.PI / 2);
    
    this.m_blueLineCos = Math.cos(blueLineRadian) * extendLine;
    this.m_blueLineSin = Math.sin(blueLineRadian) * extendLine;
    
    this.m_magentaLineX1 = x1 + Math.sin(magentaLineRadian) * extendLine;
    this.m_magentaLineY1 = y1 + Math.cos(magentaLineRadian) * extendLine;
    
    this.m_magentaLineX2 = x1 + Math.sin(magentaLineRadian + Math.PI) * extendLine;
    this.m_magentaLineY2 = y1 + Math.cos(magentaLineRadian + Math.PI) * extendLine;
    
}
// -------------------------------------------------------------------------
//
// -------------------------------------------------------------------------
getCursorPosition(x, y) {
    this.m_x1 = x - this.m_blueLineSin;
    this.m_y1 = y - this.m_blueLineCos;
    this.m_x2 = x + this.m_blueLineSin;
    this.m_y2 = y + this.m_blueLineCos;

        
    return this.intersect(this.m_magentaLineX1, this.m_magentaLineY1, this.m_magentaLineX2, this.m_magentaLineY2, this.m_x1, this.m_y1, this.m_x2, this.m_y2);
}

                                              
  // -------------------------------------------------------------------------
  //
  // -------------------------------------------------------------------------
  intersect(x1, y1, x2, y2, x3, y3, x4, y4) {
      
      var denominator = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1));
      
      var a                                   = y1 - y3;
      var b                                   = x1 - x3;
      var numerator1                          = ((x4 - x3) * a) - ((y4 - y3) * b);
      var numerator2                          = ((x2 - x1) * a) - ((y2 - y1) * b);
      a                                       = numerator1 / denominator;
      
      return {m_x : x1 + (a * (x2 - x1)), m_y : y1 + (a * (y2 - y1))};
  }
 }

要使用它,请用蓝线的 x1、y1、x2、y2 创建一个瞬间

let movableLine = document.getElementById('movable');
    
let x1 = parseInt(movableLine.getAttribute("x1"));
let y1 = parseInt(movableLine.getAttribute("y1"));
let x2 = parseInt(movableLine.getAttribute("x2"));
let y2 = parseInt(movableLine.getAttribute("y2"));

this.m_tracker = new cTracker(x1, y1, x2, y2);

要获得放置蓝线位置的 X、Y,您只需执行以下操作...

let xySVG = this._getSVGCoords(event.clientX, event.clientY);         
let xy = this.m_tracker.getCursorPosition(xySVG.x, xySVG.y);

console.log(xy); // This is where the blue line needs to be placed...

这是一个有效的 fiddle:https://jsfiddle.net/syxfv63z/2/