-2

I have made this Solar System and so far I have achieved to make this code. As depicted in figure, instead of using public static class SolarCv extends JComponent I want to use public static class SolarCv extends JPanel, but it's not working.What should I change in the code and how to add orbits around objects as shown in picture?

package solar;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.io.IOException;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.WindowConstants;

public class Orbit {

    public static class SolarCv extends JComponent {
        int width;
        int height;

        public SolarCv(int width, int height) {
            this.width = width;
            this.height = height;
        }

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

        @Override
        public void paint(Graphics g) {
            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

            // Clear the background.
            g2.setColor(getBackground());
            g2.fillRect(0, 0, getWidth(), getHeight());

            // Sun transform. Just centre it in the window.
            AffineTransform StarSun = AffineTransform.getTranslateInstance(getWidth() / 2, getHeight() / 2);

            // Draw the sun
            g2.setTransform(StarSun);
            SuperStar(g2, 80, Color.YELLOW);

            // Orbital period.
            // One rotation every 10s.
            double percentRotation = System.currentTimeMillis() % 10000 / 10000.0;
            // To radians.
            double angle = -Math.PI * 2 * percentRotation;

            // Earth transform.
            // Set the orbital radius to 1/3rd the panel width
            AffineTransform StarEarth = AffineTransform.getTranslateInstance(getWidth() / 4, 0);
            // Rotate
            StarEarth.preConcatenate(AffineTransform.getRotateInstance(angle));
            // Add the sun transform
            StarEarth.preConcatenate(StarSun);

            // Draw the earth
            g2.setTransform(StarEarth);
            SuperStar(g2, 40, Color.BLUE);

            // Moon transform.
            // Set the orbital radius to 1/10th the panel width
            AffineTransform StarMoon = AffineTransform.getTranslateInstance(getWidth() / 10, 0);
            // Rotate
            StarMoon.preConcatenate(AffineTransform.getRotateInstance(angle));
            // Add the earth transform (already includes the sun transform)
            StarMoon.preConcatenate(StarEarth);

            // Draw the moon
            g2.setTransform(StarMoon);
            SuperStar(g2, 20, Color.DARK_GRAY);
        }

