Поверните движущуюся фигуру вокруг ее центра

Я делаю 2D-игру на Java, в которой игрок ведет многоугольник через препятствия. Многоугольник перемещается вверх и вниз, а игровой мир прокручивается влево и вправо. Мне нужно, чтобы многоугольник вращался вокруг своего центра, но поскольку он постоянно перемещается, точка, которую он вращает, перемещается. Попытка перевести его обратно в исходный центр, повернуть и вернуть обратно не работает. Как мне получить центр формы?

Вот мои расчеты движения с таймером 2 мс:

@Override
public void actionPerformed(ActionEvent e) {

    double theta = angleRad+90;
    if (up == true) {
        if (accelerating == false) {
            time2 = 0;
            moveX0 = moveX;
            moveY0 = moveY;
            accelerating = true;
        }
        time1++;
        double t = time1/500;
        if (accCount % 10 == 0) {
            DronePilot.velocity++;
        }
        moveX = moveX0 + velX*Math.cos(theta)*t;
        moveY = moveY0 + velY*Math.sin(theta)*t-(1/2d)*g*Math.pow(t, 2);
        velX = (DronePilot.velocity)*Math.cos(theta);
        velY = (DronePilot.velocity)*Math.sin(theta)-g*(t);
        accCount++;
    } else if (up == false){
        if (accelerating == true) {
            time1 = 0;
            moveX0 = moveX;
            moveY0 = moveY;
            accelerating = false;
        }
        time2++;
        double t = time2/500;
        moveX = moveX0 + velX*Math.cos(theta)*t;
        moveY = moveY0 + velY*Math.sin(theta)*t-(1/2d)*g*Math.pow(t, 2);
        accCount = 0;
    } if (left == true) {
        angleCount++;
        if (angleCount % 2 == 0) {
            angleDeg++;
        }
        angleRad = Math.toRadians(angleDeg);
    } else if (right == true) {
        angleCount--;
        if (angleCount % 2 == 0) {
            angleDeg--;
        }
        angleRad = Math.toRadians(angleDeg);
    }
    repaint();
}
}

А вот и мой метод paintComponent:

@Override
public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2D = (Graphics2D)g;

    Graphics g2 = g.create();
    Graphics2D copy = (Graphics2D)g2;




    copy.rotate(-angleRad, xPos, yPos);

    copy.translate(0, -moveY);

    g2D.translate(-moveX, 0);

    copy.draw(player.shape);

    for (Rectangle2D.Double r: DronePilot.rocksFloorArray) {
        g2D.draw(r);
    }
    for (Rectangle2D.Double r: DronePilot.rocksCeilArray) {
        g2D.draw(r);
    }
    for (Rectangle2D.Double r: DronePilot.roomsArray) {
        g2D.draw(r);
    }
}

где (xPos, yPos) - центр экрана.


person Denis    schedule 04.12.2017    source источник
comment
Что ж, вместо перевода контекста Graphics используйте AffineTransform. Я предполагаю, что вы хотите сначала перевести положение, это значительно упростит поворот объекта относительно его центрального положения.   -  person MadProgrammer    schedule 04.12.2017
comment
Вы также, кажется, переводите как g2D И copy, что может привести к разного рода интересным (но нежелательным) проблемам.   -  person MadProgrammer    schedule 04.12.2017


Ответы (2)


Преобразования (обычно) складываются

public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2D = (Graphics2D)g;

    Graphics g2 = g.create();
    Graphics2D copy = (Graphics2D)g2;

    copy.rotate(-angleRad, xPos, yPos);
    copy.translate(0, -moveY);

    g2D.translate(-moveX, 0);

    copy.draw(player.shape);
    for (Rectangle2D.Double r: DronePilot.rocksFloorArray) {
        g2D.draw(r);
    }
    for (Rectangle2D.Double r: DronePilot.rocksCeilArray) {
        g2D.draw(r);
    }
    for (Rectangle2D.Double r: DronePilot.roomsArray) {
        g2D.draw(r);
    }
}

В приведенном выше коде вы переводите как исходный контекст Graphics, так и copy. В этом контексте copy не будет затронут оригиналом, и оригинал не будет затронут copy, НО исходный контекст является общим ресурсом, и, поскольку вы не сбрасываете перевод, вы по-прежнему будете получать переводил контекст каждый раз (сложение).

Как правило, делайте ВСЕ преобразования на копии и избавляйтесь от нее, когда закончите.

Например...

Graphics2D g2d = (Graphics2D)g.create();
AffineTransform at = AffineTransform.getTranslateInstance(playerPoint.x, playerPoint.y);
at.rotate(Math.toRadians(angle), player.getBounds2D().getCenterX(), player.getBounds2D().getCenterY());
g2d.setTransform(at);
g2d.setColor(Color.RED);
g2d.fill(player);
g2d.setColor(Color.BLACK);
g2d.draw(player);
g2d.dispose();

