简体   繁体   中英

How to draw snakes in Swing?

I'm rather new to Java Swing, and working on a ladders and snaked project for my college course. The instructor has told us to implement a game in which the player can choose exactly how many snakes are on the game board, and where the snakes are. So is for the ladders! So I cannot use one, or several fixed images in my game, so that the player cannot change them anymore.

I need a way to draw such snakes and ladders in my game. The question is what is the best option to do this in Java? By which means can I draw user-desired snakes on my game board?

One thing you could do, is rotate the image by a given angle, this way, you could still use images and supply the ability to change their start and end points

旋转影像

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                try {
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (IOException exp) {
                    exp.printStackTrace();
                }
            }
        });
    }

    public class TestPane extends JPanel {

        private BufferedImage ladder;
        private double angle;

        public TestPane() throws IOException {
            ladder = ImageIO.read(getClass().getResource("Ladder.png"));
            JSlider slider = new JSlider(0, 100, 0);
            slider.addChangeListener(new ChangeListener() {
                @Override
                public void stateChanged(ChangeEvent e) {
                    angle = (360d * (slider.getValue() / 100d));
                    repaint();
                }
            });
            setLayout(new BorderLayout());
            add(slider, BorderLayout.SOUTH);
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            applyQualityRenderingHints(g2d);
            int x = getWidth() / 2;
            int y = getHeight() / 2;
            g2d.setColor(Color.RED);
            g2d.rotate(Math.toRadians(angle), x, y);
            g2d.drawImage(ladder, x - (ladder.getWidth() / 2), y - ladder.getHeight(), this);
            g2d.fillOval(x - 3, y - 3, 6, 6);
            g2d.dispose();
        }

        protected void applyQualityRenderingHints(Graphics2D g2d) {
            g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
            g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        }

    }

}

Now, because you're rotating the actual Graphics context, this can become very complicated very quickly, especially when you're trying to change the location and rotate a number of objects

Another option might be to rotate the image as a whole, for example...

旋转影像

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                try {
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (IOException exp) {
                    exp.printStackTrace();
                }
            }
        });
    }

    public class TestPane extends JPanel {

        private BufferedImage ladder;
        private double angle;

        public TestPane() throws IOException {
            ladder = ImageIO.read(getClass().getResource("Ladder.png"));
            JSlider slider = new JSlider(0, 100, 0);
            slider.addChangeListener(new ChangeListener() {
                @Override
                public void stateChanged(ChangeEvent e) {
                    angle = (360d * (slider.getValue() / 100d));
                    repaint();
                }
            });
            setLayout(new BorderLayout());
            add(slider, BorderLayout.SOUTH);
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            applyQualityRenderingHints(g2d);
            int x = getWidth() / 2;
            int y = getHeight() / 2;
            g2d.setColor(Color.RED);
            BufferedImage rotated = rotate(ladder, angle);
            g2d.drawImage(rotated, x - (rotated.getWidth() / 2), y - (rotated.getHeight() / 2), this);
            g2d.dispose();
        }

        public BufferedImage rotate(BufferedImage image, double byAngle) {

            double rads = Math.toRadians(byAngle);
            double sin = Math.abs(Math.sin(rads)), cos = Math.abs(Math.cos(rads));
            int w = image.getWidth();
            int h = image.getHeight();
            int newWidth = (int) Math.floor(w * cos + h * sin);
            int newHeight = (int) Math.floor(h * cos + w * sin);

            BufferedImage rotated = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = rotated.createGraphics();
            applyQualityRenderingHints(g2d);
            AffineTransform at = new AffineTransform();
            at.translate((newWidth - w) / 2, (newHeight - h) / 2);

            int x = w / 2;
            int y = h / 2;

            at.rotate(Math.toRadians(byAngle), x, y);
            g2d.setTransform(at);
            g2d.drawImage(image, 0, 0, this);
            g2d.dispose();

            return rotated;
        }

        protected void applyQualityRenderingHints(Graphics2D g2d) {
            g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
            g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        }

    }

}

Either way, you've got some serious management and maths ahead of you.

You might like to take a closer look at Painting in AWT and Swing and Performing Custom Painting and 2D Graphics for more details

There are many (many, many) degrees of freedom for answering this question. In fact, one could consider it as "too broad", as it is not much more specific than "how can I paint something in Swing?" (with "something" being a snake or a ladder here). There is a reason of why a significant part of game development is not only plain programming, but also the graphics design etc.