        private void SuperStar(Graphics2D g2, int size, Color color) {
            g2.setColor(color);
            g2.fillOval(-size / 2, -size / 2, size, size);
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        JFrame SolarSys = new JFrame("Orbit");
        SolarSys.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        JComponent SolarCv = new SolarCv(500, 500);
        SolarSys.add(SolarCv);
        SolarSys.pack();
        SolarSys.setVisible(true);

        while (true) {
            Thread.sleep(20);
            SolarCv.repaint();
        }
    }
}
  • 2
    1) *Is it possible to use JPanel instead of JComponent?* - why? 2) *but it's not working* - define not working. The painting looks the same when I use JComponent or JPanel. 3) "and how to add orbits around objects as shown in picture?" - use the `drawOval(...)` method 4) custom painting is done by overriding paintComponent(...), not paint(...). 5) use a Swing Timer for animation, not Thread.sleep. – camickr Feb 08 '21 at 15:23
  • I have an assignment and I should make it look like in figure, so not using JComponent but JPanel. – OPTIMU5PR1M3 Feb 08 '21 at 15:25
  • (1-) and as I stated in my comment, the code appears to work exactly the same when you use a JPanel instead of a JComponent. Painting on a JPanel is the same as painting on a JComponent. So one last time, define "not working" when you use a JPanel! – camickr Feb 08 '21 at 15:27
  • If you change "public static class SolarCv extends JComponent" to "public static class SolarCv extends JPanel" shows error. – OPTIMU5PR1M3 Feb 08 '21 at 15:31
  • Did you include the proper import statements? – camickr Feb 08 '21 at 15:46
  • Could you send the hole code using JPanel? – OPTIMU5PR1M3 Feb 08 '21 at 15:51
  • Please don't make more work for other people by vandalizing your posts. By posting on the Stack Exchange network, you've granted a non-revocable right, under the [CC BY-SA 4.0 license](//creativecommons.org/licenses/by-sa/4.0/), for Stack Exchange to distribute that content (i.e. regardless of your future choices). By Stack Exchange policy, the non-vandalized version of the post is the one which is distributed. Thus, any vandalism will be reverted. If you want to know more about deleting a post please see: [How does deleting work?](//meta.stackexchange.com/q/5221) – Sabito stands with Ukraine Feb 14 '21 at 14:20

1 Answers1

1

I found your assignment interesting, so I created the following GUI.

Solar System GUI

Introduction

I couldn't make the sun, earth, and moon relative sizes because they wouldn't fit on a monitor.

I couldn't make the sun, earth, and moon relative distance apart because they wouldn't fit on a monitor.

However, I could make the rotational speeds of the earth and moon relative to each other.

So, how did I do this?

Since this is an assignment, I'm not going to provide my entire code. I will provide an explanation of how I wrote my code and some code snippets in the hopes that it will help the OP and others in the future.

Explanation

Generally, when you create a Swing GUI, you should use the model / view / controller pattern. This pattern allows you to separate your concerns and focus on one part of the application at a time.

I wrote five Java classes to create this GUI. I wrote two model classes, two view classes, and one controller class. The controller class has one additional anonymous class.

Model

The first model class I wrote was the Body class. This class is a plan Java getter / setter class.

The Body class contains a java.awt.Point marking the center of the body, a radius of the body in int pixels, a radius of the orbit in int pixels, an increment of the theta angle in degrees, and a body color.

Now, the orbit of the earth should be a part of an earth instance. However, I found it easier to make the earth orbit part of the sun instance. This is one of those cases where your code doesn't reflect reality so that you can model reality more accurately.

It took me a while to come up with the appropriate theta angle increments for the earth and the moon. I'll just give you my equations since I spent about 15 minutes trying different functions.

sun thetaIncrement = 625_000.0 / framesPerSecond / 365.25 / 360.0
earth thetaIncrement = 625_000.0 / framesPerSecond / 27.3 / 360.0

The 625,000 is a "magic" number that makes the animation move at a reasonable speed. The 360 is the number of degrees in a circle.

The only difference between the two formulas is the values for the number of days to make a complete rotation.

The Body class has one interesting method, the getOrbitPoint method. Basically, the getOrbitPoint method converts polar coordinates to cartesian coordinates. It's pretty straightforward, but I'll post the code.

    public Point getOrbitPoint() {
        double radians = Math.toRadians(theta);
        int x = (int) Math.round(Math.cos(radians) * 
                orbitRadius + center.x);
        int y = (int) Math.round(Math.sin(radians) * 
                orbitRadius + center.y);
        
        theta += thetaIncrement;
        theta = (theta > 360.0) ? theta - 360.0 : theta;
        theta = (theta < -360.0) ? theta + 360.0 : theta;
        
        return new Point(x, y);
    }

A positive thetaIncrement will make the body rotate clockwise. A negative thetaIncrement will make the body rotate counter-clockwise.

The second model class I created is the SolarSystemModel class. This class is a plain Java getter / setter class.

The SolarSystemModel class holds an int value for the number of display panel pixels, an int value for the number of frames per second for the animation, and a java.util.List of Body instances.

I created a factory method to create three Body instances, sun, earth, and moon. Here's that code snippet.

    private List<Body> createBodyFactory() {
        List<Body> bodies = new ArrayList<>();
        
        int c = drawingPanelWidth / 2;
        int o = drawingPanelWidth / 3;
        double thetaIncrement = 625_000.0 / framesPerSecond / 365.25 / 360.0;
        Body body = new Body(c, c, 60, o, thetaIncrement, Color.YELLOW);
        bodies.add(body);
        
        Point point = body.getOrbitPoint();
        thetaIncrement = 625_000.0 / framesPerSecond / 27.3 / 360.0;
        body = new Body(point.x, point.y, 20, 60, thetaIncrement, Color.BLUE);
        bodies.add(body);
        
        point = body.getOrbitPoint();
        body = new Body(point.x, point.y, 10, 20, 0.0, Color.WHITE);
        bodies.add(body);
        
        return bodies;
    }

View

I created two classes, one for the JFrame and one for the drawing JPanel. Rather than a lengthy explanation, I'm posting the code for those two classes.

public class SolarSystemGUI implements Runnable {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new SolarSystemGUI());
    }
    
    private Animation animation;
    
    private DrawingPanel drawingPanel;
    
    private SolarSystemModel model;
    
    public SolarSystemGUI() {
        this.model = new SolarSystemModel();
    }

    @Override
    public void run() {
        JFrame frame = new JFrame("Solar System");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        drawingPanel = new DrawingPanel(model);
        frame.add(drawingPanel, BorderLayout.CENTER);
        
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
        
        this.animation = new Animation(this, model);
        new Thread(animation).start();
    }
    
    public void repaint() {
        drawingPanel.repaint();
    }
    
    public class DrawingPanel extends JPanel {

        private static final long serialVersionUID = 1L;
        
        private SolarSystemModel model;
        
        public DrawingPanel(SolarSystemModel model) {
            this.model = model;
            this.setBackground(Color.BLACK);
            int width = model.getDrawingPanelWidth();
            this.setPreferredSize(new Dimension(width, width));
        }
        
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            
            List<Body> bodies = model.getBodies();
            for (Body body : bodies) {
                drawBody(g, body);
            }
        }
        
        private void drawBody(Graphics g, Body body) {
            Point point = body.getCenter();
            int radius = body.getRadius();
            int x = point.x - radius;
            int y = point.y - radius;
            int width = radius + radius;
            
            g.setColor(body.getColor());
            g.fillOval(x, y, width, width);
        }
        
    }

}

