How to Create a 3D Domino in Java Netbeans
In this Java Tutorial we will see How To Create a 3D Dominp Piece that rotates, floats, and responds to user input In Java Using Netbeans.
What We Are Gonna Use In This Project:
- Java Programming Language.- NetBeans Editor.
Project Source Code:
/**
*
* @author 1BestCsharp
*/
public class Domino3D extends JFrame{
// Rotation angles for the 3D domino
private double currentRotationX = -30; // Current X-axis rotation in degrees
private double currentRotationY = 45; // Current Y-axis rotation in degrees
// Target angles that the domino will animate towards
private double targetRotationX = -30; // Target X-axis rotation
private double targetRotationY = 45; // Target Y-axis rotation
// Animation properties
private final double ROTATION_SPEED = 0.15; // Speed of rotation animation
private boolean isAnimating = false; // Flag to track if animation is in progress
// Mouse handling properties
private boolean isDragging = false; // Flag to track if user is dragging the domino
private Point previousPoint; // Previous mouse position for drag calculations
// Domino properties
private int topValue = 6; // Number on top half of domino (1-6)
private int bottomValue = 3; // Number on bottom half of domino (1-6)
private Color dominoColor = new Color(250, 250, 250); // Main domino color
private Color dotColor = new Color(20, 20, 20); // Color of dots on domino
private Color dividerColor = new Color(40, 40, 40); // Color of center dividing line
// Domino dimensions
private static final double DOMINO_WIDTH = 120; // Width of domino
private static final double DOMINO_HEIGHT = 240; // Height of domino
private static final double DOMINO_DEPTH = 25; // Depth of domino
// Floating animation properties
private Timer animationTimer; // Timer for animation updates
private double floatOffset = 0; // Current vertical offset for floating effect
private boolean floatingUp = true; // Direction of floating animation
// Drawing panel
private DominoPanel dominoPanel; // Panel where the domino is drawn
/**
* Constructor: Sets up the application window and initializes components
*/
public Domino3D(){
// Set up the main window
setTitle("Modern Domino");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
// Create the panel where domino will be drawn
dominoPanel = new DominoPanel();
dominoPanel.setPreferredSize(new Dimension(800, 600));
dominoPanel.setBackground(new Color(15, 15, 15));
add(dominoPanel, BorderLayout.CENTER);
// Create and add control buttons
JPanel controlPanel = createControlPanel();
add(controlPanel, BorderLayout.SOUTH);
// Set up mouse event handlers
setupMouseListeners();
// Start animation timer
startAnimation();
// Finalize window setup
pack();
setLocationRelativeTo(null); // Center on screen
}
/**
* Creates the control panel with buttons
*/
private JPanel createControlPanel() {
// Create panel with dark background and padding
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 buttons for rotation
JButton rotateXButton = createControlButton("Rotate X");
JButton rotateYButton = createControlButton("Rotate Y");
// Add action to rotate around X axis
rotateXButton.addActionListener(e -> {
targetRotationX += 90; // Rotate 90 degrees around X axis
isAnimating = true;
});
// Add action to rotate around Y axis
rotateYButton.addActionListener(e -> {
targetRotationY += 90; // Rotate 90 degrees around Y axis
isAnimating = true;
});
// Add buttons to panel
panel.add(rotateXButton);
panel.add(rotateYButton);
return panel;
}
/**
* Creates a styled button with hover effects
*/
private JButton createControlButton(String text) {
// Create button with text
JButton button = new JButton(text);
// Style the button
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 hover and click effects
button.addMouseListener(new MouseAdapter() {
// Change appearance when mouse enters button area
@Override
public void mouseEntered(MouseEvent e) {
button.setBackground(new Color(80, 80, 80));
button.setCursor(new Cursor(Cursor.HAND_CURSOR));
}
// Restore appearance when mouse leaves button area
@Override
public void mouseExited(MouseEvent e) {
button.setBackground(new Color(60, 60, 60));
button.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
// Pressed
@Override
public void mousePressed(MouseEvent e) {
button.setBackground(new Color(100, 100, 100));
}
// Released
@Override
public void mouseReleased(MouseEvent e) {
button.setBackground(new Color(80, 80, 80));
}
});
return button;
}
/**
* Sets up mouse listeners for rotating the domino with drag actions
*/
private void setupMouseListeners() {
// Handle mouse press and release
dominoPanel.addMouseListener(new MouseAdapter() {
// Start tracking drag when mouse is pressed
@Override
public void mousePressed(MouseEvent e) {
isDragging = true;
previousPoint = e.getPoint(); // Store initial mouse position
dominoPanel.setCursor(new Cursor(Cursor.MOVE_CURSOR));
}
// Stop tracking drag when mouse is released
@Override
public void mouseReleased(MouseEvent e) {
isDragging = false;
dominoPanel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
});
// Handle mouse drag motion
dominoPanel.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
if (isDragging) {
Point currentPoint = e.getPoint();
// Calculate how far mouse has moved since last position
double deltaX = currentPoint.x - previousPoint.x;
double deltaY = currentPoint.y - previousPoint.y;
// Apply rotations based on mouse movement
targetRotationY += deltaX * 0.5; // Horizontal motion rotates Y
targetRotationX += deltaY * 0.5; // Vertical motion rotates X
previousPoint = currentPoint; // Update previous position
dominoPanel.repaint(); // Redraw with new rotation
}
}
});
}
/**
* Starts the animation timer for continuous updates
*/
private void startAnimation() {
// Create timer that updates every 16ms
animationTimer = new Timer(16, e -> {
// Handle floating animation (gentle up and down movement)
if (floatingUp) {
floatOffset += 0.15; // Move up
if (floatOffset >= 8) floatingUp = false; // Change direction at peak
} else {
floatOffset -= 0.15; // Move down
if (floatOffset <= -8) floatingUp = true; // Change direction at bottom
}
// Handle rotation animation
boolean needsUpdate = false;
// Smoothly animate X rotation toward target
if (Math.abs(targetRotationX - currentRotationX) > 0.1) {
currentRotationX += (targetRotationX - currentRotationX) * ROTATION_SPEED;
needsUpdate = true;
} else {
currentRotationX = targetRotationX; // Snap to exact target when close
}
// Smoothly animate Y rotation toward target
if (Math.abs(targetRotationY - currentRotationY) > 0.1) {
currentRotationY += (targetRotationY - currentRotationY) * ROTATION_SPEED;
needsUpdate = true;
} else {
currentRotationY = targetRotationY; // Snap to exact target when close
}
// Repaint if anything changed
if (needsUpdate || floatingUp || !floatingUp) {
dominoPanel.repaint();
} else if (isAnimating) {
isAnimating = false; // Animation complete
}
});
animationTimer.start(); // Start the timer
}
/**
* Inner class for drawing the 3D domino
* This handles all the 3D rendering of the domino
*/
class DominoPanel extends JPanel {
// 3D coordinates of domino vertices for each face
private double[][][] faceVertices;
// Constants for face indices
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;
// Array of dot positions for each domino value (1-6)
private final Point2D.Double[][] dotPositions = {
{}, // 0 (not used)
{new Point2D.Double(0.5, 0.5)}, // 1: center dot
{new Point2D.Double(0.25, 0.25), new Point2D.Double(0.75, 0.75)}, // 2: diagonal dots
{new Point2D.Double(0.25, 0.25), new Point2D.Double(0.5, 0.5), new Point2D.Double(0.75, 0.75)}, // 3: diagonal + center
{new Point2D.Double(0.25, 0.25), new Point2D.Double(0.25, 0.75),
new Point2D.Double(0.75, 0.25), new Point2D.Double(0.75, 0.75)}, // 4: corners
{new Point2D.Double(0.25, 0.25), new Point2D.Double(0.25, 0.75), new Point2D.Double(0.5, 0.5),
new Point2D.Double(0.75, 0.25), new Point2D.Double(0.75, 0.75)}, // 5: corners + center
{new Point2D.Double(0.25, 0.25), new Point2D.Double(0.25, 0.5), new Point2D.Double(0.25, 0.75),
new Point2D.Double(0.75, 0.25), new Point2D.Double(0.75, 0.5), new Point2D.Double(0.75, 0.75)} // 6: 2 columns of 3
};
/**
* Constructor: Initialize the panel and vertex coordinates
*/
public DominoPanel() {
initFaceVertices(); // Set up 3D coordinates
setCursor(new Cursor(Cursor.HAND_CURSOR)); // Show hand cursor by default
}
/**
* Initialize the 3D coordinates for each face of the domino
* This sets up the positions of all vertices in 3D space
*/
private void initFaceVertices() {
// Create 3D array: [face][vertex][coordinate]
faceVertices = new double[6][4][3];
// Calculate half dimensions for easier positioning
double w = DOMINO_WIDTH / 2.0;
double h = DOMINO_HEIGHT / 2.0;
double d = DOMINO_DEPTH / 2.0;
// FRONT face vertices (clockwise from top-left)
faceVertices[FRONT][0] = new double[]{-w, -h, d}; // Top-left
faceVertices[FRONT][1] = new double[]{w, -h, d}; // Top-right
faceVertices[FRONT][2] = new double[]{w, h, d}; // Bottom-right
faceVertices[FRONT][3] = new double[]{-w, h, d}; // Bottom-left
// BACK face vertices (clockwise from top-left)
faceVertices[BACK][0] = new double[]{w, -h, -d}; // Top-left
faceVertices[BACK][1] = new double[]{-w, -h, -d}; // Top-right
faceVertices[BACK][2] = new double[]{-w, h, -d}; // Bottom-right
faceVertices[BACK][3] = new double[]{w, h, -d}; // Bottom-left
// RIGHT face vertices
faceVertices[RIGHT][0] = new double[]{w, -h, d}; // Top-front
faceVertices[RIGHT][1] = new double[]{w, -h, -d}; // Top-back
faceVertices[RIGHT][2] = new double[]{w, h, -d}; // Bottom-back
faceVertices[RIGHT][3] = new double[]{w, h, d}; // Bottom-front
// LEFT face vertices
faceVertices[LEFT][0] = new double[]{-w, -h, -d}; // Top-back
faceVertices[LEFT][1] = new double[]{-w, -h, d}; // Top-front
faceVertices[LEFT][2] = new double[]{-w, h, d}; // Bottom-front
faceVertices[LEFT][3] = new double[]{-w, h, -d}; // Bottom-back
// TOP face vertices
faceVertices[TOP][0] = new double[]{-w, -h, -d}; // Left-back
faceVertices[TOP][1] = new double[]{w, -h, -d}; // Right-back
faceVertices[TOP][2] = new double[]{w, -h, d}; // Right-front
faceVertices[TOP][3] = new double[]{-w, -h, d}; // Left-front
// BOTTOM face vertices
faceVertices[BOTTOM][0] = new double[]{-w, h, d}; // Left-front
faceVertices[BOTTOM][1] = new double[]{w, h, d}; // Right-front
faceVertices[BOTTOM][2] = new double[]{w, h, -d}; // Right-back
faceVertices[BOTTOM][3] = new double[]{-w, h, -d}; // Left-back
}
/**
* Paint the domino on the panel
* This is called automatically whenever the panel needs to be redrawn
*/
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// Enable antialiasing for smoother edges
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
// Draw background gradient
GradientPaint bgGradient = new GradientPaint(
0, 0, new Color(25, 25, 30), // Start color (top)
getWidth(), getHeight(), new Color(10, 10, 15) // End color (bottom)
);
g2d.setPaint(bgGradient);
g2d.fillRect(0, 0, getWidth(), getHeight()); // Fill entire panel
// Find center of panel
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
// Apply floating animation effect
double scaleFloat = 1.0 + Math.abs(floatOffset) / 100.0; // Subtle scale change
// Move to center and apply floating offset
g2d.translate(centerX, centerY + floatOffset);
g2d.scale(scaleFloat, scaleFloat); // Scale slightly based on float position
// Calculate which faces are visible from current angle
int[] faceVisibility = calculateFaceVisibility();
// Draw faces in back-to-front order
for (int i = 0; i < 6; i++) {
drawFace(g2d, faceVisibility[i]);
}
}
/**
* Calculate which order to draw faces based on current rotation
* This is important for correct depth perception (back faces drawn first)
*/
private int[] calculateFaceVisibility() {
// Calculate center point of each face after rotation
double[][] faceCenters = new double[6][3];
for (int face = 0; face < 6; face++) {
double[] center = {0, 0, 0};
// Average the 4 corners to find center
for (int i = 0; i < 4; i++) {
center[0] += faceVertices[face][i][0]; // x
center[1] += faceVertices[face][i][1]; // y
center[2] += faceVertices[face][i][2]; // z
}
// Divide by 4 to get average
center[0] /= 4;
center[1] /= 4;
center[2] /= 4;
// Apply current rotation to center point
faceCenters[face] = rotatePoint(center);
}
// Sort faces by z-coordinate (back to front)
int[] faceOrder = {0, 1, 2, 3, 4, 5};
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 if wrong order
int temp = faceOrder[i];
faceOrder[i] = faceOrder[j];
faceOrder[j] = temp;
}
}
}
return faceOrder;
}
/**
* Draw a single face of the domino
* This handles the drawing of one complete face with all its details
*/
private void drawFace(Graphics2D g2d, int face) {
// Project 3D points to 2D for drawing
Point2D.Double[] projectedPoints = new Point2D.Double[4];
for (int i = 0; i < 4; i++) {
// Apply rotation to each point
double[] rotated = rotatePoint(faceVertices[face][i]);
// Apply perspective scale (farther = smaller)
double scale = 1.2 + rotated[2] / 1000.0;
// Convert to 2D coordinates
projectedPoints[i] = new Point2D.Double(
rotated[0] * scale,
rotated[1] * scale
);
}
// Create path for the face
Path2D.Double facePath = new Path2D.Double();
facePath.moveTo(projectedPoints[0].x, projectedPoints[0].y);
for (int i = 1; i < 4; i++) {
facePath.lineTo(projectedPoints[i].x, projectedPoints[i].y);
}
facePath.closePath();
// Draw differently based on which face it is
if (face == FRONT || face == BACK) {
// Draw the main face with gradient
GradientPaint faceGradient = new GradientPaint(
(float)projectedPoints[0].x, (float)projectedPoints[0].y,
brightenColor(dominoColor, 0.1f), // Lighter at top
(float)projectedPoints[2].x, (float)projectedPoints[2].y,
darkenColor(dominoColor, 0.1f) // Darker at bottom
);
g2d.setPaint(faceGradient);
g2d.fill(facePath);
// Draw the dividing line between top and bottom halves
Point2D.Double leftMiddle = new Point2D.Double(
(projectedPoints[0].x + projectedPoints[3].x) / 2, // Middle of left edge
(projectedPoints[0].y + projectedPoints[3].y) / 2
);
Point2D.Double rightMiddle = new Point2D.Double(
(projectedPoints[1].x + projectedPoints[2].x) / 2, // Middle of right edge
(projectedPoints[1].y + projectedPoints[2].y) / 2
);
// Draw divider line
g2d.setColor(dividerColor);
g2d.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.draw(new Line2D.Double(leftMiddle, rightMiddle));
if (face == FRONT) {
// Draw dots on front face
drawDots(g2d, projectedPoints[0], projectedPoints[1], leftMiddle, topValue);
drawDots(g2d, leftMiddle, rightMiddle, projectedPoints[3], bottomValue);
} else {
// Draw dots on back face (reversed order)
drawDots(g2d, projectedPoints[0], projectedPoints[1], leftMiddle, bottomValue);
drawDots(g2d, leftMiddle, rightMiddle, projectedPoints[3], topValue);
}
} else {
// Draw side faces with gradient
GradientPaint faceGradient = new GradientPaint(
(float)projectedPoints[0].x, (float)projectedPoints[0].y,
brightenColor(dominoColor, 0.1f),
(float)projectedPoints[2].x, (float)projectedPoints[2].y,
darkenColor(dominoColor, 0.1f)
);
g2d.setPaint(faceGradient);
g2d.fill(facePath);
}
// Draw face border
g2d.setColor(new Color(40, 40, 40, 100));
g2d.setStroke(new BasicStroke(1.5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.draw(facePath);
}
/**
* Draw the dots on a face section
* This places the dots in the correct positions on each half of the domino
*/
private void drawDots(Graphics2D g2d, Point2D.Double topLeft, Point2D.Double topRight,
Point2D.Double bottomLeft, int value) {
if (value < 1 || value > 6) return; // Invalid value
// Calculate dot size based on face dimensions
double width = topRight.distance(topLeft);
double height = bottomLeft.distance(topLeft);
double dotSize = Math.min(width, height) * 0.15; // Size relative to face
// Draw each dot for this value
for (Point2D.Double pos : dotPositions[value]) {
// Calculate dot position using relative coordinates
double x = topLeft.x + (topRight.x - topLeft.x) * pos.x + (bottomLeft.x - topLeft.x) * pos.y;
double y = topLeft.y + (topRight.y - topLeft.y) * pos.x + (bottomLeft.y - topLeft.y) * pos.y;
// Create gradient for dot
RadialGradientPaint dotGradient = new RadialGradientPaint(
new Point2D.Double(x - dotSize/4, y - dotSize/4), // Offset for 3D effect
(float)dotSize,
new float[]{0.0f, 1.0f},
new Color[]{brightenColor(dotColor, 0.3f), dotColor} // Lighter center, darker edges
);
// Draw dot
g2d.setPaint(dotGradient);
g2d.fill(new Ellipse2D.Double(x - dotSize/2, y - dotSize/2, dotSize, dotSize));
// Add subtle border to dot
g2d.setColor(new Color(0, 0, 0, 50));
g2d.draw(new Ellipse2D.Double(x - dotSize/2, y - dotSize/2, dotSize, dotSize));
}
}
/**
* Apply rotation transformations to a 3D point
* This converts a 3D point based on the current rotation angles
*/
private double[] rotatePoint(double[] point) {
double[] result = new double[3];
double[] temp = new double[3];
// Convert degrees to radians for math functions
double radX = Math.toRadians(currentRotationX);
double radY = Math.toRadians(currentRotationY);
// Copy point
System.arraycopy(point, 0, temp, 0, 3);
// X rotation
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);
System.arraycopy(result, 0, temp, 0, 3);
// Y rotation
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);
System.arraycopy(result, 0, temp, 0, 3);
return result;
}
private Color brightenColor(Color color, float factor) {
float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
float brightness = Math.min(1.0f, hsb[2] + factor);
return Color.getHSBColor(hsb[0], hsb[1], brightness);
}
private Color darkenColor(Color color, float factor) {
float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
float brightness = Math.max(0.0f, hsb[2] - factor);
return Color.getHSBColor(hsb[0], hsb[1], brightness);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
new Domino3D().setVisible(true);
});
}
}
The Final Result:
More Java Projects:
Download Projects Source Code