It is not clear how much the task of your course is focussed on exactly this point. If it is a general computer science course, then there likely is no need to spend dozens of hours for making the " prettiest " game. Instead, it could be sufficient to draw plain lines between the fields that the snakes/ladders should connect. Green lines for snakes, brown lines for ladders. However, maybe the priorities are different.


Regarding this question in particular, there are, broadly speaking, two options:

  • Paint the snakes as images
  • Paint the snakes as graphical objects

MadProgrammer showed in his answer the approach of using images. They can be rotated and drawn and scaled arbitrarily. In fact, when you have an image, say of size 100x1000, then you could make it span two arbitrary points. So if you have the points (200,400) and (700,1100) on the screen, then you can compute an orientation and scaling for the image so that the top center point of your image is located at (200,400), and the bottom center point is at (700,1100) - which is likely a requirement that could appear when you want to "draw a ladder starting at one field and ending at another".

The issue that I saw regarding the snakes was that the "contents" of the image would have to depend on the start- and end point. Namely, a snake that is painted between two fields that are close to each other might have a completely different shape than one that is painted between two distant fields.

(Similarly, a ladder: The number of steps that the ladder should have would certainly depend on the distance between the fields that it connects).


So, I did some "recreational programming" here, and created a snake painting class. The difference, compared to images, is that the snakes are graphical objects - particularly, they are composed of Shape objects. The tricky part is the body of the snake: It should have some waves, and a certain thickness, and the thickness should largely be constant along the body, except for the tail part....

Again: There are many degrees of freedom, and of course, this is just a snippet, quickly written down, to see (mainly for myself) of how one could tackle this problem of "drawing a snake body".

The result is a snake where you can drag around the head and tail between arbitrary points:

SmallSnake

Some of the degrees of freedom that I mentioned are summarized as (compile-time) variables in the Snake class. One could, for example, adjust the number of "waves" based on the distance between the head and the tail point:

LongSnake

But these are things that I'll leave to the real artists ;-)

The code is a bit crude and largely uncommented, but maybe someone finds it helpful nevertheless:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class SnakeDrawing
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> createAndShowGUI());
    }

    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        f.getContentPane().add(new SnakeDrawingPanel());

        f.setSize(800, 800);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}

class Snake
{
    private Point2D point0 = new Point2D.Double(100,500);
    private Point2D point1 = new Point2D.Double(700,500);

    double bodyWidth = 10;
    int waves = 4;
    double waveHeight = 0.05;
    double tailStart = 0.8;
    double headLength = 20;
    double headWidth = 16;
    double eyeRadius = 6;
    double irisRadius = 3;

    private Shape body;
    private Shape head;
    private Shape eyeR;
    private Shape eyeL;
    private Shape irisR;
    private Shape irisL;

    void setPoints(Point2D point0, Point2D point1)
    {
        this.point0.setLocation(point0);
        this.point1.setLocation(point1);

        AffineTransform at = AffineTransform.getRotateInstance(
            currentAngleRad(), point0.getX(), point0.getY());
        at.translate(point0.getX(), point0.getY());

        createBody(at);
        createHead(at);
    }

    void draw(Graphics2D g)
    {
        g.setColor(new Color(0,128,0));
        g.fill(body);
        g.fill(head);
        g.setColor(Color.WHITE);
        g.fill(eyeR);
        g.fill(eyeL);
        g.setColor(Color.BLACK);
        g.fill(irisR);
        g.fill(irisL);
    }

    private void createBody(AffineTransform at)
    {
        double distance = point1.distance(point0);
        int steps = 100;
        Path2D body = new Path2D.Double();
        Point2D previousPoint = null;
        for (int i=0; i<steps; i++)
        {
            double alpha = (double)i/(steps-1);
            Point2D point = computeCenterPoint(alpha, distance);
            if (previousPoint != null)
            {
                Point2D bodyPoint = 
                    computeBodyPoint(alpha, point, previousPoint);
                if (i==1)
                {
                    body.moveTo(bodyPoint.getX(), bodyPoint.getY());
                }
                else
                {
                    body.lineTo(bodyPoint.getX(), bodyPoint.getY());
                }
            }
            previousPoint = point;
        }
        previousPoint = null;
        for (int i=steps-1; i>=0; i--)
        {
            double alpha = (double)i/(steps-1);
            Point2D point = computeCenterPoint(alpha, distance);
            if (previousPoint != null)
            {
                Point2D bodyPoint = 
                    computeBodyPoint(alpha, point, previousPoint);
                body.lineTo(bodyPoint.getX(), bodyPoint.getY());
            }
            previousPoint = point;
        }
        this.body = at.createTransformedShape(body);
    }

