仿射变换与多边形点的关系

Relationship Between AffineTransformation and Polygon's Point

作为一个项目,我正在尝试创建 Asteroids 游戏的模拟。目前,我正在努力做到这一点,如果玩家将自己送出界外,宇宙飞船就会出现在 GUI 的另一侧。

但是,我对 AffineTransformation 的旋转和 createTransformedShape 函数如何与多边形的 x 点和 y 点交互感到很困惑。

目前,我的飞船正在运行;它以用户指定的角度飞行,所有涉及的运动都正常。但是,当我试图获取宇宙飞船的最低 X 坐标以比较它是否大于常量 WIDTH 时,它 returns 780。无论我是否在地图的一侧到另一侧,它总是 returns 780。我觉得这很奇怪,因为它不应该 return 当前所在位置的最小 X 坐标吗? Here is a screenshot of the console displaying that the "smallest x coordinate" of the polygon is 780, despite not being at 780

谁能给我解释一下为什么多边形的 X 坐标没有变化?我有 2 classes。一个是 driver classes,另一个是扩展 Polygon.class 的船 class。

public class AsteroidGame implements ActionListener, KeyListener{

    public static AsteroidGame game;
    public Renderer renderer;

    public boolean keyDown = false;
    public int playerAngle = 0;

    public boolean left = false;
    public boolean right = false;
    public boolean go = false;
    public boolean back = false;
    public boolean still = true;
    public double angle = 0;
    public int turnRight = 5;
    public int turnLeft = -5;

    public Shape transformed;
    public Shape transformedLine;

    public Point p1;
    public Point p2;
    public Point center;
    public Point p4;

    public final int WIDTH = 1600;
    public final int HEIGHT = 800;

    public Ship ship;


    public AffineTransform transform = new AffineTransform();

    public AsteroidGame(){
        JFrame jframe = new JFrame();
        Timer timer = new Timer(20, this);
        renderer = new Renderer();

        jframe.add(renderer);
        jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jframe.setSize(WIDTH, HEIGHT);
        jframe.setVisible(true);
        jframe.addKeyListener(this);
        jframe.setResizable(false);

        int xPoints[] = {800, 780, 800, 820};
        int yPoints[] = {400, 460, 440, 460}; 

        p1 = new Point(400,400);
        p2 = new Point(380, 460);
        center = new Point(400,440);//center
        p4 = new Point(420, 460);

        ship = new Ship(xPoints, yPoints, 4, 0);
        transformed = transform.createTransformedShape(ship);

        timer.start();

    }

    public void repaint(Graphics g){

        g.setColor(Color.BLACK);
        g.fillRect(0, 0, WIDTH, HEIGHT);



        Graphics2D g2d = (Graphics2D)g;

        g2d.setColor(Color.WHITE);
        g2d.draw(transformed);


        /*
        g2d.draw(r2);
        Path2D.Double path = new Path2D.Double();
        path.append(r, false);
        AffineTransform t = new AffineTransform();
        t.rotate(Math.toRadians(45));
        path.transform(t);
        g2d.draw(path);


        Rectangle test = new Rectangle(WIDTH/2, HEIGHT/2, 200, 100);
        Rectangle test2 = new Rectangle(WIDTH/2, HEIGHT/2, 200, 100);

        g2d.draw(test2);
        AffineTransform at = AffineTransform.getTranslateInstance(100, 100);
        g2d.rotate(Math.toRadians(45));
        g2d.draw(test);
        */




    }



    public void actionPerformed(ActionEvent arg0) {
        // TODO Auto-generated method stub
        if (right){
            ship.right();

            transform.rotate(Math.toRadians(turnRight), ship.getCenterX(), ship.getCenterY());
            System.out.println(ship.getCenterY());

        }

        else if (left){
            ship.left(); 

            transform.rotate(Math.toRadians(turnLeft), ship.getCenterX(), ship.getCenterY());

        }
        if (go){
            ship.go();

            //ship.x += Math.sin(Math.toRadians(angle)) * 5;
            //ship.y
            /*
            ship.x += (int) Math.sin(Math.toRadians(angle));
            ship.y += (int) Math.cos(Math.toRadians(angle));
            */
            //System.out.println(Math.sin(Math.toRadians(ship.angle)) * 5 + "y" + Math.cos(Math.toRadians(ship.angle)) * 5);

        }
        else if (back){
            ship.reverse();
        }
        ship.move();
        //ship.decrement();

        transformed = transform.createTransformedShape(ship);

        if (ship.smallestX() >= WIDTH){
            System.out.println("out");
        }
        renderer.repaint();
        System.out.println("Smallest x coordinate: " + ship.smallestX());

    }