You can see how simple the view classes become when you create an appropriate application model.

The run method creates the JFrame and one instance of the drawing JPanel.

The drawing JPanel draws the Body instances. Period. The drawing JPanel doesn't try and do anything else.

The Graphics fillOval method draws the oval and fills it in based on the upper left corner. We do a little math so we can draw the oval based on the center point.

Controller

There's only one controller class, the Animation class.

The Animation class pauses for 5 seconds, then starts the rotation of the earth and moon. Because of the work we already did creating the model and the view, the Animation class is also straightforward.

Since I used an old-school Runnable, I'll post the class here.

public class Animation implements Runnable {
    
    private volatile boolean running;
    
    private final SolarSystemGUI frame;
    
    private final SolarSystemModel model;

    public Animation(SolarSystemGUI frame, SolarSystemModel model) {
        this.frame = frame;
        this.model = model;
        this.running = true;
    }

    @Override
    public void run() {
        sleep(5000L);
        long duration = 1000L / model.getFramesPerSecond();
        
        while (running) {
            List<Body> bodies = model.getBodies();
            Point center = null;
            for (Body body : bodies) {
                if (center != null) {
                    body.setCenter(center);
                }
                center = body.getOrbitPoint();
            }
            repaint();
            sleep(duration);
        }
    }
    
    private void sleep(long duration) {
        try {
            Thread.sleep(duration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    private void repaint() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                frame.repaint();
            }
        });
    }

    public synchronized void setRunning(boolean running) {
        this.running = running;
    }
    
}

Conclusion

I hope this explanation helps. Use the model / view / controller pattern. Separate your concerns. Focus on one part of the application at a time. Make sure each and every class method does one thing.

Gilbert Le Blanc
  • 50,182
  • 6
  • 67
  • 111
  • Updates to the components or the model should be done on the EDT. Animation should be done using a Swing Timer. – camickr Feb 09 '21 at 15:18
  • @camickr: Updates to Swing components are being done on the EDT, Updates to the application model can be done on any thread. – Gilbert Le Blanc Feb 09 '21 at 15:24
  • My understanding is updates to the model should ALSO be done on the EDT. Think of the situation where you are updating the model in one Thread but the GUI is painting the component on the EDT. This is why the SwingWorker has a `publish(...)` method. It allows you to access data externally and then publish the data so you can update the model on the EDT. – camickr Feb 09 '21 at 16:03