    private Point2D computeBodyPoint(
        double alpha, Point2D point, Point2D previousPoint)
    {
        double dx = point.getX() - previousPoint.getX();
        double dy = point.getY() - previousPoint.getY();
        double rdx = -dy;
        double rdy = dx;
        double d = Math.hypot(dx, dy);
        double localBodyWidth = bodyWidth;
        if (alpha > tailStart)
        {
            localBodyWidth *= (1 - (alpha - tailStart) / (1.0 - tailStart));
        }
        double px = point.getX() + rdx * (1.0 / d) * localBodyWidth;
        double py = point.getY() + rdy * (1.0 / d) * localBodyWidth;
        return new Point2D.Double(px, py);
    }

    private Point2D computeCenterPoint(
        double alpha, double distance)
    {
        double r = alpha * Math.PI * 2 * waves;
        double verticalScaling = 1 - (alpha * 2 - 1) * (alpha * 2 - 1); 
        double y = Math.sin(r) * distance * waveHeight * verticalScaling;
        double x = alpha * distance;
        return new Point2D.Double(x,y);
    }

    private void createHead(AffineTransform at)
    {
        Shape head = new Ellipse2D.Double(
            -headLength, -headWidth,
            headLength + headLength,
            headWidth + headWidth);
        this.head = at.createTransformedShape(head);

        Shape eyeR = new Ellipse2D.Double(
            -headLength * 0.5 - eyeRadius,
            -headWidth * 0.6 - eyeRadius,
            eyeRadius + eyeRadius,
            eyeRadius + eyeRadius);
        Shape eyeL = new Ellipse2D.Double(
            -headLength * 0.5 - eyeRadius,
            headWidth * 0.6 - eyeRadius,
            eyeRadius + eyeRadius,
            eyeRadius + eyeRadius);
        this.eyeR = at.createTransformedShape(eyeR);
        this.eyeL = at.createTransformedShape(eyeL);

        Shape irisR = new Ellipse2D.Double(
            -headLength * 0.4 - eyeRadius,
            -headWidth * 0.6 - irisRadius,
            irisRadius + irisRadius,
            irisRadius + irisRadius);
        Shape irisL = new Ellipse2D.Double(
            -headLength * 0.4 - eyeRadius,
            headWidth * 0.6 - irisRadius,
            irisRadius + irisRadius,
            irisRadius + irisRadius);
        this.irisR = at.createTransformedShape(irisR);
        this.irisL = at.createTransformedShape(irisL);
    }

    private double currentAngleRad()
    {
        double dx = point1.getX() - point0.getX();
        double dy = point1.getY() - point0.getY();
        double angleRad = Math.atan2(dy, dx);
        return angleRad;
    }
}

class SnakeDrawingPanel extends JPanel 
    implements MouseListener, MouseMotionListener
{
    private Point2D point0 = new Point2D.Double(100,500);
    private Point2D point1 = new Point2D.Double(700,500);
    private Point2D draggedPoint = null;
    private Snake snake = new Snake();

    SnakeDrawingPanel()
    {
        addMouseListener(this);
        addMouseMotionListener(this);
    }

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

        g.setColor(Color.WHITE);
        g.fillRect(0, 0, getWidth(), getHeight());

        snake.setPoints(point0, point1);
        snake.draw(g);
    }


    @Override
    public void mouseDragged(MouseEvent e)
    {
        if (draggedPoint != null)
        {
            draggedPoint.setLocation(e.getPoint());
            repaint();
        }
    }

    @Override
    public void mouseMoved(MouseEvent e)
    {
        // Nothing to do here
    }

    @Override
    public void mouseClicked(MouseEvent e)
    {
        // Nothing to do here
    }

    @Override
    public void mousePressed(MouseEvent e)
    {
        draggedPoint = null;
        double thresholdSquared = 10*10;

        if (e.getPoint().distanceSq(point0) < thresholdSquared)
        {
            draggedPoint = point0;
        }
        if (e.getPoint().distanceSq(point1) < thresholdSquared)
        {
            draggedPoint = point1;
        }
    }

    @Override
    public void mouseReleased(MouseEvent e)
    {
        draggedPoint = null;
    }

    @Override
    public void mouseEntered(MouseEvent e)
    {
        // Nothing to do here
    }

    @Override
    public void mouseExited(MouseEvent e)
    {
        // Nothing to do here
    }

}

