3D Rubik's Cube in Java Swing

How to Create a 3D Rubik's Cube in Java Netbeans

How to Create a 3D Rubik's Cube in Java Netbeans



In this Java Tutorial we will see How To Create a 3D Rubik's Cube with realistic lighting effects, smooth rotations, floating animations, and mouse controls. 
The cube features authentic Rubik's cube colors, gradient shading, and perspective projection to create a 3D experience In Java Using Netbeans.

This Rubik's Cube in Java Swing is an excellent project that combines computer graphics, 3D mathematics, and user interface design - all without using external 3D libraries..

What We Are Gonna Use In This Project:

- Java Programming Language.
- NetBeans Editor.





Project Source Code:



/**
 *
 * @author 1BestCsharp
 */
public class RubiksCube extends JFrame {
    
    // Target and current rotation angles
    private double currentRotationX = -30;  // Current X rotation in degrees
    private double currentRotationY = 45;   // Current Y rotation in degrees 
    private double currentRotationZ = 0;    // Current Z rotation in degrees
    
    private double targetRotationX = -30;   // Target X rotation for smooth animation
    private double targetRotationY = 45;    // Target Y rotation for smooth animation
    private double targetRotationZ = 0;     // Target Z rotation for smooth animation
    
    // Animation properties
    private final double ROTATION_SPEED = 0.15;  // Speed of rotation interpolation (0-1)
    private boolean isAnimating = false;         // Tracks if rotation animation is in progress
    
    // For mouse dragging interaction
    private boolean isDragging = false;          // Tracks if user is dragging the cube
    private Point previousPoint;                 // Stores previous mouse position for drag calculations
    
    // Faces colors - standard Rubik's cube colors
    private static final Color[] FACE_COLORS = {
        new Color(255, 0, 0),    // Front - Red
        new Color(255, 165, 0),  // Back - Orange
        new Color(0, 255, 0),    // Right - Green
        new Color(0, 0, 255),    // Left - Blue
        new Color(255, 255, 255),// Top - White
        new Color(255, 255, 0)   // Bottom - Yellow
    };
    
    // Darker versions of face colors for gradients and shading effects
    private static final Color[] FACE_COLORS_DARK = {
        new Color(180, 0, 0),    // Front - Dark Red
        new Color(180, 117, 0),  // Back - Dark Orange
        new Color(0, 180, 0),    // Right - Dark Green
        new Color(0, 0, 180),    // Left - Dark Blue
        new Color(200, 200, 200),// Top - Light Gray
        new Color(180, 180, 0)   // Bottom - Dark Yellow
    };
    
    // Face indices for improved code readability
    private static final int FRONT = 0;
    private static final int BACK = 1;
    private static final int RIGHT = 2;
    private static final int LEFT = 3;
    private static final int TOP = 4;
    private static final int BOTTOM = 5;
    
    // Stores cube state (colors of all cells)
    private CubeModel cubeModel;
    
    // Main drawing panel
    private CubePanel cubePanel;
    
    // Animation timers
    private Timer floatAnimationTimer;       // Timer for floating effect animation
    private Timer rotationAnimationTimer;    // Timer for smooth rotation animation
    private double floatOffset = 0;          // Current offset for floating animation
    private boolean floatingUp = true;       // Direction of floating animation
    
    
    public RubiksCube(){
        
        setTitle("Rubik's Cube");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new BorderLayout());
        
         // Initialize cube model (stores the state of all cube cells)
        cubeModel = new CubeModel();
        
        // Create and configure the cube rendering panel
        cubePanel = new CubePanel();
        cubePanel.setPreferredSize(new Dimension(800, 600));
        cubePanel.setBackground(new Color(15, 15, 15));  // Very dark background
        add(cubePanel, BorderLayout.CENTER);
        
         // Create control panel with buttons
        JPanel controlPanel = createControlPanel();
        add(controlPanel, BorderLayout.SOUTH);
        
        // Set up mouse listeners for interactive rotation
        setupMouseListeners();
        
         // Start animation timers
         startRotationAnimation();
         startFloatingAnimation();