Это в основном переводит положение объекта в положение игрока, а затем вращает объект вокруг центра объекта.

Вы также можете применить одно преобразование, создать копию этого контекста и применить другое преобразование, которое станет составным (так что вы можете translate один контекст, скопировать его, а затем rotate копия и первый перевод все равно будут применены к копии )

Этот невероятно простой пример демонстрирует два основных примера ...

  1. Использование контекста Graphics и AffineTransform для перемещения и поворота объекта игрока (относительно его центральной точки)
  2. Использование Path2D для создания преобразованной формы (в этом примере создаются два объекта, но вы можете использовать один AffineTransform, переведенный и повернутый, и применить его один раз).

В обоих случаях они не влияют на исходную форму.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private Shape player;
        private Point playerPoint;
        private float angle;
        private float deltaZ = 1.0f;

        private int deltaX, deltaY;

        public TestPane() {
            player = new Rectangle(0, 0, 20, 20);
            playerPoint = new Point(80, 80);
            Random rnd = new Random();
            deltaX = 1;
            deltaY = -1;

            Timer timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    playerPoint.x += deltaX;
                    playerPoint.y += deltaY;

                    Shape rotatedPlayer = rotatedAndTranslatedPlayer();
                    Rectangle2D bounds = rotatedPlayer.getBounds2D();
                    if (bounds.getX() < 0.0) {
                        playerPoint.x = (int)(bounds.getX() * -1);
                        deltaX *= -1;
                    } else if (bounds.getX() + bounds.getWidth() >= getWidth()) {
                        playerPoint.x = getWidth() - (int)bounds.getWidth();
                        deltaX *= -1;
                    }
                    if (bounds.getY() < 0) {
                        playerPoint.y = 0;
                        deltaY *= -1;
                    } else if (bounds.getY() + bounds.getHeight() > getHeight()) {
                        playerPoint.y = getHeight() - (int)bounds.getHeight();
                        deltaY *= -1;
                    }
                    angle += deltaZ;
                    repaint();
                }
            });
            timer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        protected Shape rotatedAndTranslatedPlayer() {
            Path2D.Double rotated = new Path2D.Double(player,  AffineTransform.getRotateInstance(
                    Math.toRadians(angle),
                    player.getBounds2D().getCenterX(), 
                    player.getBounds2D().getCenterY()));
            return new Path2D.Double(rotated, AffineTransform.getTranslateInstance(playerPoint.x, playerPoint.y));            
        }

        // Simply paints the "area" that the player takes up when it's rotated and
        // translated
        protected void paintAutoTranslatedShape(Graphics2D g2d) {
            g2d.setColor(Color.DARK_GRAY);
            g2d.fill(rotatedAndTranslatedPlayer().getBounds2D());
        }

        // Uses a AffineTransform to translate and rotate the player
        protected void paintPlayer(Graphics2D g2d) {
            AffineTransform at = AffineTransform.getTranslateInstance(playerPoint.x, playerPoint.y);
            at.rotate(Math.toRadians(angle), player.getBounds2D().getCenterX(), player.getBounds2D().getCenterY());
            g2d.setTransform(at);
            g2d.setColor(Color.RED);
            g2d.fill(player);
            g2d.setColor(Color.BLACK);
            g2d.draw(player);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            paintAutoTranslatedShape(g2d);
            g2d.dispose();
            g2d = (Graphics2D) g.create();
            paintPlayer(g2d);
            g2d.dispose();
        }

    }
}
person MadProgrammer    schedule 04.12.2017
comment
Последующий вопрос: в чем разница между генерацией и рисованием Path2D, созданием и рисованием createTransformedShape и простым применением AffineTransform к графическому контексту и его рисованием? Все это дало мне разные результаты при тестировании. - person Denis; 06.12.2017
comment
Преобразование формы позволяет вам: 1: создать копию формы, преобразованную на основе AffineTransform, это не повлияет ни на что другое, это также не повлияет на исходную форму, что приятно; 2: Это не влияет на Graphics контекст, который, как вы обнаружили, может вызвать проблемы, если вы не будете осторожны; 3: Вы можете принимать решения о преобразованной форме без Graphics контекста, так как я уже проверил границы; Обратной стороной является то, что вы создаете другой объект, и если вы делаете много быстрых изменений, это может повлиять на производительность, так как GC должен сделать больше. - person MadProgrammer; 07.12.2017

В openGL мы сохраняем состояние преобразования, используя pushMatrix() и popMatrix(). Здесь их эквиваленты Graphics.create() и Graphics.dispose()

Graphics g1 = g.create();
//do transformations
g1.dispose();

Graphics g2 = g.create();
//do other stuff
g2.dispose();
person Vj-    schedule 04.12.2017