    public static void main(String[] args){
        game = new AsteroidGame();
    }

    @Override
    public void keyPressed(KeyEvent e) {
        // TODO Auto-generated method stub
        if (e.getKeyCode() == KeyEvent.VK_RIGHT){
            System.out.println("I am down");
            right = true;
            keyDown = true;
        }else if (e.getKeyCode() == KeyEvent.VK_LEFT){
            left = true;
            System.out.println("I am down");
            keyDown = true;
        }

        if (e.getKeyCode() == KeyEvent.VK_UP){
            go = true;
        }
        else if (e.getKeyCode() == KeyEvent.VK_DOWN){
            back = true;
        }


    }

    @Override
    public void keyReleased(KeyEvent e) {
        // TODO Auto-generated method stub
        if (e.getKeyCode() == KeyEvent.VK_RIGHT){
            right = false;
        }
        if (e.getKeyCode() == KeyEvent.VK_LEFT){
            left = false;
        }
        if (e.getKeyCode() == KeyEvent.VK_UP){
            go = false;
        }
        if (e.getKeyCode() == KeyEvent.VK_DOWN){
            back = false;
        }
        still = true;
        keyDown = false;
        System.out.println("up");
    }

    @Override
    public void keyTyped(KeyEvent e) {
        // TODO Auto-generated method stub

    }

船Class:

public class Ship extends Polygon{

    /**
     * 
     */
    private double currSpeed = 0;

    private static final long serialVersionUID = 1L;
    public double angle;

    public Ship(int[] x, int[] y, int points, double angle){
        super(x, y, points);
        this.angle= angle;
    }

    public void right(){
        angle += 5;
    }
    public void left(){
        angle -= 5;
    }

    public void move(){
        for (int i = 0; i < super.ypoints.length; i++){
            super.ypoints[i] -= currSpeed;
            //System.out.println(super.ypoints[i]);
            //System.out.println(super.xpoints[i]);
        }
    }


    public void reverse(){
        if (currSpeed  > -15) currSpeed -= 0.2;
    }

    public void go(){
        if (currSpeed < 25) currSpeed += 0.5;

    }
    public int smallestX(){
        int min = super.xpoints[0];
        for (int i = 0; i < super.xpoints.length; i++){
            if (min > super.xpoints[i]){
                min = super.xpoints[i];
            }
        }
        return min;
    }
    public int smallestY(){
        int min = super.ypoints[0];
        for (int i = 0; i < super.ypoints.length; i++){
            if (min < super.ypoints[i]){
                min = super.ypoints[i];
            }
        }
        return min;
    }

    public int getCenterX(){
        return super.xpoints[2];
    }
    public int getCenterY(){
        return super.ypoints[2];
    }

