#javascript #geometry
#javascript #геометрия
Вопрос:
Описание
Дана синяя линия, которую можно разместить по своему усмотрению и перемещать с помощью мыши. При перемещении линии линия градиента (здесь в hotpink) оттягивается от исходного положения. Теперь к этой строке предъявляются следующие требования:
Предполагая, что левый край синей линии является точкой 1 (красный круг), и предполагая, что левый край исходного положения синей линии является точкой 2 (лаймовый круг), предполагая, что правая сторона синей линии является точкой 3 (зеленый круг), угол от точки 1 должен быть либо 90или -90 градусов до точки 2 / точки 3.
Я считаю, что термин для этого таков: линия градиентной точки и синяя линия должны быть перпендикулярны.
Направление синей линии не должно меняться, только ее положение!
Моя проблема
Перемещая синюю линию, я могу рассчитать градус до его исходного положения и нарисовать линию градиентной точки. Тем не менее, я не могу вычислить ближайшую точку от синей линии, которая сделала бы линию градиентной горячей точки перпендикулярной как исходной синей линии, так и новой позиции синей линии. Если кто-нибудь может указать мне правильную формулу или правильный термин для этой проблемы, я был бы благодарен.
Визуальный пример (вырезанные части кода)
Ниже приведен краткий пример, который я собрал вместе. Можно переместить синюю линию, но я не могу заставить точку 1 сохранять определенный угол 90 / -90 градусов до точки 2 / точки 3.
//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 amp;amp; 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 amp;amp; p2 amp;amp; 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. 1. Вы хотите ограничить перемещение этой синей линии (только перемещение перпендикулярно самой себе)? Или вы хотите нарисовать градиент перпендикулярно? 2. Когда у вас есть два вектора, они ортогональны, если их скалярное произведение равно нулю. Это может помочь.
2. @Josef Wittmann: я хочу ограничить перемещение синей линии, чтобы розовая линия всегда была перпендикулярной. Так что вроде как и то, и другое. Adobe PDF предлагает ту же функциональность, которая там называется инструментом расстояния вместе с привязкой к конечным точкам. Я проверю вашу подсказку. Спасибо.
Ответ №1:
Я создал класс, который дает вам 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...
Вот рабочая скрипка: https://jsfiddle.net/syxfv63z/2 /
Комментарии:
1. Это все еще необходимо для того, чтобы рассчитать угол в 90 градусов, как вы хотели. Что вам нужно сделать, так это сохранить угол между p1 и p2 постоянным, но длину линии можно изменять. Угол p2 и p3 останется статической линией, как вы хотите, как показано в предоставленной вами ссылке. Я так понимаю, вы хотите, чтобы формула ограничивала угол p1 и p2?
2. Попробуйте эту скрипку… jsfiddle.net/9qgfs6k4/6 это то, чего ты добиваешься? Способ перемещения мыши относительно перетаскиваемой линии в настоящее время не идеален из-за того, как он вычисляет положение мыши, и добавленное вами соотношение не помогает. У меня есть некоторый код, который делает это правильно, и я сделаю обновление, как только вы подтвердите, что именно так вы хотели, чтобы это работало.
3.
x1 == x2 || y1 == y2 ? 0 : Math.tan(-radians)
вероятно, неверно для данногоy1 == y2
случая. Это ограничивает перемещения по горизонтали, но должно ограничивать перемещения по вертикали.4. Это потому, что tan(pi / 2) возвращает огромное число для бесконечности, поэтому эти условия существуют и почему вы получаете большой скачок. Хитрость заключается в том, чтобы поменять местами event.clientX с event.clientY, когда x1 == x2, и это в какой-то степени сработает, но на самом деле это не решает проблему точности отслеживания мыши, о которой я упоминал. Я не закончил это, потому что я просто хотел знать, правильно ли я понял, что вам нужно, прежде чем продолжить, и у вас создалось впечатление, что вы решили движение мыши. Я сделаю обновление в ближайшее время.
5. Я обновил его, и теперь он работает иначе, чем то, что я опубликовал ранее. Я удалил большую часть вашего кода как минимум, чтобы вы могли понять, что отличается. Я также добавил визуальные элементы отладки, чтобы получить представление о том, что происходит. Это будет корректно обрабатывать вертикальные и горизонтальные линии.
Ответ №2:
Чтобы получить движение перпендикулярно синей линии, выполните следующие действия:
- Получить вектор ориентации синих линий.
- Поверните его на 90 градусов (перпендикулярно его поверхности).
- Нормализуйте его, например, сделайте его длину равной
1
(назовите ееn
). - Теперь получите вектор движения мыши (
current - start
, вызовите егоm
). - Теперь посчитайте
n * dot(n,m)
. Это ваш вектор движения вn
направлении.
Комментарии:
1. Спасибо, это помогло мне правильно создать градиентную линию. Ответ Джона вроде как включает в себя все, что мне нужно, так что в конце концов я согласился.