JavaFX вращение контрола мышью вокруг своего центра

129
20 июля 2019, 08:40

Собственно, весь вопрос поставлен в заголовке. Однако, при вращении обнаружены некоторые баги. Для начала алгоритм вращения:

  • Вычисляем точку щелчка мышки по контролу
  • Перемещаем мышь и вычисляем вторую точку мышки
  • Строим два вектора из полученных выше точек в центр контрола
  • Применяем скалярное произведение, тем самым получая угол поворота
  • Строим еще один вектор из новых координат мыши в старые
  • Применяем векторное произведение на полученный вектор и первый вектор, тем самым получая направление вращения
  • Поворачиваем (setRotate)
  • Заменяем старые координаты мыши на новые

Вращаю я Pane. Но, это совсем не важно. Артефакты я отобразил на видео ниже. Как видно, при большом радиусе между центром и положением мыши всё хорошо, но чем меньше радиус - тем больше вылезает артефактов. Как побороть? Спасибо.

youtube.com

Как видно, в первом случае вращение отрабатывает правильно, во - втором случае на 270 - 360 градусах происходит убыстрение вращения, а в - третьем случае вращение доходит лишь до ~230 градусов. И напоследок - код:

private double oldX;
private double oldY;
private double a = 0;
// ........
setOnMousePressed(e -> {
        if(e.getButton() == MouseButton.SECONDARY){
            Bounds bbox = localToScene(getBoundsInParent());
            centerX = (bbox.getMinX() + bbox.getWidth()) / 2;
            centerY = (bbox.getMinY() + bbox.getHeight()) / 2;
            oldX = e.getSceneX();
            oldY = e.getSceneY();
        }
    });
    setOnMouseDragged(e -> {
        if(e.getButton() == MouseButton.SECONDARY){
            double _x = e.getSceneX();
            double _y = e.getSceneY();
            // получаем угол между векторами
            Vector2 v1 = new Vector2(oldX - centerX, oldY - centerY);
            Vector2 v2 = new Vector2(_x - centerX, _y - centerY);
            double angle = v1.degreeAngleForVectors((v2));
            // получаем направление поворота
            Vector2 v3 = new Vector2(_x - oldX, _y - oldY);
            double z = v1.cross(v3);
            // меняем градусы в зависимости от направления поворота
            angle = z >= 0 ? angle : -angle;
            oldX = _x;
            oldY = _y;
            a += angle;
            // поворачиваем контрол
            setRotate(a);
        }
    });

Класс вектор:

public class Vector2 extends Vector {
public Vector2(double x, double y){
    super(x, y);
}
public Vector2(double x1, double x2, double y1, double y2){
    super(x2-x1, y2-y1);
}
@Override
public double scalar(Vector v) {
    return x * v.x + y * v.y;
}
@Override
public double length() {
    return Math.abs(Math.sqrt((x * x) + (y * y)));
}
@Override
public double degreeAngleForVectors(Vector v) {
    double scalar = scalar(v);
    double length = length() * v.length();
    double result = scalar / length;
    return (Math.acos(result) * 180.) / Math.PI;
}
@Override
public double cross(Vector v) {
    return x*v.y - v.x * y;
}

}

UPD1: Сделал способом, который посоветовал Serhii Dikobrazko:

public class TestControl extends Pane {
    private double a;
    private double centerX;
    private double centerY;
    private double angleStart;
    private double angleStartPane;
    public TestControl(){
       super();
       a = 0;
       angleStart = 0;
    // events
    setOnMousePressed(e -> {
        if(e.getButton() == MouseButton.SECONDARY){
            Bounds bbox = localToScene(getBoundsInParent());
            centerX = (bbox.getMinX() + bbox.getWidth()) / 2;
            centerY = (bbox.getMinY() + bbox.getHeight()) / 2;
            oldX = e.getSceneX();
            oldY = e.getSceneY();
            angleStart = Math.atan((oldY - centerY) / (oldX - centerX));
            angleStartPane = getRotate();
        }
    });
    setOnMouseDragged(e -> {
        if(e.getButton() == MouseButton.SECONDARY){
            double _x = e.getSceneX();
            double _y = e.getSceneY();
            double angleCurrent = Math.atan((_y - centerY) / (_x - centerX));
            double angleFinal = angleCurrent - angleStart + angleStartPane;
            a += angleFinal;
            System.out.println(a);
            setRotate(a);
        }
    });
   }
}

Результат: youtube.com Как видно, при вращении по часовой вновь ошибки. когда завершается поворот(почти полные 360), а в обратную сторону проблемы ещё более серьёзные. P.S.: JavaFX угол не запоминает, поэтому я его "накапливаю" в поле a.

UPD 2: youtube.com Результат сильно похож на изначальную проблему, если не полностью её повторяет.

Answer 1

Вариант без векторов. Ошибку искать трудно Готовое решение не дам, но алгоритм покажу

Point center; // координаты центра панели в родительском узле
Point cursor; // координаты курсора в родительском узле

Теперь когда правой кнопкой мыши зажимаем и тянем, срабатывает наше событие для поворота.

При первом вызове необходимо запомнить угол наклона отрезка от центра панели, до курсора. Найти угол по двум точкам просто. Не помню точно, вроде бы (x1 - x2)/(y1 - y2). И еще угол поворота самой панели

float cursorStartAngle;  // стартовый 
float paneStartAngle = pane.getRotate();

Он понадобится чтобы потом сделать смещение. Это как бы стартовое значение угла

Теперь при каждом следующем перемещении нужно считать этот же угол и записывать его в другую переменную

float cursorAngle; // угол по направлению к курсору

А теперь панель нужно повернуть на разницу между углом что есть сейчас и углом, что был изначально при срабатывании события драга правой кнопкой мыши float rotateAngle = cursorAngle - cursorStartAngle; Я не знаю как в javaFx поворот работает, но если он не плюсуется, то при каждом присвоении будет сбрасываться в ноль

float rotateAngle = cursorAngle - cursorStartAngle + paneStartAngle;

И уже его присвоить нужной панели.

READ ALSO
Документация по классам java в стиле man

Документация по классам java в стиле man

Есть ли такая утилита как man в Линуксе, чтобы можно было читать мануал по классам(например Thread)

119
Ссылка, размещенная внутри метода(Java)

Ссылка, размещенная внутри метода(Java)

Такая ситуация: существует условный метод play(), в котором помимо основного кода создаётся объект с помощью ссылочной переменнойВопрос заключается...

163
Метод setPaint класса Graphics2d

Метод setPaint класса Graphics2d

Пожалуйста, опишите, для чего нужен метод setPaint() класса Graphics2d и какие он использует аргументы

129
Интересная олимпиадная задача. [закрыт]

Интересная олимпиадная задача. [закрыт]

Есть задачка, довольно простая, я думаюРешение пришло сразу же: Мы ищем максимальный из дней (d-итых), и выводим какой это день недели

122