        pack();
        setLocationRelativeTo(null);  // Center on screen
        
    }
    
    
     /**
     * Creates the control panel with rotation buttons
     * @return JPanel containing control buttons
     */
      private JPanel createControlPanel() {
          
            JPanel panel = new JPanel();
            panel.setBackground(new Color(30, 30, 30));
            panel.setBorder(new EmptyBorder(15, 15, 15, 15));
            panel.setLayout(new FlowLayout(FlowLayout.CENTER, 15, 0));
            
            // Create control buttons
            JButton rotateXButton = createControlButton("Rotate X");
            JButton rotateYButton = createControlButton("Rotate Y");
            JButton rotateZButton = createControlButton("Rotate Z");
            JButton resetButton = createControlButton("Reset View");
            
            // Add action listeners to buttons
            rotateXButton.addActionListener(e -> {
                targetRotationX += 90;  // Rotate 90 degrees around X axis
                isAnimating = true;
            });
            
            rotateYButton.addActionListener(e -> {
                targetRotationY += 90;  // Rotate 90 degrees around Y axis
                isAnimating = true;
            });
            
            rotateZButton.addActionListener(e -> {
                targetRotationZ += 90;  // Rotate 90 degrees around Z axis
                isAnimating = true;
            });
            
            resetButton.addActionListener(e -> {
                // Reset to default view angles
                targetRotationX = -30;
                targetRotationY = 45;
                targetRotationZ = 0;
                isAnimating = true;
            });
            
            // Add buttons to panel
            panel.add(rotateXButton);
            panel.add(rotateYButton);
            panel.add(rotateZButton);
            panel.add(resetButton);

            return panel;
          
        }
      
     /**
     * Creates a styled control button with hover effects
     * @param text Button text
     * @return Styled JButton
     */
      private JButton createControlButton(String text) {
          
            JButton button = new JButton(text);
            button.setForeground(Color.WHITE);
            button.setBackground(new Color(60, 60, 60));
            button.setFocusPainted(false);
            button.setBorderPainted(false);
            button.setFont(new Font("SansSerif", Font.BOLD, 14));
            button.setPreferredSize(new Dimension(120, 40));
            
            // Add custom hover and press effects
            button.addMouseListener(new MouseAdapter() {
                
                @Override
                public void mouseEntered(MouseEvent e) {
                    button.setBackground(new Color(80, 80, 80));  // Lighter on hover
                    button.setCursor(new Cursor(Cursor.HAND_CURSOR));
                }
                
                 @Override
                public void mouseExited(MouseEvent e) {
                    button.setBackground(new Color(60, 60, 60));  // Back to normal when not hovering
                    button.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
                }
                
                @Override
                public void mousePressed(MouseEvent e) {
                    button.setBackground(new Color(100, 100, 100));  // Even lighter when pressed
                }
                
                @Override
                public void mouseReleased(MouseEvent e) {
                    button.setBackground(new Color(80, 80, 80));  // Back to hover state when released
                }
                
            });

            return button;
          
      }
      
      
     /**
     * Sets up mouse listeners for interactive cube rotation
     */
      private void setupMouseListeners() {
          
           cubePanel.addMouseListener(new MouseAdapter() {
               
                @Override
                public void mousePressed(MouseEvent e) {
                    isDragging = true;
                    previousPoint = e.getPoint();
                    cubePanel.setCursor(new Cursor(Cursor.MOVE_CURSOR));
                }
                
                @Override
                public void mouseReleased(MouseEvent e) {
                    isDragging = false;
                    cubePanel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
                }
               
           });
           
           cubePanel.addMouseMotionListener(new MouseMotionAdapter() {
               
                 @Override
                 public void mouseDragged(MouseEvent e) {
                     
                        if (isDragging) {
                            
                              Point currentPoint = e.getPoint();
                              
                              // Calculate movement deltas
                              double deltaX = currentPoint.x - previousPoint.x;
                              double deltaY = currentPoint.y - previousPoint.y;
                              
                               // Update target rotations - mouse X movement rotates Y axis and vice versa
                               targetRotationY += deltaX * 0.5;  // Horizontal movement -> Y rotation
                               targetRotationX += deltaY * 0.5;  // Vertical movement -> X rotation
                               
                                // Update current rotations partially for immediate visual feedback
                                currentRotationY += deltaX * 0.25;
                                currentRotationX += deltaY * 0.25;
                                
                                 previousPoint = currentPoint;
                                 cubePanel.repaint();
                            
                        }
                     
                 }
               
           });
          
      }
      
      
      
     /**
     * Starts the floating animation that makes the cube hover up and down
     */
      private void startFloatingAnimation() {
          
            floatAnimationTimer = new Timer(50, e -> {
                
                  // Update floating offset based on direction
                  if (floatingUp) {
                      
                      floatOffset += 0.15;
                      
                     if (floatOffset >= 8) {  // Max height reached
                            floatingUp = false;  // Change direction
                     }
          
                  }
                  
                   else {
                      
                           floatOffset -= 0.15;
                            if (floatOffset <= -8) {  // Min height reached
                                floatingUp = true;    // Change direction
                            }
                  }
                  
                 cubePanel.repaint();
                
            });
            
          floatAnimationTimer.start();
          
      }
      
      
      
      
      
     /**
     * Starts the rotation animation for smooth transitions
     */
      private void startRotationAnimation() {
          
           rotationAnimationTimer = new Timer(16, e -> { 
               
                 boolean needsUpdate = false;
                 
                 // Smoothly interpolate X rotation using easing
                 if (Math.abs(targetRotationX - currentRotationX) > 0.1) {
                          currentRotationX += (targetRotationX - currentRotationX) * ROTATION_SPEED;
                          needsUpdate = true;
                 }
                  else {
                    currentRotationX = targetRotationX;  // Snap to exact value when close enough
                }
                 
                 // Smoothly interpolate Y rotation using easing
                 if (Math.abs(targetRotationY - currentRotationY) > 0.1) {
                          currentRotationY += (targetRotationY - currentRotationY) * ROTATION_SPEED;
                          needsUpdate = true;        
                  }
                 else {
                        currentRotationY = targetRotationY;  // Snap to exact value when close enough
                 }
                 
                  // Smoothly interpolate Z rotation using easing
                 if (Math.abs(targetRotationZ - currentRotationZ) > 0.1) {
                           currentRotationZ += (targetRotationZ - currentRotationZ) * ROTATION_SPEED;
                           needsUpdate = true;
                 }
                  else {
                        currentRotationZ = targetRotationZ;  // Snap to exact value when close enough
                  }
                 
                  // Only repaint if rotations changed
                  if (needsUpdate) {
                        cubePanel.repaint();             
                  }
                  else if (isAnimating) {
                        isAnimating = false;  // Stop animation flag when target reached
                  }

           });
           
         rotationAnimationTimer.start();
          
      }
    
    
    
     /**
     * Inner class to store the state of the Rubik's Cube
     * Currently just stores colors, but could be extended for moves/rotations
     */
     class CubeModel {
         
         private final int SIZE = 3;       // 3x3x3 standard cube
         private Color[][][] cells;        // [face][row][col] array of cell colors
         
         public CubeModel() {
             
             cells = new Color[6][SIZE][SIZE];
             
             for (int face = 0; face < 6; face++) {
                 
                 for (int row = 0; row < SIZE; row++) {
                     
                    for (int col = 0; col < SIZE; col++) {
                        cells[face][row][col] = FACE_COLORS[face];
                    }
                     
                 }
                 
             }
             
         }
         
         /**
         * Gets the color of a specific cell
         * @param face Face index (0-5)
         * @param row Row index (0-2)
         * @param col Column index (0-2)
         * @return Color of the specified cell
         */
        public Color getCellColor(int face, int row, int col) {
            return cells[face][row][col];
        }
        
         /**
         * Gets the darker color variant for a specific cell (for gradients)
         * @param face Face index (0-5)
         * @param row Row index (0-2)
         * @param col Column index (0-2)
         * @return Darker version of the cell color
         */
        public Color getCellDarkColor(int face, int row, int col) {
            return FACE_COLORS_DARK[face];
        }
         
     }
     
     /**
     * Inner class for rendering the 3D cube
     */
      class CubePanel extends JPanel {
          
            private static final int CUBE_SIZE = 300;  // Size of the cube in pixels
            private static final int CELL_GAP = 6;     // Gap between cells for visual separation
            
            // Array to store 3D coordinates of cube corners for each face
            private double[][][] faceVertices;
            
            public CubePanel() {
                // Initialize 3D vertices for all cube faces
                initFaceVertices();

                // Set cursor to show interactivity
                setCursor(new Cursor(Cursor.HAND_CURSOR));
        }
            
         /**
         * Initializes the 3D vertices for all cube faces
         * Each face has 4 corners defined in 3D space
         */
         private void initFaceVertices() {
             
            // Each face has 4 vertices in 3D space
            faceVertices = new double[16][4][3];
            
            double s = CUBE_SIZE / 2.0;  // Half size of the cube
            
            // FRONT face (z = s)
            faceVertices[FRONT][0] = new double[]{-s, -s, s};  // Top-left
            faceVertices[FRONT][1] = new double[]{s, -s, s};   // Top-right
            faceVertices[FRONT][2] = new double[]{s, s, s};    // Bottom-right
            faceVertices[FRONT][3] = new double[]{-s, s, s};   // Bottom-left
            
            // BACK face (z = -s)
            faceVertices[BACK][0] = new double[]{s, -s, -s};   // Top-left (mirrored)
            faceVertices[BACK][1] = new double[]{-s, -s, -s};  // Top-right (mirrored)
            faceVertices[BACK][2] = new double[]{-s, s, -s};   // Bottom-right (mirrored)
            faceVertices[BACK][3] = new double[]{s, s, -s};    // Bottom-left (mirrored)
            
            // RIGHT face (x = s)
            faceVertices[RIGHT][0] = new double[]{s, -s, s};   // Top-left
            faceVertices[RIGHT][1] = new double[]{s, -s, -s};  // Top-right
            faceVertices[RIGHT][2] = new double[]{s, s, -s};   // Bottom-right
            faceVertices[RIGHT][3] = new double[]{s, s, s};    // Bottom-left
            
            // LEFT face (x = -s)
            faceVertices[LEFT][0] = new double[]{-s, -s, -s};  // Top-left
            faceVertices[LEFT][1] = new double[]{-s, -s, s};   // Top-right
            faceVertices[LEFT][2] = new double[]{-s, s, s};    // Bottom-right
            faceVertices[LEFT][3] = new double[]{-s, s, -s};   // Bottom-left
            
            // TOP face (y = -s)
            faceVertices[TOP][0] = new double[]{-s, -s, -s};   // Top-left
            faceVertices[TOP][1] = new double[]{s, -s, -s};    // Top-right
            faceVertices[TOP][2] = new double[]{s, -s, s};     // Bottom-right
            faceVertices[TOP][3] = new double[]{-s, -s, s};    // Bottom-left
            
            // BOTTOM face (y = s)
            faceVertices[BOTTOM][0] = new double[]{-s, s, s};  // Top-left
            faceVertices[BOTTOM][1] = new double[]{s, s, s};   // Top-right
            faceVertices[BOTTOM][2] = new double[]{s, s, -s};  // Bottom-right
            faceVertices[BOTTOM][3] = new double[]{-s, s, -s}; // Bottom-left

         }
         
        
        @Override
        protected void paintComponent(Graphics g) {
            
             super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g;
            
            // Enable antialiasing for smoother rendering
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
                    RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_RENDERING, 
                    RenderingHints.VALUE_RENDER_QUALITY);
            
            // Create background gradient
            GradientPaint bgGradient = new GradientPaint(
                0, 0, new Color(25, 25, 30),
                getWidth(), getHeight(), new Color(10, 10, 15)
            );
            g2d.setPaint(bgGradient);
            g2d.fillRect(0, 0, getWidth(), getHeight());
            
            // Get center of panel for translation
            int centerX = getWidth() / 2;
            int centerY = getHeight() / 2;
            
            // Apply floating animation effect
            double scaleFloat = 1.0 + Math.abs(floatOffset) / 100.0;  // Subtle scale change
            double yFloat = floatOffset;  // Y position offset for floating
            
            // Move to center of panel with floating offset
            g2d.translate(centerX, centerY + yFloat);
            g2d.scale(scaleFloat, scaleFloat);  // Apply subtle scaling
            
            // Calculate visibility of faces based on current rotation
            int[] faceVisibility = calculateFaceVisibility();
            
            // Draw faces in order of visibility (back to front)
            for (int i = 0; i < 6; i++) {
                int face = faceVisibility[i];
                drawFace(g2d, face);
            }
            
            
        }
        
        
        /**
         * Calculates which faces are visible and their drawing order
         * Uses z-ordering (painter's algorithm) to determine the order
         * @return Array of face indices in back-to-front order
         */
        private int[] calculateFaceVisibility() {
            
            // Calculate center point of each face after rotation
            double[][] faceCenters = new double[6][3];
            
            // Find center of each face by averaging its vertices
            for (int face = 0; face < 6; face++) {
                
                  double[] center = {0, 0, 0};
                  
                 // Average all 4 corners to get center
                for (int i = 0; i < 4; i++) {
                    
                    center[0] += faceVertices[face][i][0];  // Sum x coordinates
                    center[1] += faceVertices[face][i][1];  // Sum y coordinates
                    center[2] += faceVertices[face][i][2];  // Sum z coordinates
                    
                }
                center[0] /= 4;  // Average x
                center[1] /= 4;  // Average y
                center[2] /= 4;  // Average z
                
                // Apply current rotation to center point
                double[] rotatedCenter = rotatePoint(center);
                faceCenters[face] = rotatedCenter;
                
            }
            
             // Sort faces by z-coordinate
            // Faces with smaller z (further back) are drawn first
            int[] faceOrder = {0, 1, 2, 3, 4, 5};
            
            // Simple sort by z-coordinate
            for (int i = 0; i < 5; i++) {
                for (int j = i + 1; j < 6; j++) {
                    
                    if (faceCenters[faceOrder[i]][2] > faceCenters[faceOrder[j]][2]) {
                        
                        // Swap indices if z-order is incorrect
                        int temp = faceOrder[i];
                        faceOrder[i] = faceOrder[j];
                        faceOrder[j] = temp;
                        
                    }
                    
                }
            }
            
           return faceOrder;
            
        }
        
        
         /**
         * Draws a single face of the cube
         * @param g2d Graphics context
         * @param face Face index to draw
         */
         private void drawFace(Graphics2D g2d, int face) {
             
            // Get the projected 2D points for the face after rotation
            Point2D.Double[] projectedPoints = new Point2D.Double[4];
            
            // Apply rotation and projection to each vertex
            for (int i = 0; i < 4; i++) {
                
                // Rotate the 3D point according to current rotation angles
                double[] rotated = rotatePoint(faceVertices[face][i]);
                
                // Apply perspective projection effect
                double scale = 1.2 + rotated[2] / 1000.0;  // Scale based on z-depth
                
                // Project 3D point to 2D screen space
                projectedPoints[i] = new Point2D.Double(
                    rotated[0] * scale,  // Scale X by perspective factor
                    rotated[1] * scale   // Scale Y by perspective factor
                );
                
            }
            
            // Create face outline shape
            Path2D.Double facePath = new Path2D.Double();
            facePath.moveTo(projectedPoints[0].x, projectedPoints[0].y);  // Start at first point
            
            // Connect all points to form the face
            for (int i = 1; i < 4; i++) {
                facePath.lineTo(projectedPoints[i].x, projectedPoints[i].y);
            }
            facePath.closePath();  // Close the path
            
            // Draw face background
            g2d.setColor(new Color(15, 15, 15));
            g2d.fill(facePath);
            
            // Draw face border with gradient effect
            GradientPaint borderGradient = new GradientPaint(
                (float)projectedPoints[0].x, (float)projectedPoints[0].y, new Color(60, 60, 60),
                (float)projectedPoints[2].x, (float)projectedPoints[2].y, new Color(30, 30, 30)
            );
            g2d.setPaint(borderGradient);
            g2d.setStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
            g2d.draw(facePath);
            
            // Calculate cell size for 3x3 grid
            double cellSize = CUBE_SIZE / 3.0;
            
            // Draw each cell in the 3x3 grid
            for (int row = 0; row < 3; row++) {
                
                for (int col = 0; col < 3; col++) {
                    
                      drawCell(g2d, projectedPoints, row, col, cellSize, face);
                    
                }
                
            }
             
         }
         
         
         /**
         * Draws a single cell within a face
         * @param g2d Graphics context
         * @param facePoints The projected 2D points of the face corners
         * @param row Cell row (0-2)
         * @param col Cell column (0-2)
         * @param cellSize Size of each cell
         * @param face Face index
         */
         private void drawCell(Graphics2D g2d, Point2D.Double[] facePoints, int row, int col, double cellSize, int face) {
             
            // Calculate the normalized position of this cell (0-1 range within the face)
            double u1 = (col) / 3.0;     // Left edge position (0, 1/3, or 2/3)
            double v1 = (row) / 3.0;     // Top edge position (0, 1/3, or 2/3)
            double u2 = (col + 1) / 3.0; // Right edge position (1/3, 2/3, or 1)
            double v2 = (row + 1) / 3.0; // Bottom edge position (1/3, 2/3, or 1)
            
            // Adjust for gap between cells
            double gapRatio = CELL_GAP / cellSize;
            u1 += gapRatio / 3;  // Move right edge inward
            v1 += gapRatio / 3;  // Move top edge inward
            u2 -= gapRatio / 3;  // Move left edge inward
            v2 -= gapRatio / 3;  // Move bottom edge inward
            
            // Array to hold the cell's corner points
            Point2D.Double[] cellPoints = new Point2D.Double[4];
            for (int i = 0; i < 4; i++) {
                cellPoints[i] = new Point2D.Double(0, 0);
            }
            
            
            // Calculate the cell's corners using bilinear interpolation within the face
            // Top-left
            interpolate(facePoints, u1, v1, cellPoints[0]);
            // Top-right
            interpolate(facePoints, u2, v1, cellPoints[1]);
            // Bottom-right
            interpolate(facePoints, u2, v2, cellPoints[2]);
            // Bottom-left
            interpolate(facePoints, u1, v2, cellPoints[3]);
            
            
            // Create path for the cell
            Path2D.Double cellPath = new Path2D.Double();
            cellPath.moveTo(cellPoints[0].x, cellPoints[0].y);
            for (int i = 1; i < 4; i++) {
                cellPath.lineTo(cellPoints[i].x, cellPoints[i].y);
            }
            cellPath.closePath();
            
            // Get base colors from cube model
            Color baseColor = cubeModel.getCellColor(face, row, col);
            Color darkColor = cubeModel.getCellDarkColor(face, row, col);
            
            // Calculate center point for radial gradient
            Point2D.Double center = new Point2D.Double();
            center.x = (cellPoints[0].x + cellPoints[1].x + cellPoints[2].x + cellPoints[3].x) / 4;
            center.y = (cellPoints[0].y + cellPoints[1].y + cellPoints[2].y + cellPoints[3].y) / 4;
            
            // Calculate maximum distance from center to any corner (for gradient radius)
            double maxDist = 0;
            for (Point2D.Double point : cellPoints) {
                double dist = point.distance(center);
                if (dist > maxDist) {
                    maxDist = dist;
                }
            }
            
            // Create three-color radial gradient
            RadialGradientPaint gradient = new RadialGradientPaint(
                center,
                (float)maxDist * 1.2f,
                new float[]{0.0f, 0.7f, 1.0f},
                new Color[]{
                    brightenColor(baseColor),  // Lighter in center
                    baseColor,                 // Normal color in middle
                    darkColor                  // Darker at edges
                }
            );
            
            // Fill cell with gradient
            g2d.setPaint(gradient);
            g2d.fill(cellPath);
            
            // Add subtle inner shadow effect
            g2d.setColor(new Color(0, 0, 0, 40)); // 40% opacity black
            g2d.setStroke(new BasicStroke(5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
            g2d.draw(cellPath);
            
            // Add shine effect (highlight from top-left to center)
            Path2D.Double shinePath = new Path2D.Double();
            shinePath.moveTo(cellPoints[0].x, cellPoints[0].y);
            shinePath.lineTo(cellPoints[1].x, cellPoints[1].y);
            
            // Add midpoints to make highlight area look better
            double midX = (cellPoints[1].x + cellPoints[2].x) / 2;
            double midY = (cellPoints[1].y + cellPoints[2].y) / 2;
            shinePath.lineTo(midX, midY);
            
            midX = (cellPoints[0].x + cellPoints[3].x) / 2;
            midY = (cellPoints[0].y + cellPoints[3].y) / 2;
            shinePath.lineTo(midX, midY);
            shinePath.closePath();
            
            // Create highlight gradient from top-left to center
            GradientPaint shineGradient = new GradientPaint(
                (float)cellPoints[0].x, (float)cellPoints[0].y, new Color(255, 255, 255, 80),
                (float)center.x, (float)center.y, new Color(255, 255, 255, 0)
            );
            
            // Apply highlight gradient
            g2d.setPaint(shineGradient);
            g2d.fill(shinePath);
             
         }
         
         
         private void interpolate(Point2D.Double[] quad, double u, double v, Point2D.Double result) {
             
            // Bilinear interpolation on a quadrilateral
            double nu = 1.0 - u;
            double nv = 1.0 - v;
            
            // Interpolate along top and bottom edges
            double topX = nu * quad[0].x + u * quad[1].x;
            double topY = nu * quad[0].y + u * quad[1].y;
            double bottomX = nu * quad[3].x + u * quad[2].x;
            double bottomY = nu * quad[3].y + u * quad[2].y;
            
            // Interpolate between top and bottom
            result.x = nv * topX + v * bottomX;
            result.y = nv * topY + v * bottomY;
             
         }
        
        
         private Color brightenColor(Color color) {
            // Create a brighter version of the color for highlights
            float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
            float brightness = Math.min(1.0f, hsb[2] + 0.2f);
            float saturation = Math.max(0.0f, hsb[1] - 0.1f); // Slightly less saturated
            return Color.getHSBColor(hsb[0], saturation, brightness);
        }
         
        
        
        
        private double[] rotatePoint(double[] point) {
            
            double[] result = new double[3];
            double[] temp = new double[3];
            
             // Apply rotations (in radians)
            double radX = Math.toRadians(currentRotationX);
            double radY = Math.toRadians(currentRotationY);
            double radZ = Math.toRadians(currentRotationZ);
            
            // Copy original point
            System.arraycopy(point, 0, temp, 0, 3);
            
            // Rotate around X-axis
            result[0] = temp[0];
            result[1] = temp[1] * Math.cos(radX) - temp[2] * Math.sin(radX);
            result[2] = temp[1] * Math.sin(radX) + temp[2] * Math.cos(radX);
            
            // Copy result to temp
            System.arraycopy(result, 0, temp, 0, 3);
            
            // Rotate around Y-axis
            result[0] = temp[0] * Math.cos(radY) + temp[2] * Math.sin(radY);
            result[1] = temp[1];
            result[2] = -temp[0] * Math.sin(radY) + temp[2] * Math.cos(radY);
            
            // Copy result to temp
            System.arraycopy(result, 0, temp, 0, 3);
            
            // Rotate around Z-axis
            result[0] = temp[0] * Math.cos(radZ) - temp[1] * Math.sin(radZ);
            result[1] = temp[0] * Math.sin(radZ) + temp[1] * Math.cos(radZ);
            result[2] = temp[2];
            
            return result;
            
        }
        

      }
    
    

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        
         // Run the application on the Event Dispatch Thread
        SwingUtilities.invokeLater(() -> {
            try {
                // Apply a modern look and feel
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (Exception e) {
                e.printStackTrace();
            }
            
            new RubiksCube().setVisible(true);
        });
        
    }

}


  


The Final Result:

3D Rubik's Cube in Java Swing - 1

3D Rubik's Cube in Java Swing - 2

3D Rubik's Cube in Java Swing - 3



if you want the source code click on the download button below