Portrait of Marius Naasen Hi, I am Marius

Visualization of a 3D Box

After taking a course in linear algebra during my bachelor studies, I was curious about how we can leverage linear algebra for computer graphics. There was a small section in the course about this, but we never went deeper into it. So I decided to try it on my own.

For the codebase of this project along with three Unity demo builds, please refer to the GitHub repository.

A GIF of a rotating 3D box

Off-Diagonal Rotation: Illustrates rotation when an off-diagonal matrix that consists of rotation in all three dimension axis is applied to the box.

Project Overview

This project involves my implementation of rendering a box in 3D space using linear algebra. Through demonstrations and reviews of important code snippets, we will explore how to apply rotation matrices to a box to visualize its rotation in a virtual environment.

The core of this project involves the following.

  • 3D Space Rendering: We represent a cube using 8 vertices and place a viewer (camera) in the 3D space. The objective is to render the box as seen by the viewer on a 2D plane (the computer screen).
  • Rotation Matrices: By applying mathematical rotation matrices, we can rotate the box around its axes, allowing us to visualize its rotation dynamically.

Code Overview

In this section we will go over the most crucial parts of the code to give an overview.

Box.java Script

The Box.java class is responsible for drawing the 3D box and applying the rotation matrices.

  • Constructor: Here we initialize the 8 vertices making up the box. These points will later be altered using matrix multiplication.
  • RotateYAxis(): This is a simple standalone method for rotating the box vertices around the \(y\) axis.
  • PaintComponent(): The box is drawn within this method. By inspecting the projected vertices of the box, we draw a green line from each vertex to every other vertex. This is not the most efficient approach as there will be redundancies; however, given the number of vertices, it is a simple and feasible solution.

import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;

public class Box extends JPanel {
    private List vertices = new ArrayList<>();
    private List projectedVertices = new ArrayList<>();
    private final Camera camera;

    // Constructor to initialize the box with default vertices
    public Box(Camera camera) {
        this.camera = camera;
        vertices.add(new Vector(-0.3, 0.3, -0.3, 1));
        vertices.add(new Vector(0.3, 0.3, -0.3, 1));
        vertices.add(new Vector(-0.3, 0.3, 0.3, 1));
        vertices.add(new Vector(0.3, 0.3, 0.3, 1));        
        vertices.add(new Vector(-0.3, -0.3, -0.3, 1));
        vertices.add(new Vector(0.3, -0.3, -0.3, 1));
        vertices.add(new Vector(-0.3, -0.3, 0.3, 1));
        vertices.add(new Vector(0.3, -0.3, 0.3, 1));
    }

    // Method to apply rotation around the Y-axis
    public void RotateYAxis(double angle) {
        double[][] rotationMatrix = {
            {Math.cos(angle), 0, Math.sin(angle), 0},
            {0, 1, 0, 0},
            {-Math.sin(angle), 0, Math.cos(angle), 0},
            {0, 0, 0, 1}
        };
        Matrix rotation = new Matrix(rotationMatrix);
        List newVertices = new ArrayList<>();
        for (Vector vertex : vertices) {
            newVertices.add(rotation.multiply(vertex));
        }
        vertices = newVertices;
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        calculateProjection();
        calculateIntoScreen();
        g.setColor(Color.GREEN);
        this.setBackground(Color.BLACK);

        // Draw vertices and edges of the box
        for (int i = 0; i < projectedVertices.size(); i++) {
            g.setColor(Color.RED);
            g.drawOval((int)projectedVertices.get(i).getValue(0) - 5, (int)projectedVertices.get(i).getValue(1) - 5, 10, 10);
            for (int j = 0; j < projectedVertices.size(); j++) {
                if (i == j) continue;
                g.setColor(Color.GREEN);
                Vector v1 = projectedVertices.get(i);
                Vector v2 = projectedVertices.get(j);
                g.drawLine((int)v1.getValue(0), (int)v1.getValue(1), (int)v2.getValue(0), (int)v2.getValue(1));
            }
        }
    }

    private void calculateProjection() {
        // Code to project 3D points to 2D screen
    }

    private void calculateIntoScreen() {
        // Code to transform 3D points to fit the screen size
    }
}
                        

MainMovement.java Script

One of the main classes is found in MainMovement.java. By running this class, we setup the necessities like the camera, the box and parameters. The unique part about this class is that it allows for keyboard inputs to rotate the box interactively. Note that we are only interesed when the key is pressed down, thus there is no implementation for the methods keyReleased() and keyTyped().


import javax.swing.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.List;

public class MainMovement {
    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setSize(1100, 1100);
        frame.setResizable(false);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Initialize camera position
        List cameraPos = new ArrayList<>();
        cameraPos.add(0.0);
        cameraPos.add(0.0);
        cameraPos.add(1.0);
        Camera camera = new Camera(cameraPos);

        double speed = 1.5;
        Box box = new Box(camera);
        frame.add(box);

        frame.addKeyListener(new KeyListener() {
            @Override
            public void keyPressed(KeyEvent e) {
                int key = e.getKeyCode();
                switch (key) {
                    case KeyEvent.VK_W:
                        box.RotateXAxis(Math.toRadians(speed)); // Rotate up
                        break;
                    case KeyEvent.VK_S:
                        box.RotateXAxis(Math.toRadians(-speed)); // Rotate down
                        break;
                    case KeyEvent.VK_A:
                        box.RotateYAxis(Math.toRadians(speed)); // Rotate left
                        break;
                    case KeyEvent.VK_D:
                        box.RotateYAxis(Math.toRadians(-speed)); // Rotate right
                        break;
                }
                frame.repaint();
            }

            @Override
            public void keyReleased(KeyEvent e) {}

            @Override
            public void keyTyped(KeyEvent e) {}
        });
    }
}
                        

Matrix.java Script

The Matrix.java class provides the necessary mathematical operations for handling matrices, such as matrix multiplication with a vector and matrix multiplication with another matrix. The methods for vector and matrix multiplication are implemented from scratch, but any matrix library would suffice.

import java.util.ArrayList;
import java.util.List;

public class Matrix {
    private List matrix;
    private int width;
    private int height;

    // Constructor
    public Matrix(List vectors) {
        this.matrix = vectors;
        this.height = vectors.size();
        this.width = vectors.get(0).getDim();
    }

    // Method for matrix multiplication with a vector
    public Vector multiply(Vector v) {
        if (v.getDim() != width) throw new IllegalArgumentException("v must be same width as matrix");
        List newVectorValues = new ArrayList<>();
        for (int i = 0; i < height; i++) {
            double sum = 0;
            for (int j = 0; j < width; j++) {
                sum += v.getValue(j) * matrix.get(j).getValue(i);
            }
            newVectorValues.add(sum);
        }
        return new Vector(newVectorValues);
    }

    // Method for matrix multiplication
    public Matrix multiply(Matrix other) {
        if (other.getDim() != width) throw new IllegalArgumentException("other must have dim == this.width");
        List newMatrixVectors = new ArrayList<>();
        for (int i = 0; i < other.getWidth(); i++ ) {
            newMatrixVectors.add(this.multiply(other.getVectorAt(i)));
        }
        return new Matrix(newMatrixVectors);
    }
}
                        

Conclusion

This project provided insights into the application of linear algebra in computer graphics. By representing a 3D box and using rotation matrices, we can visualize dynamic rotations and transformations, demonstrating the practical use of mathematical concepts in programming.