Java 如何';获取';从仿射变换派生的形状对象的特定点

Java 如何';获取';从仿射变换派生的形状对象的特定点,java,shape,affinetransform,Java,Shape,Affinetransform,作为一个自我项目,我正在努力使游戏“小行星” 目前,我一直在试图找出如何使我的飞船发射的激光出现在飞船的顶端。到目前为止,我已经尝试使用形状对象的.getBounds2D().getX()方法进行实验,但是由于getBounds2D()在多边形周围绘制了一个矩形,激光最终从我的多边形周围的假想“盒子”的一角出现ship 是否有一种从形状对象“获取”特定点的方法;其中,在这种情况下,该特定点是船的顶端 主要类别: public class AsteroidGame implements Acti

作为一个自我项目,我正在努力使游戏“小行星”

目前,我一直在试图找出如何使我的飞船发射的激光出现在飞船的顶端。到目前为止,我已经尝试使用
形状
对象的
.getBounds2D().getX()
方法进行实验,但是由于
getBounds2D()
在多边形周围绘制了一个矩形,激光最终从我的多边形周围的假想“盒子”的一角出现
ship

是否有一种从形状对象“获取”特定点的方法;其中,在这种情况下,该特定点是船的顶端

主要类别:

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 ArrayList<Laser> lasers;
    public ArrayList<Shape> transformedLasers;

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

    public Ship ship;
    public Rectangle shipHead;
    public Shape shipHeadTrans;
    public Point headPoint;


    public AffineTransform transform = new AffineTransform();
    public AffineTransform lasTransform = new AffineTransform();
    public AffineTransform headTransform = 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}; 

        //(800, 400) is the initial location of the 'tip' of the ship'.
        headPoint = new Point(800, 400);

        lasers = new ArrayList<Laser>();
        transformedLasers = new ArrayList<Shape>();

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

        shipHead = new Rectangle(headPoint);
        shipHeadTrans = transform.createTransformedShape(shipHead);
        //shipHeadTrans.getBounds2D().

        timer.start();

    }

    public void repaint(Graphics g){

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

        Graphics2D g2d = (Graphics2D)g;

        //drawing the ship
        g2d.setColor(Color.WHITE);
        g2d.draw(transformed);

        //drawing lasers
        g2d.setColor(Color.RED);
        for (int i = 0; i < transformedLasers.size(); i++){
            System.out.println(i);
            g2d.draw(transformedLasers.get(i));
        }



    }



    public void actionPerformed(ActionEvent arg0) {
        // TODO Auto-generated method stub

        /*The for if and else if statements are just to send the ship
         * to the other side of the canvas if it ever leaves the screen
         */
        if (transformed.getBounds2D().getMinX() > WIDTH){
            double tempAng = ship.getAng();
            double diff = 90-tempAng;

            transform.rotate(Math.toRadians(diff), ship.getCenterX(), ship.getCenterY());
            transform.translate(0,WIDTH);
            transform.rotate(Math.toRadians(-diff), ship.getCenterX(), ship.getCenterY());

        }

        else if (transformed.getBounds2D().getX() < 0){
            double tempAng = ship.getAng();
            double diff = 90-tempAng;
            transform.rotate(Math.toRadians(diff), ship.getCenterX(), ship.getCenterY());
            transform.translate(0,-WIDTH);
            transform.rotate(Math.toRadians(-diff), ship.getCenterX(), ship.getCenterY());
        }

        else if (transformed.getBounds2D().getY() > HEIGHT){
            double tempAng = ship.getAng();
            double diff = 180-tempAng;
            transform.rotate(Math.toRadians(diff), ship.getCenterX(), ship.getCenterY());
            transform.translate(0,HEIGHT);
            transform.rotate(Math.toRadians(-diff), ship.getCenterX(), ship.getCenterY());
        }

        else if (transformed.getBounds2D().getY() < 0){
            double tempAng = ship.getAng();
            double diff = 180-tempAng;
            transform.rotate(Math.toRadians(diff), ship.getCenterX(), ship.getCenterY());
            transform.translate(0,-HEIGHT);
            transform.rotate(Math.toRadians(-diff), ship.getCenterX(), ship.getCenterY());
        }


        if (right){
            ship.right();
            //rotating the ship
            transform.rotate(Math.toRadians(turnRight), ship.getCenterX(), ship.getCenterY());
            //rotating the 'tip' of the ship.
            headTransform.rotate(Math.toRadians(turnRight), ship.getCenterX(), ship.getCenterY());
        }

        else if (left){
            ship.left(); 
            //rotating the ship
            transform.rotate(Math.toRadians(turnLeft), ship.getCenterX(), ship.getCenterY());
            //rotating the 'tip' of the ship
            headTransform.rotate(Math.toRadians(turnLeft), ship.getCenterX(), ship.getCenterY());
        }
        if (go){
            ship.go();
        }
        else if (back){
            ship.reverse();
        }

        //moving and shaping each individual laser that had been shot
        for (int i = 0; i < transformedLasers.size(); i++){
            lasers.get(i).move();

            lasTransform = new AffineTransform();
            lasTransform.rotate(Math.toRadians(lasers.get(i).getAng()), transformed.getBounds2D().getX(), transformed.getBounds2D().getY());
            transformedLasers.set(i, lasTransform.createTransformedShape(lasers.get(i)));

        }

        //moving the ship
        ship.move();

        //moving the 'tip'
        shipHead.y -= ship.getSpeed();

        transformed = transform.createTransformedShape(ship);
        shipHeadTrans = headTransform.createTransformedShape(shipHead);


        renderer.repaint();

    }

    //defining a new laser
    public void fireLaser(){
        Laser tempLaser = new Laser((int)transformed.getBounds2D().getX(), (int)transformed.getBounds2D().getY(), 5, 10, ship.getAng());
        lasers.add(tempLaser);

        lasTransform = new AffineTransform();
        lasTransform.rotate(Math.toRadians(ship.getAng()), transformed.getBounds2D().getX(), transformed.getBounds2D().getY());
        transformedLasers.add(lasTransform.createTransformedShape(tempLaser));

    }

    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){
            right = true;
            keyDown = true;
        }else if (e.getKeyCode() == KeyEvent.VK_LEFT){
            left = true;
            keyDown = true;
        }

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

        //fire laser
        if (e.getKeyCode() == KeyEvent.VK_SPACE){
            fireLaser();
        }

    }

    @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;
    }

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

    }
我在考虑将变形对象
变换后的
转换回多边形以获得点,但我不确定如何或是否可行。

您可以使用它来变换多边形上的单个点

如果您不使用旋转变换来移动船舶,而是保留船舶所在位置的单个
(x,y)
,事情会简单得多。您可以在
move()
中移动船舶的位置,而不是尝试平移多边形。然后,当你想给船上漆时,例如:

// Optionally copying the Graphics so the
// transform doesn't affect later painting.
Graphics2D temp = (Graphics2D) g2d.create();
temp.translate(ship.locX, ship.locY);
temp.rotate(ship.angle);
temp.draw(ship);
要基于速度移动点,可以执行以下操作以查找移动向量:

double velX = speed * Math.cos(angle);
double velY = speed * Math.sin(angle);
locX += timeElapsed * velX;
locY += timeElapsed * velY;
这本质上是从极坐标到笛卡尔坐标的转换。x和y速度是斜边为
速度
且已知角度为
角度
的三角形的腿:

             /|
            / |
           /  |
          /   |
   speed /    |
        /     |
       /      |velY
      / angle |
     /)_______|
         velX
在我的回答中有一个这样做的例子:


请注意:


你是说,与我的初始移动函数不同,只是为了让
ship
保持一个点,因此我只会转换它

或多或少,是的。您仍然有一个多边形来保持船的形状,但是多边形上的点相对于
(0,0)

假设以下定义:

  • 多边形上的每个
    (x,y)
    点都是
    pi
    。(换句话说,
    p0
    p1
    p2
    p3
    中的一个)
  • 平移的
    (x,y)
    坐标为
    T
然后,在翻译
图形2d
后,每个
pi
坐标变为面板上的
pi+T
。因此,如果多边形点是相对于
(0,0)
定义的,则转换到船舶的
(locX,locY)
将多边形移动到相对于
(locX,locY)
的位置

最简单的方法是将多边形顶端的点定义为
(0,0)
,这样在平移后,船的顶端就是船的位置:

// Your original points:
int xPoints[] = {800, 780, 800, 820};
int yPoints[] = {400, 460, 440, 460}; 
// Become these points relative to (0,0):
int xPoints[] = {0, -20, 0, 20};
int yPoints[] = {0, 60, 40, 60};
例如,要在同一位置启动船舶,您需要将其位置初始化为
(800400)


我又想了一遍,意识到旋转有点复杂,因为你可能不想绕着顶端旋转飞船。您可能希望围绕其中心旋转船舶

这里有一个MCVE演示如何完成所有这些


您仍然可以使用变换以这种方式进行计算,但依赖变换进行运动不会产生任何奇怪的结果。(在问题的代码中,例如,船舶仅沿y轴移动。明显的侧向移动是由于一系列旋转连接。)

你是说,与我的初始移动功能不同,只是为了使船舶保持一个点,因此我只转换该点吗,我只是想知道如何才能找回飞船的“尖端”——我不一定需要翻译它。在画布上移动和转换的实例是变形的形状
,但是没有任何方法可以让我获得船梢的特定坐标。一旦我得到了“提示”,我就可以改变激光发射的位置和方式的坐标。我意识到旋转有点复杂,所以我添加了一个MCVE,希望能有所帮助。好吧,我最终给出了你最初的建议/答案,效果很好,再次感谢。如果你想看看我最后做了什么,这里是我的github的链接:没问题!很乐意帮忙。
             /|
            / |
           /  |
          /   |
   speed /    |
        /     |
       /      |velY
      / angle |
     /)_______|
         velX
// Your original points:
int xPoints[] = {800, 780, 800, 820};
int yPoints[] = {400, 460, 440, 460}; 
// Become these points relative to (0,0):
int xPoints[] = {0, -20, 0, 20};
int yPoints[] = {0, 60, 40, 60};
package mcve.game;

import javax.swing.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.GraphicsConfiguration;
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;

public class MovementExample implements ActionListener {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(MovementExample::new);
    }

    final int fps    = 60;
    final int period = 1000 / fps;

    final JFrame    frame;
    final GamePanel panel;
    final Controls  controls;
    final Ship      ship;

    final List<Bullet> bullets = new ArrayList<>();

    MovementExample() {
        frame = new JFrame("Movement Example");

        Dimension size = getMaximumWindowSize(frame);
        size.width  /= 2;
        size.height /= 2;
        frame.setPreferredSize(size);

        panel = new GamePanel();
        frame.setContentPane(panel);

        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

        controls = new Controls();

        ship = new Ship(panel.getWidth()  / 2,
                        panel.getHeight() / 2);

        new Timer(period, this).start();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        double secondsElapsed = 1.0 / fps;
        ship.update(secondsElapsed);

        bullets.forEach(b -> b.update(secondsElapsed));
        Rectangle bounds = panel.getBounds();
        bullets.removeIf(b -> !bounds.contains(b.locX, b.locY));

        panel.repaint();
    }

    class GamePanel extends JPanel {
        GamePanel() {
            setBackground(Color.WHITE);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g.create();
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                RenderingHints.VALUE_ANTIALIAS_ON);

            if (ship != null) {
                ship.draw(g2);
            }
            bullets.forEach(b -> b.draw(g2));

            g2.dispose();
        }
    }

    abstract class AbstractGameObject {
        double maxSpeed;
        double rotationAngle;
        double locX;
        double locY;
        double velX;
        double velY;

        AbstractGameObject(double initialX, double initialY) {
            locX = initialX;
            locY = initialY;
        }

        abstract void update(double secondsElapsed);
        abstract void draw(Graphics2D g2);
    }

    class Ship extends AbstractGameObject {
        Polygon shape;
        double  rotationRate;

        Ship(double initialX, double initialY) {
            super(initialX, initialY);
            maxSpeed      = 128; // pixels/second
            rotationAngle = Math.PI * 3 / 2;
            rotationRate  = (2 * Math.PI) / 2; // radians/second

            int xPoints[] = {0, -20, 0, 20};
            int yPoints[] = {0, 60, 40, 60};
            shape = new Polygon(xPoints, yPoints, 4);
        }

        Point2D.Double getTip() {
            Point2D.Double center = getCenter();
            // The tip is at (0,0) and it's already centered
            // on the x-axis origin, so the distance from the
            // tip to the center is just center.y.
            double distance = center.y;
            // Then find the location of the tip, relative
            // to the center.
            double tipX = distance * Math.cos(rotationAngle);
            double tipY = distance * Math.sin(rotationAngle);
            // Now find the actual location of the center.
            center.x += locX;
            center.y += locY;
            // And return the actual location of the tip, relative
            // to the actual location of the center.
            return new Point2D.Double(tipX + center.x, tipY + center.y);
        }

        Point2D.Double getCenter() {
            // Returns the center point of the ship,
            // relative to (0,0).
            Point2D.Double center = new Point2D.Double();
            for (int i = 0; i < shape.npoints; ++i) {
                center.x += shape.xpoints[i];
                center.y += shape.ypoints[i];
            }
            center.x /= shape.npoints;
            center.y /= shape.npoints;
            return center;
        }

        @Override
        void update(double secondsElapsed) {
            // See my answer here: https://stackoverflow.com/a/43692434/2891664
            // for a discussion of why this logic is the way it is.
            double speed = 0;
            if (controls.isUpHeld()) {
                speed += maxSpeed;
            }
            if (controls.isDownHeld()) {
                speed -= maxSpeed;
            }
            velX  = speed * Math.cos(rotationAngle);
            velY  = speed * Math.sin(rotationAngle);
            locX += secondsElapsed * velX;
            locY += secondsElapsed * velY;

            double rotation = 0;
            if (controls.isLeftHeld()) {
                rotation -= rotationRate;
            }
            if (controls.isRightHeld()) {
                rotation += rotationRate;
            }
            rotationAngle += secondsElapsed * rotation;
            // Cap the angle so it can never e.g. get so
            // large that it loses precision.
            if (rotationAngle > 2 * Math.PI) {
                rotationAngle -= 2 * Math.PI;
            }

            if (controls.isFireHeld()) {
                Point2D.Double tipLoc = getTip();
                Bullet bullet = new Bullet(tipLoc.x, tipLoc.y, rotationAngle);
                bullets.add(bullet);
            }
        }

        @Override
        void draw(Graphics2D g2) {
            Graphics2D copy = (Graphics2D) g2.create();
            copy.setColor(Color.RED);

            // Translate to the ship's location.
            copy.translate(locX, locY);
            // Rotate the ship around its center.
            Point2D.Double center = getCenter();
            // The PI/2 offset is necessary because the
            // polygon points are defined with the ship
            // already vertical, i.e. at an angle of -PI/2.
            copy.rotate(rotationAngle + (Math.PI / 2), center.x, center.y);

            copy.fill(shape);
        }
    }

    class Bullet extends AbstractGameObject {
        Ellipse2D.Double shape = new Ellipse2D.Double();

        Bullet(double initialX, double initialY, double initialRotation) {
            super(initialX, initialY);
            maxSpeed      = 512;
            rotationAngle = initialRotation;
            velX          = maxSpeed * Math.cos(rotationAngle);
            velY          = maxSpeed * Math.sin(rotationAngle);

            double radius = 3;
            shape.setFrame(-radius, -radius, 2 * radius, 2 * radius);
        }

        @Override
        void update(double secondsElapsed) {
            locX += secondsElapsed * velX;
            locY += secondsElapsed * velY;
        }

        @Override
        void draw(Graphics2D g2) {
            Graphics2D copy = (Graphics2D) g2.create();
            copy.setColor(Color.BLACK);
            copy.translate(locX, locY);
            copy.fill(shape);
        }
    }

    // See https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html
    class Controls {
        final Set<Integer> keysHeld = new HashSet<>();

        Controls() {
            bind(KeyEvent.VK_A, "left");
            bind(KeyEvent.VK_D, "right");
            bind(KeyEvent.VK_W, "up");
            bind(KeyEvent.VK_S, "down");
            bind(KeyEvent.VK_SPACE, "fire");
        }

        boolean isLeftHeld()  { return keysHeld.contains(KeyEvent.VK_A); }
        boolean isRightHeld() { return keysHeld.contains(KeyEvent.VK_D); }
        boolean isUpHeld()    { return keysHeld.contains(KeyEvent.VK_W); }
        boolean isDownHeld()  { return keysHeld.contains(KeyEvent.VK_S); }
        boolean isFireHeld()  { return keysHeld.contains(KeyEvent.VK_SPACE); }

        void bind(int keyCode, String name) {
            bind(keyCode, name, true);
            bind(keyCode, name, false);
        }

        void bind(int keyCode, String name, boolean isOnRelease) {
            KeyStroke stroke = KeyStroke.getKeyStroke(keyCode, 0, isOnRelease);
            name += isOnRelease ? ".released" : ".pressed";
            panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
                 .put(stroke, name);
            panel.getActionMap()
                 .put(name, new AbstractAction() {
                     @Override
                     public void actionPerformed(ActionEvent e) {
                         if (isOnRelease) {
                             keysHeld.remove(keyCode);
                         } else {
                             keysHeld.add(keyCode);
                         }
                     }
                 });
        }
    }

    // This returns the usable size of the display which
    // the JFrame resides in, as described here:
    // http://docs.oracle.com/javase/8/docs/api/java/awt/GraphicsEnvironment.html#getMaximumWindowBounds--
    static Dimension getMaximumWindowSize(JFrame frame) {
        GraphicsConfiguration config = frame.getGraphicsConfiguration();
        Dimension size   = config.getBounds().getSize();
        Insets    insets = Toolkit.getDefaultToolkit().getScreenInsets(config);
        size.width  -= insets.left + insets.right;
        size.height -= insets.top  + insets.bottom;
        return size;
    }
}
AffineTransform transform = new AffineTransform();

@Override
void update(double secondsElapsed) {
    ...
    // Clear the previous translation and rotation.
    transform.setToIdentity();
    // Set to current.
    transform.translate(locX, locY);
    Point2D.Double center = getCenter();
    transform.rotate(rotationAngle + (Math.PI / 2), center.x, center.y);

    if (controls.isFireHeld()) {
        Point2D.Double tip = new Point2D.Double(0, 0);
        transform.transform(tip, tip);
        Bullet bullet = new Bullet(tip.x, tip.y, rotationAngle);
        bullets.add(bullet);
    }
}