EDIT:

As an example / extension of the answer by MadProgrammer, here is a program that contains a method that allows you to draw an image between two given points. So, for a given ladder image, you can basically drag around the top- and bottom center point of the image:

LadderImage

Coincidentally, the relevant method is called drawImageBetweenPoints :

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class LadderDrawing
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> createAndShowGUI());
    }

    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        f.getContentPane().add(new LadderDrawingPanel());

        f.setSize(800, 800);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}


class LadderDrawingPanel extends JPanel 
    implements MouseListener, MouseMotionListener
{
    private Point2D point0 = new Point2D.Double(300,300);
    private Point2D point1 = new Point2D.Double(500,700);
    private Point2D draggedPoint = null;

    private BufferedImage ladderImage;

    LadderDrawingPanel()
    {
        addMouseListener(this);
        addMouseMotionListener(this);

        try
        {
            ladderImage = ImageIO.read(new File("ladder.png"));
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

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

        g.setColor(Color.WHITE);
        g.fillRect(0, 0, getWidth(), getHeight());

        g.setColor(Color.RED);
        paintDot(g, point0, 8);
        paintDot(g, point1, 8);

        drawImageBetweenPoints(g, ladderImage, point0, point1);
    }

    private static void paintDot(Graphics2D g, Point2D p, double radius)
    {
        g.fill(new Ellipse2D.Double(
            p.getX() - radius, p.getY() - radius,
            radius + radius, radius + radius));
    }

    private static void drawImageBetweenPoints(
        Graphics2D g, BufferedImage image, Point2D p0, Point2D p1)
    {
        AffineTransform at = new AffineTransform();
        at.concatenate(AffineTransform.getTranslateInstance(
            p0.getX(), p0.getY()));

        double dx = p1.getX() - p0.getX();
        double dy = p1.getY() - p0.getY();
        double angleRad = Math.atan2(dy, dx) - Math.PI * 0.5;
        at.concatenate(AffineTransform.getRotateInstance(angleRad));

        double distance = p1.distance(p0);
        double scalingY = distance / image.getHeight();

        // Default: Uniform scaling
        double scalingX = scalingY;

        // For keeping the width of the image
        //scalingX = 1.0;

        // For scaling to a fixed width:
        //double desiredWidth = 50;
        //scalingX = desiredWidth / image.getWidth();

        at.concatenate(AffineTransform.getScaleInstance(scalingX, scalingY));

        at.concatenate(AffineTransform.getTranslateInstance(
            -image.getWidth() * 0.5, 0));

        AffineTransform oldAT = g.getTransform();
        g.transform(at);
        g.drawImage(image, 0, 0, null);
        g.setTransform(oldAT);
    }


    @Override
    public void mouseDragged(MouseEvent e)
    {
        if (draggedPoint != null)
        {
            draggedPoint.setLocation(e.getPoint());
            repaint();
        }
    }

    @Override
    public void mouseMoved(MouseEvent e)
    {
        // Nothing to do here
    }

    @Override
    public void mouseClicked(MouseEvent e)
    {
        // Nothing to do here
    }

    @Override
    public void mousePressed(MouseEvent e)
    {
        draggedPoint = null;
        double thresholdSquared = 10*10;

        if (e.getPoint().distanceSq(point0) < thresholdSquared)
        {
            draggedPoint = point0;
        }
        if (e.getPoint().distanceSq(point1) < thresholdSquared)
        {
            draggedPoint = point1;
        }
    }

    @Override
    public void mouseReleased(MouseEvent e)
    {
        draggedPoint = null;
    }

    @Override
    public void mouseEntered(MouseEvent e)
    {
        // Nothing to do here
    }

    @Override
    public void mouseExited(MouseEvent e)
    {
        // Nothing to do here
    }

}

Again, I think that a manual drawing may be more flexible (and, particularly for the ladder, not much more difficult), because you can select the number of steps of the ladder to dynamically adjust based on the distance of the points. For example:

LadderManual01

LadderManual02

It boils down to a bit of math for computing the positions of the bars and steps, and playing a bit with strokes and shapes:

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class LadderDrawingManual
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> createAndShowGUI());
    }

    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        f.getContentPane().add(new LadderDrawingManualPanel());

        f.setSize(800, 800);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}