    public double getAng(){
        return angle;
    }

为什么它不起作用

仿射变换实际上并没有影响飞船实例,而是创建了一个全新的实例,这就是为什么您的方法似乎不起作用的原因。为了证明这一点,我有以下代码:

int[] xPoints = {800, 780, 800, 820};
int[] yPoints = {400, 460, 440, 460}; 

Ship ship = new Ship(xPoints, yPoints, 4, 0);
System.out.println("old points:");
System.out.println(Arrays.toString(ship.xpoints));
System.out.println(Arrays.toString(ship.ypoints));
AffineTransform transform = new AffineTransform();
transform.translate(20, 20);

Shape transformed = transform.createTransformedShape(ship);
System.out.println("new points (unchanged):");
System.out.println(Arrays.toString(ship.xpoints));
System.out.println(Arrays.toString(ship.ypoints));

两次,点数相同

解决方案

我建议您不要将点视为屏幕上的实际点,而应将它们视为模型,这也会使物理部分更容易。我们将以 (0,0) 为中心点。当我们需要渲染它时,将它转换到正确的位置和旋转。 这些点将是这样的:

int[] xPoints = {0, -20, 0, 20};
int[] yPoints = {-40, 20, 0, 20}; 

所以现在,您在移动飞船时不需要编辑所有的点,只需编辑旋转和中心。注意要先平移,再旋转。

这些点在 (0,0) 附近这一事实很重要,因为这是旋转的中心。这可能就是为什么你现在似乎可以移动,你的旋转不是以船的中心为中心的。并且当你旋转向上移动时,你也向右移动了一点。

这解决了我们的问题,因为现在你不需要再看所有的点了,你可以只看船的中心。如果中心超出范围,则将其移至另一侧。

导致下面的简单代码(我在这里使用 300 因为这是我示例中的宽度和高度,请在实际代码中将其更改为您的宽度和高度)

if(center_x > 300)
    center_x = 0;
if(center_x < 0)
    center_x = 300;
if(center_y > 300)
    center_y = 0;
if(center_y < 0)
    center_y = 300;

这会在需要时将飞船带到屏幕的另一边。但是,我们仍然需要显示一部分船,而另一部分在另一侧。出于这个原因,我们可以渲染这艘船两次。我希望下面的代码不言自明。

Rectangle2D box = transformed.getBounds2D();
//wrap in x direction
if(box.getX() + box.getWidth() > 300){
    AffineTransform transform2 = new AffineTransform();
    transform2.translate(ship.center_x - 300, ship.center_y);
    transform2.rotate(Math.toRadians(ship.angle));
    Shape transformed2 = transform2.createTransformedShape(ship);
    g2.draw(transformed2);
}else if(box.getX() < 0){
    AffineTransform transform2 = new AffineTransform();
    transform2.translate(ship.center_x + 300, ship.center_y);
    transform2.rotate(Math.toRadians(ship.angle));
    Shape transformed2 = transform2.createTransformedShape(ship);
    g2.draw(transformed2);
}

//wrap in y direction
if(box.getY() + box.getHeight() > 300){
    AffineTransform transform2 = new AffineTransform();
    transform2.translate(ship.center_x, ship.center_y - 300);
    transform2.rotate(Math.toRadians(ship.angle));
    Shape transformed2 = transform2.createTransformedShape(ship);
    g2.draw(transformed2);
}else if(box.getY() < 0){
    AffineTransform transform2 = new AffineTransform();
    transform2.translate(ship.center_x, ship.center_y + 300);
    transform2.rotate(Math.toRadians(ship.angle));
    Shape transformed2 = transform2.createTransformedShape(ship);
    g2.draw(transformed2);
}

物理学

您还要求提供有关实际物理学的更多信息,我只会给您公式(如果您需要更多帮助,请告诉我)。这里,theta 是以弧度为单位的船的角度,如果 theta 为 0,则船指向上。 x轴指向右侧,y轴指向上方

a_x = Math.sin(theta)*a;
a_y = -Math.cos(theta)*a;
v_x_new = v_x_old + a_x*timeDiff;
v_y_new = v_y_old + a_y*timeDiff;
x_new = x_old + v_x_old*timeDiff + a_x*timeDiff*timeDiff/2;
y_new = y_old + v_y_old*timeDiff + a_y*timeDiff*timeDiff/2;

x_old = x_new;
y_old = y_new;
v_x_old = v_x_new;
v_y_old = v_y_new;

最终结果

我已经实现了飞船 class 的外观,还测试了渲染方法是否有效。

public class AsteroidsTest {

    public static void main(String[] args){
        int[] xPoints = {0, -20, 0, 20};
        int[] yPoints = {-40, 20, 0, 20};
        Ship ship = new Ship(xPoints, yPoints, 4, 0);
        ship.center_x = 100;
        ship.center_y = 100;
        ship.angle = 45;
        ship.speed_x = 30;
        ship.speed_y = -30;

        System.out.println("running...");

        JFrame window = new JFrame();
        window.setBounds(30, 30, 300, 300);
        window.getContentPane().add(new MyCanvas(ship));
        window.setVisible(true);
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        long time1 = System.currentTimeMillis();
        while(true){
            long time2 = System.currentTimeMillis();
            double timeDiff = (time2-time1)/1000f;
            time1 = time2;
            ship.move(timeDiff);
            window.getContentPane().repaint();
        }

    }
}

class MyCanvas extends JComponent {

    private Ship ship;

    public MyCanvas(Ship ship){
        this.ship = ship;
    }

    public void paint(Graphics g) {
        Graphics2D g2 = (Graphics2D)g;
        AffineTransform transform = new AffineTransform();
        transform.translate(ship.center_x, ship.center_y);
        transform.rotate(Math.toRadians(ship.angle));
        Shape transformed = transform.createTransformedShape(ship);
        g2.draw(transformed);

        Rectangle2D box = transformed.getBounds2D();
        //wrap in x direction
        if(box.getX() + box.getWidth() > 300){
            AffineTransform transform2 = new AffineTransform();
            transform2.translate(ship.center_x - 300, ship.center_y);
            transform2.rotate(Math.toRadians(ship.angle));
            Shape transformed2 = transform2.createTransformedShape(ship);
            g2.draw(transformed2);
        }else if(box.getX() < 0){
            AffineTransform transform2 = new AffineTransform();
            transform2.translate(ship.center_x + 300, ship.center_y);
            transform2.rotate(Math.toRadians(ship.angle));
            Shape transformed2 = transform2.createTransformedShape(ship);
            g2.draw(transformed2);
        }

        //wrap in y direction
        if(box.getY() + box.getHeight() > 300){
            AffineTransform transform2 = new AffineTransform();
            transform2.translate(ship.center_x, ship.center_y - 300);
            transform2.rotate(Math.toRadians(ship.angle));
            Shape transformed2 = transform2.createTransformedShape(ship);
            g2.draw(transformed2);
        }else if(box.getY() < 0){
            AffineTransform transform2 = new AffineTransform();
            transform2.translate(ship.center_x, ship.center_y + 300);
            transform2.rotate(Math.toRadians(ship.angle));
            Shape transformed2 = transform2.createTransformedShape(ship);
            g2.draw(transformed2);
        }


    }
}

飞船class:

import java.awt.Polygon;

public class Ship extends Polygon{
    private static final long serialVersionUID = 1L;

    public double center_x = 0;
    public double center_y = 0;

    public double speed_x = 0;
    public double speed_y = 0;

    //the angle of the ship
    public double angle = 0;
    //the acceleration of the ship in the direction defined by angle
    public double acceleration = 0;

    public Ship(int[] x, int[] y, int points, double angle){
        super(x, y, points);
        this.angle= angle;
    }

    public void right(){
        angle += 5;
    }

    public void left(){
        angle -= 5;
    }

    public void move(double timeDiff){
        double a_x = Math.sin(Math.toRadians(angle))*acceleration;
        double a_y = -Math.cos(Math.toRadians(angle))*acceleration;
        center_x = center_x + speed_x*timeDiff + a_x*timeDiff*timeDiff/2;
        center_y = center_y + speed_y*timeDiff + a_y*timeDiff*timeDiff/2;
        speed_x = speed_x + a_x*timeDiff;
        speed_y = speed_y + a_y*timeDiff;

        if(center_x > 300)
            center_x = 0;
        if(center_x < 0)
            center_x = 300;
        if(center_y > 300)
            center_y = 0;
        if(center_y < 0)
            center_y = 300;
    }


    public void reverse(){
        acceleration = -1;
    }

    public void go(){
        acceleration = 1;
    }

    public void stop(){
        acceleration = 0;
    }

    public int getCenterX(){
        return (int) Math.round(center_x);
    }
    public int getCenterY(){
        return (int) Math.round(center_y);
    }

    public double getAng(){
        return angle;
    }

}