class LadderDrawingManualPanel extends JPanel 
    implements MouseListener, MouseMotionListener
{
    private Point2D point0 = new Point2D.Double(300,300);
    private Point2D point1 = new Point2D.Double(500,700);
    private Point2D draggedPoint = null;

    LadderDrawingManualPanel()
    {
        addMouseListener(this);
        addMouseMotionListener(this);
    }

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

        g.setColor(Color.WHITE);
        g.fillRect(0, 0, getWidth(), getHeight());

        g.setColor(Color.RED);
        paintDot(g, point0, 8);
        paintDot(g, point1, 8);

        drawLadderBetweenPoints(g, point0, point1);
    }

    private static void paintDot(Graphics2D g, Point2D p, double radius)
    {
        g.fill(new Ellipse2D.Double(
            p.getX() - radius, p.getY() - radius,
            radius + radius, radius + radius));
    }

    private static void drawLadderBetweenPoints(
        Graphics2D g, Point2D p0, Point2D p1)
    {
        final double ladderWidth = 40;
        final double distanceBetweenSteps = 30;
        final double barWidth = 5;

        double dx = p1.getX() - p0.getX();
        double dy = p1.getY() - p0.getY();
        double distance = p1.distance(p0);

        double dirX = dx / distance;
        double dirY = dy / distance;

        double offsetX = dirY * ladderWidth * 0.5;
        double offsetY = -dirX * ladderWidth * 0.5;

        Line2D lineR = new Line2D.Double(
            p0.getX() + offsetX, 
            p0.getY() + offsetY,
            p1.getX() + offsetX,
            p1.getY() + offsetY);

        Line2D lineL = new Line2D.Double(
            p0.getX() - offsetX, 
            p0.getY() - offsetY,
            p1.getX() - offsetX,
            p1.getY() - offsetY);

        drawBar(g, lineL, barWidth);
        drawBar(g, lineR, barWidth);

        int numSteps = (int)(distance / distanceBetweenSteps);
        for (int i=0; i<numSteps; i++)
        {
            double stepOffsetX = (i+1) * distanceBetweenSteps;
            double stepOffsetY = (i+1) * distanceBetweenSteps;

            Line2D step = new Line2D.Double(
                p0.getX() + stepOffsetX * dirX - offsetX, 
                p0.getY() + stepOffsetY * dirY - offsetY,
                p0.getX() + stepOffsetX * dirX + offsetX,
                p0.getY() + stepOffsetY * dirY + offsetY);
            drawBar(g, step, barWidth);
        }
    }

    private static void drawBar(Graphics2D g, Line2D line, double barWidth)
    {
        Stroke stroke = new BasicStroke(
            (float)barWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
        Shape bar = stroke.createStrokedShape(line);
        g.setColor(new Color(200,100,0));
        g.fill(bar);
        g.setColor(Color.BLACK);
        g.draw(bar);
    }


    @Override
    public void mouseDragged(MouseEvent e)
    {
        if (draggedPoint != null)
        {
            draggedPoint.setLocation(e.getPoint());
            repaint();
        }
    }

    @Override
    public void mouseMoved(MouseEvent e)
    {
        // Nothing to do here
    }

    @Override
    public void mouseClicked(MouseEvent e)
    {
        // Nothing to do here
    }

    @Override
    public void mousePressed(MouseEvent e)
    {
        draggedPoint = null;
        double thresholdSquared = 10*10;

        if (e.getPoint().distanceSq(point0) < thresholdSquared)
        {
            draggedPoint = point0;
        }
        if (e.getPoint().distanceSq(point1) < thresholdSquared)
        {
            draggedPoint = point1;
        }
    }

    @Override
    public void mouseReleased(MouseEvent e)
    {
        draggedPoint = null;
    }

    @Override
    public void mouseEntered(MouseEvent e)
    {
        // Nothing to do here
    }

    @Override
    public void mouseExited(MouseEvent e)
    {
        // Nothing to do here
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM