Java制作软光栅化渲染器_艾孜尔江撰

  1. Main.java :

import java.io.IOException;

/**
 * The sole purpose of this class is to hold the main method.
 *
 * Any other use should be placed in a separate class
 */
public class Main
{
	// Lazy exception handling here. You can do something more interesting 
	// depending on what you're doing
	public static void main(String[] args) throws IOException
	{
		Display display = new Display(800, 600, "Software Rendering");
		RenderContext target = display.GetFrameBuffer();

		Bitmap texture = new Bitmap("./res/bricks.jpg");
		Bitmap texture2 = new Bitmap("./res/bricks2.jpg");
		Mesh monkeyMesh = new Mesh("./res/smoothMonkey0.obj");
		Transform monkeyTransform = new Transform(new Vector4f(0,0.0f,3.0f));

		Mesh terrainMesh = new Mesh("./res/terrain2.obj");
		Transform terrainTransform = new Transform(new Vector4f(0,-1.0f,0.0f));

		Camera camera = new Camera(new Matrix4f().InitPerspective((float)Math.toRadians(70.0f),
					   	(float)target.GetWidth()/(float)target.GetHeight(), 0.1f, 1000.0f));
		
		float rotCounter = 0.0f;
		long previousTime = System.nanoTime();
		while(true)
		{
			long currentTime = System.nanoTime();
			float delta = (float)((currentTime - previousTime)/1000000000.0);
			previousTime = currentTime;

			camera.Update(display.GetInput(), delta);
			Matrix4f vp = camera.GetViewProjection();

			monkeyTransform = monkeyTransform.Rotate(new Quaternion(new Vector4f(0,1,0), delta));

			target.Clear((byte)0x00);
			target.ClearDepthBuffer();
			monkeyMesh.Draw(target, vp, monkeyTransform.GetTransformation(), texture2);
			terrainMesh.Draw(target, vp, terrainTransform.GetTransformation(), texture);

			display.SwapBuffers();
		}
	}
}
  1. Bitmap.java :
public class Bitmap
{
	/** The width, in pixels, of the image */
	private final int  m_width;
	/** The height, in pixels, of the image */
	private final int  m_height;
	/** Every pixel component in the image */
	private final byte m_components[];

	/** Basic getter */
	public int GetWidth() { return m_width; }
	/** Basic getter */
	public int GetHeight() { return m_height; }

	public byte GetComponent(int index) { return m_components[index]; }

	/**
	 * Creates and initializes a Bitmap.
	 *
	 * @param width The width, in pixels, of the image.
	 * @param height The height, in pixels, of the image.
	 */
	public Bitmap(int width, int height)
	{
		m_width      = width;
		m_height     = height;
		m_components = new byte[m_width * m_height * 4];
	}

	public Bitmap(String fileName) throws IOException
	{
		int width = 0;
		int height = 0;
		byte[] components = null;

		BufferedImage image = ImageIO.read(new File(fileName));

		width = image.getWidth();
		height = image.getHeight();

		int imgPixels[] = new int[width * height];
		image.getRGB(0, 0, width, height, imgPixels, 0, width);
		components = new byte[width * height * 4];

		for(int i = 0; i < width * height; i++)
		{
			int pixel = imgPixels[i];

			components[i * 4]     = (byte)((pixel >> 24) & 0xFF); // A
			components[i * 4 + 1] = (byte)((pixel      ) & 0xFF); // B
			components[i * 4 + 2] = (byte)((pixel >> 8 ) & 0xFF); // G
			components[i * 4 + 3] = (byte)((pixel >> 16) & 0xFF); // R
		}

		m_width = width;
		m_height = height;
		m_components = components;
	}

	/**
	 * Sets every pixel in the bitmap to a specific shade of grey.
	 *
	 * @param shade The shade of grey to use. 0 is black, 255 is white.
	 */
	public void Clear(byte shade)
	{
		Arrays.fill(m_components, shade);
	}

	/**
	 * Sets the pixel at (x, y) to the color specified by (a,b,g,r).
	 *
	 * @param x Pixel location in X
	 * @param y Pixel location in Y
	 * @param a Alpha component
	 * @param b Blue component
	 * @param g Green component
	 * @param r Red component
	 */
	public void DrawPixel(int x, int y, byte a, byte b, byte g, byte r)
	{
		int index = (x + y * m_width) * 4;
		m_components[index    ] = a;
		m_components[index + 1] = b;
		m_components[index + 2] = g;
		m_components[index + 3] = r;
	}

	public void CopyPixel(int destX, int destY, int srcX, int srcY, Bitmap src, float lightAmt)
	{
		int destIndex = (destX + destY * m_width) * 4;
		int srcIndex = (srcX + srcY * src.GetWidth()) * 4;
		
		m_components[destIndex    ] = (byte)((src.GetComponent(srcIndex) & 0xFF) * lightAmt);
		m_components[destIndex + 1] = (byte)((src.GetComponent(srcIndex + 1) & 0xFF) * lightAmt);
		m_components[destIndex + 2] = (byte)((src.GetComponent(srcIndex + 2) & 0xFF) * lightAmt);
		m_components[destIndex + 3] = (byte)((src.GetComponent(srcIndex + 3) & 0xFF) * lightAmt);
	}

	/**
	 * Copies the Bitmap into a BGR byte array.
	 *
	 * @param dest The byte array to copy into.
	 */
	public void CopyToByteArray(byte[] dest)
	{
		for(int i = 0; i < m_width * m_height; i++)
		{
			dest[i * 3    ] = m_components[i * 4 + 1];
			dest[i * 3 + 1] = m_components[i * 4 + 2];
			dest[i * 3 + 2] = m_components[i * 4 + 3];
		}
	}
}
  1. Camera.java :
import java.awt.event.KeyEvent;

public class Camera
{
	private static final Vector4f Y_AXIS = new Vector4f(0,1,0);

	private Transform m_transform;
	private Matrix4f m_projection;

	private Transform GetTransform()
	{
		return m_transform;
	}

	public Camera(Matrix4f projection)
	{
		this.m_projection = projection;
		this.m_transform = new Transform();
	}

	public Matrix4f GetViewProjection()
	{
		Matrix4f cameraRotation = GetTransform().GetTransformedRot().Conjugate().ToRotationMatrix();
		Vector4f cameraPos = GetTransform().GetTransformedPos().Mul(-1);

		Matrix4f cameraTranslation = new Matrix4f().InitTranslation(cameraPos.GetX(), cameraPos.GetY(), cameraPos.GetZ());

		return m_projection.Mul(cameraRotation.Mul(cameraTranslation));
	}

	public void Update(Input input, float delta)
	{
		// Speed and rotation amounts are hardcoded here.
		// In a more general system, you might want to have them as variables.
		final float sensitivityX = 2.66f * delta;
		final float sensitivityY = 2.0f * delta;
		final float movAmt = 5.0f * delta;

		// Similarly, input keys are hardcoded here.
		// As before, in a more general system, you might want to have these as variables.
		if(input.GetKey(KeyEvent.VK_W))
			Move(GetTransform().GetRot().GetForward(), movAmt);
		if(input.GetKey(KeyEvent.VK_S))
			Move(GetTransform().GetRot().GetForward(), -movAmt);
		if(input.GetKey(KeyEvent.VK_A))
			Move(GetTransform().GetRot().GetLeft(), movAmt);
		if(input.GetKey(KeyEvent.VK_D))
			Move(GetTransform().GetRot().GetRight(), movAmt);
		
		if(input.GetKey(KeyEvent.VK_RIGHT))
			Rotate(Y_AXIS, sensitivityX);
		if(input.GetKey(KeyEvent.VK_LEFT))
			Rotate(Y_AXIS, -sensitivityX);
		if(input.GetKey(KeyEvent.VK_DOWN))
			Rotate(GetTransform().GetRot().GetRight(), sensitivityY);
		if(input.GetKey(KeyEvent.VK_UP))
			Rotate(GetTransform().GetRot().GetRight(), -sensitivityY);
	}

	private void Move(Vector4f dir, float amt)
	{
		m_transform = GetTransform().SetPos(GetTransform().GetPos().Add(dir.Mul(amt)));
	}

	private void Rotate(Vector4f axis, float angle)
	{
		m_transform = GetTransform().Rotate(new Quaternion(axis, angle));
	}
}

  1. Display.java :

import java.awt.Canvas;

import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.awt.image.BufferStrategy;
import java.awt.image.DataBufferByte;
import javax.swing.JFrame;

/**
 * Represents a window that can be drawn in using a software renderer.
 */
public class Display extends Canvas
{
	/** The window being used for display */
	private final JFrame         m_frame;
	/** The bitmap representing the final image to display */
	private final RenderContext  m_frameBuffer;
	/** Used to display the framebuffer in the window */
	private final BufferedImage  m_displayImage;
	/** The pixels of the display image, as an array of byte components */
	private final byte[]         m_displayComponents;
	/** The buffers in the Canvas */
	private final BufferStrategy m_bufferStrategy;
	/** A graphics object that can draw into the Canvas's buffers */
	private final Graphics       m_graphics;

	private final Input          m_input;

	public RenderContext GetFrameBuffer() { return m_frameBuffer; }
	public Input GetInput() { return m_input; }

	/**
	 * Creates and initializes a new display.
	 *
	 * @param width  How wide the display is, in pixels.
	 * @param height How tall the display is, in pixels.
	 * @param title  The text displayed in the window's title bar.
	 */
	public Display(int width, int height, String title)
	{
		//Set the canvas's preferred, minimum, and maximum size to prevent
		//unintentional resizing.
		Dimension size = new Dimension(width, height);
		setPreferredSize(size);
		setMinimumSize(size);
		setMaximumSize(size);

		//Creates images used for display.
		m_frameBuffer = new RenderContext(width, height);
		m_displayImage = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
		m_displayComponents = 
			((DataBufferByte)m_displayImage.getRaster().getDataBuffer()).getData();

		m_frameBuffer.Clear((byte)0x80);
		m_frameBuffer.DrawPixel(100, 100, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0xFF);

		//Create a JFrame designed specifically to show this Display.
		m_frame = new JFrame();
		m_frame.add(this);
		m_frame.pack();
		m_frame.setResizable(false);
		m_frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		m_frame.setLocationRelativeTo(null);
		m_frame.setTitle(title);
		m_frame.setSize(width, height);
		m_frame.setVisible(true);

		//Allocates 1 display buffer, and gets access to it via the buffer
		//strategy and a graphics object for drawing into it.
		createBufferStrategy(1);
		m_bufferStrategy = getBufferStrategy();
		m_graphics = m_bufferStrategy.getDrawGraphics();
		
		m_input = new Input();
		addKeyListener(m_input);
		addFocusListener(m_input);
		addMouseListener(m_input);
		addMouseMotionListener(m_input);

		setFocusable(true);
		requestFocus();
	}

	/**
	 * Displays in the window.
	 */
	public void SwapBuffers()
	{
		//Display components should be the byte array used for displayImage's pixels.
		//Therefore, this call should effectively copy the frameBuffer into the
		//displayImage.
		m_frameBuffer.CopyToByteArray(m_displayComponents);
		m_graphics.drawImage(m_displayImage, 0, 0, 
			m_frameBuffer.GetWidth(), m_frameBuffer.GetHeight(), null);
		m_bufferStrategy.show();
	}
}

  1. Edge.java :
public class Edge
{
	private float m_x;
	private float m_xStep;
	private int m_yStart;
	private int m_yEnd;
	private float m_texCoordX;
	private float m_texCoordXStep;
	private float m_texCoordY;
	private float m_texCoordYStep;
	private float m_oneOverZ;
	private float m_oneOverZStep;
	private float m_depth;
	private float m_depthStep;
	private float m_lightAmt;
	private float m_lightAmtStep;

	public float GetX() { return m_x; }
	public int GetYStart() { return m_yStart; }
	public int GetYEnd() { return m_yEnd; }
	public float GetTexCoordX() { return m_texCoordX; }
	public float GetTexCoordY() { return m_texCoordY; }
	public float GetOneOverZ() { return m_oneOverZ; }
	public float GetDepth() { return m_depth; }
	public float GetLightAmt() { return m_lightAmt; }

	public Edge(Gradients gradients, Vertex minYVert, Vertex maxYVert, int minYVertIndex)
	{
		m_yStart = (int)Math.ceil(minYVert.GetY());
		m_yEnd = (int)Math.ceil(maxYVert.GetY());

		float yDist = maxYVert.GetY() - minYVert.GetY();
		float xDist = maxYVert.GetX() - minYVert.GetX();

		float yPrestep = m_yStart - minYVert.GetY();
		m_xStep = (float)xDist/(float)yDist;
		m_x = minYVert.GetX() + yPrestep * m_xStep;
		float xPrestep = m_x - minYVert.GetX();

		m_texCoordX = gradients.GetTexCoordX(minYVertIndex) +
			gradients.GetTexCoordXXStep() * xPrestep +
			gradients.GetTexCoordXYStep() * yPrestep;
		m_texCoordXStep = gradients.GetTexCoordXYStep() + gradients.GetTexCoordXXStep() * m_xStep;

		m_texCoordY = gradients.GetTexCoordY(minYVertIndex) +
			gradients.GetTexCoordYXStep() * xPrestep +
			gradients.GetTexCoordYYStep() * yPrestep;
		m_texCoordYStep = gradients.GetTexCoordYYStep() + gradients.GetTexCoordYXStep() * m_xStep;

		m_oneOverZ = gradients.GetOneOverZ(minYVertIndex) +
			gradients.GetOneOverZXStep() * xPrestep +
			gradients.GetOneOverZYStep() * yPrestep;
		m_oneOverZStep = gradients.GetOneOverZYStep() + gradients.GetOneOverZXStep() * m_xStep;

		m_depth = gradients.GetDepth(minYVertIndex) +
			gradients.GetDepthXStep() * xPrestep +
			gradients.GetDepthYStep() * yPrestep;
		m_depthStep = gradients.GetDepthYStep() + gradients.GetDepthXStep() * m_xStep;

		m_lightAmt = gradients.GetLightAmt(minYVertIndex) +
			gradients.GetLightAmtXStep() * xPrestep +
			gradients.GetLightAmtYStep() * yPrestep;
		m_lightAmtStep = gradients.GetLightAmtYStep() + gradients.GetLightAmtXStep() * m_xStep;
	}

	public void Step()
	{
		m_x += m_xStep;
		m_texCoordX += m_texCoordXStep;
		m_texCoordY += m_texCoordYStep;
		m_oneOverZ += m_oneOverZStep;
		m_depth += m_depthStep;
		m_lightAmt += m_lightAmtStep;
	}
}
  1. Gradients.java :
public class Gradients
{
	private float[] m_texCoordX;
	private float[] m_texCoordY;
	private float[] m_oneOverZ;
	private float[] m_depth;
	private float[] m_lightAmt;

	private float m_texCoordXXStep;
	private float m_texCoordXYStep;
	private float m_texCoordYXStep;
	private float m_texCoordYYStep;
	private float m_oneOverZXStep;
	private float m_oneOverZYStep;
	private float m_depthXStep;
	private float m_depthYStep;
	private float m_lightAmtXStep;
	private float m_lightAmtYStep;

	public float GetTexCoordX(int loc) { return m_texCoordX[loc]; }
	public float GetTexCoordY(int loc) { return m_texCoordY[loc]; }
	public float GetOneOverZ(int loc) { return m_oneOverZ[loc]; }
	public float GetDepth(int loc) { return m_depth[loc]; }
	public float GetLightAmt(int loc) { return m_lightAmt[loc]; }

	public float GetTexCoordXXStep() { return m_texCoordXXStep; }
	public float GetTexCoordXYStep() { return m_texCoordXYStep; }
	public float GetTexCoordYXStep() { return m_texCoordYXStep; }
	public float GetTexCoordYYStep() { return m_texCoordYYStep; }
	public float GetOneOverZXStep() { return m_oneOverZXStep; }
	public float GetOneOverZYStep() { return m_oneOverZYStep; }
	public float GetDepthXStep() { return m_depthXStep; }
	public float GetDepthYStep() { return m_depthYStep; }
	public float GetLightAmtXStep() { return m_lightAmtXStep; }
	public float GetLightAmtYStep() { return m_lightAmtYStep; }

	private float CalcXStep(float[] values, Vertex minYVert, Vertex midYVert,
			Vertex maxYVert, float oneOverdX)
	{
		return
			(((values[1] - values[2]) *
			(minYVert.GetY() - maxYVert.GetY())) -
			((values[0] - values[2]) *
			(midYVert.GetY() - maxYVert.GetY()))) * oneOverdX;
	}

	private float CalcYStep(float[] values, Vertex minYVert, Vertex midYVert,
			Vertex maxYVert, float oneOverdY)
	{
		return
			(((values[1] - values[2]) *
			(minYVert.GetX() - maxYVert.GetX())) -
			((values[0] - values[2]) *
			(midYVert.GetX() - maxYVert.GetX()))) * oneOverdY;
	}

	private float Saturate(float val)
	{
		if(val > 1.0f)
		{
			return 1.0f;
		}
		if(val < 0.0f)
		{
			return 0.0f;
		}
		return val;
	}

	public Gradients(Vertex minYVert, Vertex midYVert, Vertex maxYVert)
	{
		float oneOverdX = 1.0f /
			(((midYVert.GetX() - maxYVert.GetX()) *
			(minYVert.GetY() - maxYVert.GetY())) -
			((minYVert.GetX() - maxYVert.GetX()) *
			(midYVert.GetY() - maxYVert.GetY())));

		float oneOverdY = -oneOverdX;

		m_oneOverZ = new float[3];
		m_texCoordX = new float[3];
		m_texCoordY = new float[3];
		m_depth = new float[3];
		m_lightAmt = new float[3];

		m_depth[0] = minYVert.GetPosition().GetZ();
		m_depth[1] = midYVert.GetPosition().GetZ();
		m_depth[2] = maxYVert.GetPosition().GetZ();

		Vector4f lightDir = new Vector4f(0,0,1);
		m_lightAmt[0] = Saturate(minYVert.GetNormal().Dot(lightDir)) * 0.9f + 0.1f;
		m_lightAmt[1] = Saturate(midYVert.GetNormal().Dot(lightDir)) * 0.9f + 0.1f;
		m_lightAmt[2] = Saturate(maxYVert.GetNormal().Dot(lightDir)) * 0.9f + 0.1f;

		// Note that the W component is the perspective Z value;
		// The Z component is the occlusion Z value
		m_oneOverZ[0] = 1.0f/minYVert.GetPosition().GetW();
		m_oneOverZ[1] = 1.0f/midYVert.GetPosition().GetW();
		m_oneOverZ[2] = 1.0f/maxYVert.GetPosition().GetW();

		m_texCoordX[0] = minYVert.GetTexCoords().GetX() * m_oneOverZ[0];
		m_texCoordX[1] = midYVert.GetTexCoords().GetX() * m_oneOverZ[1];
		m_texCoordX[2] = maxYVert.GetTexCoords().GetX() * m_oneOverZ[2];

		m_texCoordY[0] = minYVert.GetTexCoords().GetY() * m_oneOverZ[0];
		m_texCoordY[1] = midYVert.GetTexCoords().GetY() * m_oneOverZ[1];
		m_texCoordY[2] = maxYVert.GetTexCoords().GetY() * m_oneOverZ[2];

		m_texCoordXXStep = CalcXStep(m_texCoordX, minYVert, midYVert, maxYVert, oneOverdX);
		m_texCoordXYStep = CalcYStep(m_texCoordX, minYVert, midYVert, maxYVert, oneOverdY);
		m_texCoordYXStep = CalcXStep(m_texCoordY, minYVert, midYVert, maxYVert, oneOverdX);
		m_texCoordYYStep = CalcYStep(m_texCoordY, minYVert, midYVert, maxYVert, oneOverdY);
		m_oneOverZXStep = CalcXStep(m_oneOverZ, minYVert, midYVert, maxYVert, oneOverdX);
		m_oneOverZYStep = CalcYStep(m_oneOverZ, minYVert, midYVert, maxYVert, oneOverdY);
		m_depthXStep = CalcXStep(m_depth, minYVert, midYVert, maxYVert, oneOverdX);
		m_depthYStep = CalcYStep(m_depth, minYVert, midYVert, maxYVert, oneOverdY);
		m_lightAmtXStep = CalcXStep(m_lightAmt, minYVert, midYVert, maxYVert, oneOverdX);
		m_lightAmtYStep = CalcYStep(m_lightAmt, minYVert, midYVert, maxYVert, oneOverdY);
	}
}

  1. IndexedModel.java :

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

public class IndexedModel
{
	private List<Vector4f> m_positions;
	private List<Vector4f> m_texCoords;
	private List<Vector4f> m_normals;
	private List<Vector4f> m_tangents;
	private List<Integer>  m_indices;

	public IndexedModel()
	{
		m_positions = new ArrayList<Vector4f>();
		m_texCoords = new ArrayList<Vector4f>();
		m_normals = new ArrayList<Vector4f>();
		m_tangents = new ArrayList<Vector4f>();
		m_indices = new ArrayList<Integer>();
	}

	public void CalcNormals()
	{
		for(int i = 0; i < m_indices.size(); i += 3)
		{
			int i0 = m_indices.get(i);
			int i1 = m_indices.get(i + 1);
			int i2 = m_indices.get(i + 2);

			Vector4f v1 = m_positions.get(i1).Sub(m_positions.get(i0));
			Vector4f v2 = m_positions.get(i2).Sub(m_positions.get(i0));

			Vector4f normal = v1.Cross(v2).Normalized();

			m_normals.set(i0, m_normals.get(i0).Add(normal));
			m_normals.set(i1, m_normals.get(i1).Add(normal));
			m_normals.set(i2, m_normals.get(i2).Add(normal));
		}

		for(int i = 0; i < m_normals.size(); i++)
			m_normals.set(i, m_normals.get(i).Normalized());
	}

	public void CalcTangents()
	{
		for(int i = 0; i < m_indices.size(); i += 3)
		{
			int i0 = m_indices.get(i);
			int i1 = m_indices.get(i + 1);
			int i2 = m_indices.get(i + 2);

			Vector4f edge1 = m_positions.get(i1).Sub(m_positions.get(i0));
			Vector4f edge2 = m_positions.get(i2).Sub(m_positions.get(i0));

			float deltaU1 = m_texCoords.get(i1).GetX() - m_texCoords.get(i0).GetX();
			float deltaV1 = m_texCoords.get(i1).GetY() - m_texCoords.get(i0).GetY();
			float deltaU2 = m_texCoords.get(i2).GetX() - m_texCoords.get(i0).GetX();
			float deltaV2 = m_texCoords.get(i2).GetY() - m_texCoords.get(i0).GetY();

			float dividend = (deltaU1*deltaV2 - deltaU2*deltaV1);
			float f = dividend == 0 ? 0.0f : 1.0f/dividend;

			Vector4f tangent = new Vector4f(
					f * (deltaV2 * edge1.GetX() - deltaV1 * edge2.GetX()),
					f * (deltaV2 * edge1.GetY() - deltaV1 * edge2.GetY()),
					f * (deltaV2 * edge1.GetZ() - deltaV1 * edge2.GetZ()),
					0);
			
			m_tangents.set(i0, m_tangents.get(i0).Add(tangent));
			m_tangents.set(i1, m_tangents.get(i1).Add(tangent));
			m_tangents.set(i2, m_tangents.get(i2).Add(tangent));
		}

		for(int i = 0; i < m_tangents.size(); i++)
			m_tangents.set(i, m_tangents.get(i).Normalized());
	}

	public List<Vector4f> GetPositions() { return m_positions; }
	public List<Vector4f> GetTexCoords() { return m_texCoords; }
	public List<Vector4f> GetNormals() { return m_normals; }
	public List<Vector4f> GetTangents() { return m_tangents; }
	public List<Integer>  GetIndices() { return m_indices; }
}

  1. Input.java :
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

/**
 * Stores the current state of any user input devices, and updates them with new
 * input events.
 * 
 * @author Benny Bobaganoosh (thebennybox@gmail.com)
 */
public class Input implements KeyListener, FocusListener,
		MouseListener, MouseMotionListener {
	private boolean[] keys = new boolean[65536];
	private boolean[] mouseButtons = new boolean[4];
	private int mouseX = 0;
	private int mouseY = 0;

	/** Updates state when the mouse is dragged */
	public void mouseDragged(MouseEvent e) {
		mouseX = e.getX();
		mouseY = e.getY();
	}

	/** Updates state when the mouse is moved */
	public void mouseMoved(MouseEvent e) {
		mouseX = e.getX();
		mouseY = e.getY();
	}

	/** Updates state when the mouse is clicked */
	public void mouseClicked(MouseEvent e) {
	}

	/** Updates state when the mouse enters the screen */
	public void mouseEntered(MouseEvent e) {
	}

	/** Updates state when the mouse exits the screen */
	public void mouseExited(MouseEvent e) {
	}

	/** Updates state when a mouse button is pressed */
	public void mousePressed(MouseEvent e) {
		int code = e.getButton();
		if (code > 0 && code < mouseButtons.length)
			mouseButtons[code] = true;
	}

	/** Updates state when a mouse button is released */
	public void mouseReleased(MouseEvent e) {
		int code = e.getButton();
		if (code > 0 && code < mouseButtons.length)
			mouseButtons[code] = false;
	}

	/** Updates state when the window gains focus */
	public void focusGained(FocusEvent e) {
	}

	/** Updates state when the window loses focus */
	public void focusLost(FocusEvent e) {
		for (int i = 0; i < keys.length; i++)
			keys[i] = false;
		for (int i = 0; i < mouseButtons.length; i++)
			mouseButtons[i] = false;
	}

	/** Updates state when a key is pressed */
	public void keyPressed(KeyEvent e) {
		int code = e.getKeyCode();
		if (code > 0 && code < keys.length)
			keys[code] = true;
	}

	/** Updates state when a key is released */
	public void keyReleased(KeyEvent e) {
		int code = e.getKeyCode();
		if (code > 0 && code < keys.length)
			keys[code] = false;
	}

	/** Updates state when a key is typed */
	public void keyTyped(KeyEvent e) {
	}

	/**
	 * Gets whether or not a particular key is currently pressed.
	 * 
	 * @param key The key to test
	 * @return Whether or not key is currently pressed.
	 */
	public boolean GetKey(int key) {
		return keys[key];
	}

	/**
	 * Gets whether or not a particular mouse button is currently pressed.
	 * 
	 * @param button The button to test
	 * @return Whether or not the button is currently pressed.
	 */
	public boolean GetMouse(int button) {
		return mouseButtons[button];
	}

	/**
	 * Gets the location of the mouse cursor on x, in pixels.
	 * @return The location of the mouse cursor on x, in pixels
	 */
	public int GetMouseX() {
		return mouseX;
	}

	/**
	 * Gets the location of the mouse cursor on y, in pixels.
	 * @return The location of the mouse cursor on y, in pixels
	 */
	public int GetMouseY() {
		return mouseY;
	}
}

  1. Matrix4f.java :
public class Matrix4f
{
	private float[][] m;

	public Matrix4f()
	{
		m = new float[4][4];
	}

	public Matrix4f InitIdentity()
	{
		m[0][0] = 1;	m[0][1] = 0;	m[0][2] = 0;	m[0][3] = 0;
		m[1][0] = 0;	m[1][1] = 1;	m[1][2] = 0;	m[1][3] = 0;
		m[2][0] = 0;	m[2][1] = 0;	m[2][2] = 1;	m[2][3] = 0;
		m[3][0] = 0;	m[3][1] = 0;	m[3][2] = 0;	m[3][3] = 1;

		return this;
	}

	public Matrix4f InitScreenSpaceTransform(float halfWidth, float halfHeight)
	{
		m[0][0] = halfWidth;	m[0][1] = 0;	m[0][2] = 0;	m[0][3] = halfWidth - 0.5f;
		m[1][0] = 0;	m[1][1] = -halfHeight;	m[1][2] = 0;	m[1][3] = halfHeight - 0.5f;
		m[2][0] = 0;	m[2][1] = 0;	m[2][2] = 1;	m[2][3] = 0;
		m[3][0] = 0;	m[3][1] = 0;	m[3][2] = 0;	m[3][3] = 1;

		return this;
	}

	public Matrix4f InitTranslation(float x, float y, float z)
	{
		m[0][0] = 1;	m[0][1] = 0;	m[0][2] = 0;	m[0][3] = x;
		m[1][0] = 0;	m[1][1] = 1;	m[1][2] = 0;	m[1][3] = y;
		m[2][0] = 0;	m[2][1] = 0;	m[2][2] = 1;	m[2][3] = z;
		m[3][0] = 0;	m[3][1] = 0;	m[3][2] = 0;	m[3][3] = 1;

		return this;
	}

	public Matrix4f InitRotation(float x, float y, float z, float angle)
	{
		float sin = (float)Math.sin(angle);
		float cos = (float)Math.cos(angle);

		m[0][0] = cos+x*x*(1-cos); m[0][1] = x*y*(1-cos)-z*sin; m[0][2] = x*z*(1-cos)+y*sin; m[0][3] = 0;
		m[1][0] = y*x*(1-cos)+z*sin; m[1][1] = cos+y*y*(1-cos);	m[1][2] = y*z*(1-cos)-x*sin; m[1][3] = 0;
		m[2][0] = z*x*(1-cos)-y*sin; m[2][1] = z*y*(1-cos)+x*sin; m[2][2] = cos+z*z*(1-cos); m[2][3] = 0;
		m[3][0] = 0;	m[3][1] = 0;	m[3][2] = 0;	m[3][3] = 1;

		return this;
	}

	public Matrix4f InitRotation(float x, float y, float z)
	{
		Matrix4f rx = new Matrix4f();
		Matrix4f ry = new Matrix4f();
		Matrix4f rz = new Matrix4f();

		rz.m[0][0] = (float)Math.cos(z);rz.m[0][1] = -(float)Math.sin(z);rz.m[0][2] = 0;				rz.m[0][3] = 0;
		rz.m[1][0] = (float)Math.sin(z);rz.m[1][1] = (float)Math.cos(z);rz.m[1][2] = 0;					rz.m[1][3] = 0;
		rz.m[2][0] = 0;					rz.m[2][1] = 0;					rz.m[2][2] = 1;					rz.m[2][3] = 0;
		rz.m[3][0] = 0;					rz.m[3][1] = 0;					rz.m[3][2] = 0;					rz.m[3][3] = 1;

		rx.m[0][0] = 1;					rx.m[0][1] = 0;					rx.m[0][2] = 0;					rx.m[0][3] = 0;
		rx.m[1][0] = 0;					rx.m[1][1] = (float)Math.cos(x);rx.m[1][2] = -(float)Math.sin(x);rx.m[1][3] = 0;
		rx.m[2][0] = 0;					rx.m[2][1] = (float)Math.sin(x);rx.m[2][2] = (float)Math.cos(x);rx.m[2][3] = 0;
		rx.m[3][0] = 0;					rx.m[3][1] = 0;					rx.m[3][2] = 0;					rx.m[3][3] = 1;

		ry.m[0][0] = (float)Math.cos(y);ry.m[0][1] = 0;					ry.m[0][2] = -(float)Math.sin(y);ry.m[0][3] = 0;
		ry.m[1][0] = 0;					ry.m[1][1] = 1;					ry.m[1][2] = 0;					ry.m[1][3] = 0;
		ry.m[2][0] = (float)Math.sin(y);ry.m[2][1] = 0;					ry.m[2][2] = (float)Math.cos(y);ry.m[2][3] = 0;
		ry.m[3][0] = 0;					ry.m[3][1] = 0;					ry.m[3][2] = 0;					ry.m[3][3] = 1;

		m = rz.Mul(ry.Mul(rx)).GetM();

		return this;
	}

	public Matrix4f InitScale(float x, float y, float z)
	{
		m[0][0] = x;	m[0][1] = 0;	m[0][2] = 0;	m[0][3] = 0;
		m[1][0] = 0;	m[1][1] = y;	m[1][2] = 0;	m[1][3] = 0;
		m[2][0] = 0;	m[2][1] = 0;	m[2][2] = z;	m[2][3] = 0;
		m[3][0] = 0;	m[3][1] = 0;	m[3][2] = 0;	m[3][3] = 1;

		return this;
	}

	public Matrix4f InitPerspective(float fov, float aspectRatio, float zNear, float zFar)
	{
		float tanHalfFOV = (float)Math.tan(fov / 2);
		float zRange = zNear - zFar;

		m[0][0] = 1.0f / (tanHalfFOV * aspectRatio);	m[0][1] = 0;					m[0][2] = 0;	m[0][3] = 0;
		m[1][0] = 0;						m[1][1] = 1.0f / tanHalfFOV;	m[1][2] = 0;	m[1][3] = 0;
		m[2][0] = 0;						m[2][1] = 0;					m[2][2] = (-zNear -zFar)/zRange;	m[2][3] = 2 * zFar * zNear / zRange;
		m[3][0] = 0;						m[3][1] = 0;					m[3][2] = 1;	m[3][3] = 0;


		return this;
	}

	public Matrix4f InitOrthographic(float left, float right, float bottom, float top, float near, float far)
	{
		float width = right - left;
		float height = top - bottom;
		float depth = far - near;

		m[0][0] = 2/width;m[0][1] = 0;	m[0][2] = 0;	m[0][3] = -(right + left)/width;
		m[1][0] = 0;	m[1][1] = 2/height;m[1][2] = 0;	m[1][3] = -(top + bottom)/height;
		m[2][0] = 0;	m[2][1] = 0;	m[2][2] = -2/depth;m[2][3] = -(far + near)/depth;
		m[3][0] = 0;	m[3][1] = 0;	m[3][2] = 0;	m[3][3] = 1;

		return this;
	}

	public Matrix4f InitRotation(Vector4f forward, Vector4f up)
	{
		Vector4f f = forward.Normalized();

		Vector4f r = up.Normalized();
		r = r.Cross(f);

		Vector4f u = f.Cross(r);

		return InitRotation(f, u, r);
	}

	public Matrix4f InitRotation(Vector4f forward, Vector4f up, Vector4f right)
	{
		Vector4f f = forward;
		Vector4f r = right;
		Vector4f u = up;

		m[0][0] = r.GetX();	m[0][1] = r.GetY();	m[0][2] = r.GetZ();	m[0][3] = 0;
		m[1][0] = u.GetX();	m[1][1] = u.GetY();	m[1][2] = u.GetZ();	m[1][3] = 0;
		m[2][0] = f.GetX();	m[2][1] = f.GetY();	m[2][2] = f.GetZ();	m[2][3] = 0;
		m[3][0] = 0;		m[3][1] = 0;		m[3][2] = 0;		m[3][3] = 1;

		return this;
	}

	public Vector4f Transform(Vector4f r)
	{
		return new Vector4f(m[0][0] * r.GetX() + m[0][1] * r.GetY() + m[0][2] * r.GetZ() + m[0][3] * r.GetW(),
		                    m[1][0] * r.GetX() + m[1][1] * r.GetY() + m[1][2] * r.GetZ() + m[1][3] * r.GetW(),
		                    m[2][0] * r.GetX() + m[2][1] * r.GetY() + m[2][2] * r.GetZ() + m[2][3] * r.GetW(),
							m[3][0] * r.GetX() + m[3][1] * r.GetY() + m[3][2] * r.GetZ() + m[3][3] * r.GetW());
	}

	public Matrix4f Mul(Matrix4f r)
	{
		Matrix4f res = new Matrix4f();

		for(int i = 0; i < 4; i++)
		{
			for(int j = 0; j < 4; j++)
			{
				res.Set(i, j, m[i][0] * r.Get(0, j) +
						m[i][1] * r.Get(1, j) +
						m[i][2] * r.Get(2, j) +
						m[i][3] * r.Get(3, j));
			}
		}

		return res;
	}

	public float[][] GetM()
	{
		float[][] res = new float[4][4];

		for(int i = 0; i < 4; i++)
			for(int j = 0; j < 4; j++)
				res[i][j] = m[i][j];

		return res;
	}

	public float Get(int x, int y)
	{
		return m[x][y];
	}

	public void SetM(float[][] m)
	{
		this.m = m;
	}

	public void Set(int x, int y, float value)
	{
		m[x][y] = value;
	}
}
  1. Mesh.java :
import java.util.List;
import java.util.ArrayList;
import java.io.IOException;

public class Mesh
{
	private List<Vertex>  m_vertices;
	private List<Integer> m_indices;
	
	public Mesh(String fileName) throws IOException
	{
		IndexedModel model = new OBJModel(fileName).ToIndexedModel();

		m_vertices = new ArrayList<Vertex>();
		for(int i = 0; i < model.GetPositions().size(); i++)
		{
			m_vertices.add(new Vertex(
						model.GetPositions().get(i),
						model.GetTexCoords().get(i),
						model.GetNormals().get(i)));
		}

		m_indices = model.GetIndices();
	}

	public void Draw(RenderContext context, Matrix4f viewProjection, Matrix4f transform, Bitmap texture)
	{
		Matrix4f mvp = viewProjection.Mul(transform);
		for(int i = 0; i < m_indices.size(); i += 3)
		{
			context.DrawTriangle(
					m_vertices.get(m_indices.get(i)).Transform(mvp, transform),
					m_vertices.get(m_indices.get(i + 1)).Transform(mvp, transform),
					m_vertices.get(m_indices.get(i + 2)).Transform(mvp, transform),
					texture);
		}
	}
}

  1. OBJModel.java:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;

public class OBJModel
{
	private class OBJIndex
	{
		private int m_vertexIndex;
		private int m_texCoordIndex;
		private int m_normalIndex;

		public int GetVertexIndex()   { return m_vertexIndex; }
		public int GetTexCoordIndex() { return m_texCoordIndex; }
		public int GetNormalIndex()   { return m_normalIndex; }

		public void SetVertexIndex(int val)   { m_vertexIndex = val; }
		public void SetTexCoordIndex(int val) { m_texCoordIndex = val; }
		public void SetNormalIndex(int val)   { m_normalIndex = val; }

		@Override
		public boolean equals(Object obj)
		{
			OBJIndex index = (OBJIndex)obj;

			return m_vertexIndex == index.m_vertexIndex
					&& m_texCoordIndex == index.m_texCoordIndex
					&& m_normalIndex == index.m_normalIndex;
		}

		@Override
		public int hashCode()
		{
			final int BASE = 17;
			final int MULTIPLIER = 31;

			int result = BASE;

			result = MULTIPLIER * result + m_vertexIndex;
			result = MULTIPLIER * result + m_texCoordIndex;
			result = MULTIPLIER * result + m_normalIndex;

			return result;
		}
	}

	private List<Vector4f> m_positions;
	private List<Vector4f> m_texCoords;
	private List<Vector4f> m_normals;
	private List<OBJIndex> m_indices;
	private boolean        m_hasTexCoords;
	private boolean        m_hasNormals;

	private static String[] RemoveEmptyStrings(String[] data)
	{
		List<String> result = new ArrayList<String>();
		
		for(int i = 0; i < data.length; i++)
			if(!data[i].equals(""))
				result.add(data[i]);
		
		String[] res = new String[result.size()];
		result.toArray(res);
		
		return res;
	}

	public OBJModel(String fileName) throws IOException
	{
		m_positions = new ArrayList<Vector4f>();
		m_texCoords = new ArrayList<Vector4f>();
		m_normals = new ArrayList<Vector4f>();
		m_indices = new ArrayList<OBJIndex>();
		m_hasTexCoords = false;
		m_hasNormals = false;

		BufferedReader meshReader = null;

		meshReader = new BufferedReader(new FileReader(fileName));
		String line;

		while((line = meshReader.readLine()) != null)
		{
			String[] tokens = line.split(" ");
			tokens = RemoveEmptyStrings(tokens);

			if(tokens.length == 0 || tokens[0].equals("#"))
				continue;
			else if(tokens[0].equals("v"))
			{
				m_positions.add(new Vector4f(Float.valueOf(tokens[1]),
						Float.valueOf(tokens[2]),
						Float.valueOf(tokens[3]),1));
			}
			else if(tokens[0].equals("vt"))
			{
				m_texCoords.add(new Vector4f(Float.valueOf(tokens[1]),
						1.0f - Float.valueOf(tokens[2]),0,0));
			}
			else if(tokens[0].equals("vn"))
			{
				m_normals.add(new Vector4f(Float.valueOf(tokens[1]),
						Float.valueOf(tokens[2]),
						Float.valueOf(tokens[3]),0));
			}
			else if(tokens[0].equals("f"))
			{
				for(int i = 0; i < tokens.length - 3; i++)
				{
					m_indices.add(ParseOBJIndex(tokens[1]));
					m_indices.add(ParseOBJIndex(tokens[2 + i]));
					m_indices.add(ParseOBJIndex(tokens[3 + i]));
				}
			}
		}

		
		meshReader.close();
	}

	public IndexedModel ToIndexedModel()
	{
		IndexedModel result = new IndexedModel();
		IndexedModel normalModel = new IndexedModel();
		Map<OBJIndex, Integer> resultIndexMap = new HashMap<OBJIndex, Integer>();
		Map<Integer, Integer> normalIndexMap = new HashMap<Integer, Integer>();
		Map<Integer, Integer> indexMap = new HashMap<Integer, Integer>();

		for(int i = 0; i < m_indices.size(); i++)
		{
			OBJIndex currentIndex = m_indices.get(i);

			Vector4f currentPosition = m_positions.get(currentIndex.GetVertexIndex());
			Vector4f currentTexCoord;
			Vector4f currentNormal;

			if(m_hasTexCoords)
				currentTexCoord = m_texCoords.get(currentIndex.GetTexCoordIndex());
			else
				currentTexCoord = new Vector4f(0,0,0,0);

			if(m_hasNormals)
				currentNormal = m_normals.get(currentIndex.GetNormalIndex());
			else
				currentNormal = new Vector4f(0,0,0,0);

			Integer modelVertexIndex = resultIndexMap.get(currentIndex);

			if(modelVertexIndex == null)
			{
				modelVertexIndex = result.GetPositions().size();
				resultIndexMap.put(currentIndex, modelVertexIndex);

				result.GetPositions().add(currentPosition);
				result.GetTexCoords().add(currentTexCoord);
				if(m_hasNormals)
					result.GetNormals().add(currentNormal);
			}

			Integer normalModelIndex = normalIndexMap.get(currentIndex.GetVertexIndex());

			if(normalModelIndex == null)
			{
				normalModelIndex = normalModel.GetPositions().size();
				normalIndexMap.put(currentIndex.GetVertexIndex(), normalModelIndex);

				normalModel.GetPositions().add(currentPosition);
				normalModel.GetTexCoords().add(currentTexCoord);
				normalModel.GetNormals().add(currentNormal);
				normalModel.GetTangents().add(new Vector4f(0,0,0,0));
			}

			result.GetIndices().add(modelVertexIndex);
			normalModel.GetIndices().add(normalModelIndex);
			indexMap.put(modelVertexIndex, normalModelIndex);
		}

		if(!m_hasNormals)
		{
			normalModel.CalcNormals();

			for(int i = 0; i < result.GetPositions().size(); i++)
				result.GetNormals().add(normalModel.GetNormals().get(indexMap.get(i)));
		}

		normalModel.CalcTangents();

		for(int i = 0; i < result.GetPositions().size(); i++)
			result.GetTangents().add(normalModel.GetTangents().get(indexMap.get(i)));

		return result;
	}

	private OBJIndex ParseOBJIndex(String token)
	{
		String[] values = token.split("/");

		OBJIndex result = new OBJIndex();
		result.SetVertexIndex(Integer.parseInt(values[0]) - 1);

		if(values.length > 1)
		{
			if(!values[1].isEmpty())
			{
				m_hasTexCoords = true;
				result.SetTexCoordIndex(Integer.parseInt(values[1]) - 1);
			}

			if(values.length > 2)
			{
				m_hasNormals = true;
				result.SetNormalIndex(Integer.parseInt(values[2]) - 1);
			}
		}

		return result;
	}
}

  1. Quaternion.java :
public class Quaternion
{
	private float m_x;
	private float m_y;
	private float m_z;
	private float m_w;

	public Quaternion(float x, float y, float z, float w)
	{
		this.m_x = x;
		this.m_y = y;
		this.m_z = z;
		this.m_w = w;
	}

	public Quaternion(Vector4f axis, float angle)
	{
		float sinHalfAngle = (float)Math.sin(angle / 2);
		float cosHalfAngle = (float)Math.cos(angle / 2);

		this.m_x = axis.GetX() * sinHalfAngle;
		this.m_y = axis.GetY() * sinHalfAngle;
		this.m_z = axis.GetZ() * sinHalfAngle;
		this.m_w = cosHalfAngle;
	}

	public float Length()
	{
		return (float)Math.sqrt(m_x * m_x + m_y * m_y + m_z * m_z + m_w * m_w);
	}
	
	public Quaternion Normalized()
	{
		float length = Length();
		
		return new Quaternion(m_x / length, m_y / length, m_z / length, m_w / length);
	}
	
	public Quaternion Conjugate()
	{
		return new Quaternion(-m_x, -m_y, -m_z, m_w);
	}

	public Quaternion Mul(float r)
	{
		return new Quaternion(m_x * r, m_y * r, m_z * r, m_w * r);
	}

	public Quaternion Mul(Quaternion r)
	{
		float w_ = m_w * r.GetW() - m_x * r.GetX() - m_y * r.GetY() - m_z * r.GetZ();
		float x_ = m_x * r.GetW() + m_w * r.GetX() + m_y * r.GetZ() - m_z * r.GetY();
		float y_ = m_y * r.GetW() + m_w * r.GetY() + m_z * r.GetX() - m_x * r.GetZ();
		float z_ = m_z * r.GetW() + m_w * r.GetZ() + m_x * r.GetY() - m_y * r.GetX();
		
		return new Quaternion(x_, y_, z_, w_);
	}
	
	public Quaternion Mul(Vector4f r)
	{
		float w_ = -m_x * r.GetX() - m_y * r.GetY() - m_z * r.GetZ();
		float x_ =  m_w * r.GetX() + m_y * r.GetZ() - m_z * r.GetY();
		float y_ =  m_w * r.GetY() + m_z * r.GetX() - m_x * r.GetZ();
		float z_ =  m_w * r.GetZ() + m_x * r.GetY() - m_y * r.GetX();
		
		return new Quaternion(x_, y_, z_, w_);
	}

	public Quaternion Sub(Quaternion r)
	{
		return new Quaternion(m_x - r.GetX(), m_y - r.GetY(), m_z - r.GetZ(), m_w - r.GetW());
	}

	public Quaternion Add(Quaternion r)
	{
		return new Quaternion(m_x + r.GetX(), m_y + r.GetY(), m_z + r.GetZ(), m_w + r.GetW());
	}

	public Matrix4f ToRotationMatrix()
	{
		Vector4f forward =  new Vector4f(2.0f * (m_x * m_z - m_w * m_y), 2.0f * (m_y * m_z + m_w * m_x), 1.0f - 2.0f * (m_x * m_x + m_y * m_y));
		Vector4f up = new Vector4f(2.0f * (m_x * m_y + m_w * m_z), 1.0f - 2.0f * (m_x * m_x + m_z * m_z), 2.0f * (m_y * m_z - m_w * m_x));
		Vector4f right = new Vector4f(1.0f - 2.0f * (m_y * m_y + m_z * m_z), 2.0f * (m_x * m_y - m_w * m_z), 2.0f * (m_x * m_z + m_w * m_y));

		return new Matrix4f().InitRotation(forward, up, right);
	}

	public float Dot(Quaternion r)
	{
		return m_x * r.GetX() + m_y * r.GetY() + m_z * r.GetZ() + m_w * r.GetW();
	}

	public Quaternion NLerp(Quaternion dest, float lerpFactor, boolean shortest)
	{
		Quaternion correctedDest = dest;

		if(shortest && this.Dot(dest) < 0)
			correctedDest = new Quaternion(-dest.GetX(), -dest.GetY(), -dest.GetZ(), -dest.GetW());

		return correctedDest.Sub(this).Mul(lerpFactor).Add(this).Normalized();
	}

	public Quaternion SLerp(Quaternion dest, float lerpFactor, boolean shortest)
	{
		final float EPSILON = 1e3f;

		float cos = this.Dot(dest);
		Quaternion correctedDest = dest;

		if(shortest && cos < 0)
		{
			cos = -cos;
			correctedDest = new Quaternion(-dest.GetX(), -dest.GetY(), -dest.GetZ(), -dest.GetW());
		}

		if(Math.abs(cos) >= 1 - EPSILON)
			return NLerp(correctedDest, lerpFactor, false);

		float sin = (float)Math.sqrt(1.0f - cos * cos);
		float angle = (float)Math.atan2(sin, cos);
		float invSin =  1.0f/sin;

		float srcFactor = (float)Math.sin((1.0f - lerpFactor) * angle) * invSin;
		float destFactor = (float)Math.sin((lerpFactor) * angle) * invSin;

		return this.Mul(srcFactor).Add(correctedDest.Mul(destFactor));
	}

	//From Ken Shoemake's "Quaternion Calculus and Fast Animation" article
	public Quaternion(Matrix4f rot)
	{
		float trace = rot.Get(0, 0) + rot.Get(1, 1) + rot.Get(2, 2);

		if(trace > 0)
		{
			float s = 0.5f / (float)Math.sqrt(trace+ 1.0f);
			m_w = 0.25f / s;
			m_x = (rot.Get(1, 2) - rot.Get(2, 1)) * s;
			m_y = (rot.Get(2, 0) - rot.Get(0, 2)) * s;
			m_z = (rot.Get(0, 1) - rot.Get(1, 0)) * s;
		}
		else
		{
			if(rot.Get(0, 0) > rot.Get(1, 1) && rot.Get(0, 0) > rot.Get(2, 2))
			{
				float s = 2.0f * (float)Math.sqrt(1.0f + rot.Get(0, 0) - rot.Get(1, 1) - rot.Get(2, 2));
				m_w = (rot.Get(1, 2) - rot.Get(2, 1)) / s;
				m_x = 0.25f * s;
				m_y = (rot.Get(1, 0) + rot.Get(0, 1)) / s;
				m_z = (rot.Get(2, 0) + rot.Get(0, 2)) / s;
			}
			else if(rot.Get(1, 1) > rot.Get(2, 2))
			{
				float s = 2.0f * (float)Math.sqrt(1.0f + rot.Get(1, 1) - rot.Get(0, 0) - rot.Get(2, 2));
				m_w = (rot.Get(2, 0) - rot.Get(0, 2)) / s;
				m_x = (rot.Get(1, 0) + rot.Get(0, 1)) / s;
				m_y = 0.25f * s;
				m_z = (rot.Get(2, 1) + rot.Get(1, 2)) / s;
			}
			else
			{
				float s = 2.0f * (float)Math.sqrt(1.0f + rot.Get(2, 2) - rot.Get(0, 0) - rot.Get(1, 1));
				m_w = (rot.Get(0, 1) - rot.Get(1, 0) ) / s;
				m_x = (rot.Get(2, 0) + rot.Get(0, 2) ) / s;
				m_y = (rot.Get(1, 2) + rot.Get(2, 1) ) / s;
				m_z = 0.25f * s;
			}
		}

		float length = (float)Math.sqrt(m_x * m_x + m_y * m_y + m_z * m_z + m_w * m_w);
		m_x /= length;
		m_y /= length;
		m_z /= length;
		m_w /= length;
	}

	public Vector4f GetForward()
	{
		return new Vector4f(0,0,1,1).Rotate(this);
	}

	public Vector4f GetBack()
	{
		return new Vector4f(0,0,-1,1).Rotate(this);
	}

	public Vector4f GetUp()
	{
		return new Vector4f(0,1,0,1).Rotate(this);
	}

	public Vector4f GetDown()
	{
		return new Vector4f(0,-1,0,1).Rotate(this);
	}

	public Vector4f GetRight()
	{
		return new Vector4f(1,0,0,1).Rotate(this);
	}

	public Vector4f GetLeft()
	{
		return new Vector4f(-1,0,0,1).Rotate(this);
	}
	
	public float GetX()
	{
		return m_x;
	}

	public float GetY()
	{
		return m_y;
	}

	public float GetZ()
	{
		return m_z;
	}

	public float GetW()
	{
		return m_w;
	}

	public boolean equals(Quaternion r)
	{
		return m_x == r.GetX() && m_y == r.GetY() && m_z == r.GetZ() && m_w == r.GetW();
	}
}

  1. RenderContext.java :
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;

public class RenderContext extends Bitmap
{
	private float[] m_zBuffer;

	public RenderContext(int width, int height)
	{
		super(width, height);
		m_zBuffer = new float[width * height];
	}

	public void ClearDepthBuffer()
	{
		for(int i = 0; i < m_zBuffer.length; i++)
		{
			m_zBuffer[i] = Float.MAX_VALUE;
		}
	}

	public void DrawTriangle(Vertex v1, Vertex v2, Vertex v3, Bitmap texture)
	{
		if(v1.IsInsideViewFrustum() && v2.IsInsideViewFrustum() && v3.IsInsideViewFrustum())
		{
			FillTriangle(v1, v2, v3, texture);
			return;
		}

		List<Vertex> vertices = new ArrayList<>();
		List<Vertex> auxillaryList = new ArrayList<>();
		
		vertices.add(v1);
		vertices.add(v2);
		vertices.add(v3);

		if(ClipPolygonAxis(vertices, auxillaryList, 0) &&
				ClipPolygonAxis(vertices, auxillaryList, 1) &&
				ClipPolygonAxis(vertices, auxillaryList, 2))
		{
			Vertex initialVertex = vertices.get(0);

			for(int i = 1; i < vertices.size() - 1; i++)
			{
				FillTriangle(initialVertex, vertices.get(i), vertices.get(i + 1), texture);
			}
		}
	}

	private boolean ClipPolygonAxis(List<Vertex> vertices, List<Vertex> auxillaryList,
			int componentIndex)
	{
		ClipPolygonComponent(vertices, componentIndex, 1.0f, auxillaryList);
		vertices.clear();

		if(auxillaryList.isEmpty())
		{
			return false;
		}

		ClipPolygonComponent(auxillaryList, componentIndex, -1.0f, vertices);
		auxillaryList.clear();

		return !vertices.isEmpty();
	}

	private void ClipPolygonComponent(List<Vertex> vertices, int componentIndex, 
			float componentFactor, List<Vertex> result)
	{
		Vertex previousVertex = vertices.get(vertices.size() - 1);
		float previousComponent = previousVertex.Get(componentIndex) * componentFactor;
		boolean previousInside = previousComponent <= previousVertex.GetPosition().GetW();

		Iterator<Vertex> it = vertices.iterator();
		while(it.hasNext())
		{
			Vertex currentVertex = it.next();
			float currentComponent = currentVertex.Get(componentIndex) * componentFactor;
			boolean currentInside = currentComponent <= currentVertex.GetPosition().GetW();

			if(currentInside ^ previousInside)
			{
				float lerpAmt = (previousVertex.GetPosition().GetW() - previousComponent) /
					((previousVertex.GetPosition().GetW() - previousComponent) - 
					 (currentVertex.GetPosition().GetW() - currentComponent));

				result.add(previousVertex.Lerp(currentVertex, lerpAmt));
			}

			if(currentInside)
			{
				result.add(currentVertex);
			}

			previousVertex = currentVertex;
			previousComponent = currentComponent;
			previousInside = currentInside;
		}
	}

	private void FillTriangle(Vertex v1, Vertex v2, Vertex v3, Bitmap texture)
	{
		Matrix4f screenSpaceTransform = 
				new Matrix4f().InitScreenSpaceTransform(GetWidth()/2, GetHeight()/2);
		Matrix4f identity = new Matrix4f().InitIdentity();
		Vertex minYVert = v1.Transform(screenSpaceTransform, identity).PerspectiveDivide();
		Vertex midYVert = v2.Transform(screenSpaceTransform, identity).PerspectiveDivide();
		Vertex maxYVert = v3.Transform(screenSpaceTransform, identity).PerspectiveDivide();

		if(minYVert.TriangleAreaTimesTwo(maxYVert, midYVert) >= 0)
		{
			return;
		}

		if(maxYVert.GetY() < midYVert.GetY())
		{
			Vertex temp = maxYVert;
			maxYVert = midYVert;
			midYVert = temp;
		}

		if(midYVert.GetY() < minYVert.GetY())
		{
			Vertex temp = midYVert;
			midYVert = minYVert;
			minYVert = temp;
		}

		if(maxYVert.GetY() < midYVert.GetY())
		{
			Vertex temp = maxYVert;
			maxYVert = midYVert;
			midYVert = temp;
		}

		ScanTriangle(minYVert, midYVert, maxYVert, 
				minYVert.TriangleAreaTimesTwo(maxYVert, midYVert) >= 0,
				texture);
	}

	private void ScanTriangle(Vertex minYVert, Vertex midYVert, 
			Vertex maxYVert, boolean handedness, Bitmap texture)
	{
		Gradients gradients = new Gradients(minYVert, midYVert, maxYVert);
		Edge topToBottom    = new Edge(gradients, minYVert, maxYVert, 0);
		Edge topToMiddle    = new Edge(gradients, minYVert, midYVert, 0);
		Edge middleToBottom = new Edge(gradients, midYVert, maxYVert, 1);

		ScanEdges(gradients, topToBottom, topToMiddle, handedness, texture);
		ScanEdges(gradients, topToBottom, middleToBottom, handedness, texture);
	}

	private void ScanEdges(Gradients gradients, Edge a, Edge b, boolean handedness, Bitmap texture)
	{
		Edge left = a;
		Edge right = b;
		if(handedness)
		{
			Edge temp = left;
			left = right;
			right = temp;
		}

		int yStart = b.GetYStart();
		int yEnd   = b.GetYEnd();
		for(int j = yStart; j < yEnd; j++)
		{
			DrawScanLine(gradients, left, right, j, texture);
			left.Step();
			right.Step();
		}
	}

	private void DrawScanLine(Gradients gradients, Edge left, Edge right, int j, Bitmap texture)
	{
		int xMin = (int)Math.ceil(left.GetX());
		int xMax = (int)Math.ceil(right.GetX());
		float xPrestep = xMin - left.GetX();

//		float xDist = right.GetX() - left.GetX();
//		float texCoordXXStep = (right.GetTexCoordX() - left.GetTexCoordX())/xDist;
//		float texCoordYXStep = (right.GetTexCoordY() - left.GetTexCoordY())/xDist;
//		float oneOverZXStep = (right.GetOneOverZ() - left.GetOneOverZ())/xDist;
//		float depthXStep = (right.GetDepth() - left.GetDepth())/xDist;

		// Apparently, now that stepping is actually on pixel centers, gradients are
		// precise enough again.
		float texCoordXXStep = gradients.GetTexCoordXXStep();
		float texCoordYXStep = gradients.GetTexCoordYXStep();
		float oneOverZXStep = gradients.GetOneOverZXStep();
		float depthXStep = gradients.GetDepthXStep();
		float lightAmtXStep = gradients.GetLightAmtXStep();

		float texCoordX = left.GetTexCoordX() + texCoordXXStep * xPrestep;
		float texCoordY = left.GetTexCoordY() + texCoordYXStep * xPrestep;
		float oneOverZ = left.GetOneOverZ() + oneOverZXStep * xPrestep;
		float depth = left.GetDepth() + depthXStep * xPrestep;
		float lightAmt = left.GetLightAmt() + lightAmtXStep * xPrestep;

		for(int i = xMin; i < xMax; i++)
		{
			int index = i + j * GetWidth();
			if(depth < m_zBuffer[index])
			{
				m_zBuffer[index] = depth;
				float z = 1.0f/oneOverZ;
				int srcX = (int)((texCoordX * z) * (float)(texture.GetWidth() - 1) + 0.5f);
				int srcY = (int)((texCoordY * z) * (float)(texture.GetHeight() - 1) + 0.5f);

				CopyPixel(i, j, srcX, srcY, texture, lightAmt);
			}

			oneOverZ += oneOverZXStep;
			texCoordX += texCoordXXStep;
			texCoordY += texCoordYXStep;
			depth += depthXStep;
			lightAmt += lightAmtXStep;
		}
	}
}

  1. Stars3D.java :
import java.io.IOException;

/**
 * Represents a 3D Star field that can be rendered into an image.
 */
public class Stars3D
{
	/** How much the stars are spread out in 3D space, on average. */
	private final float m_spread;
	/** How quickly the stars move towards the camera */
	private final float m_speed;

	/** The star positions on the X axis */
	private final float m_starX[];
	/** The star positions on the Y axis */
	private final float m_starY[];
	/** The star positions on the Z axis */
	private final float m_starZ[];

	/**
	 * Creates a new 3D star field in a usable state.
	 *
	 * @param numStars The number of stars in the star field
	 * @param spread   How much the stars spread out, on average.
	 * @param speed    How quickly the stars move towards the camera
	 */
	public Stars3D(int numStars, float spread, float speed) throws IOException
	{
		m_spread = spread;
		m_speed = speed;

		m_starX = new float[numStars];
		m_starY = new float[numStars];
		m_starZ = new float[numStars];

		for(int i = 0; i < m_starX.length; i++)
		{
			InitStar(i);
		}

		m_bitmap = new Bitmap("./res/bricks.jpg");
	}

	private final Bitmap m_bitmap;

	/**
	 * Initializes a star to a new pseudo-random location in 3D space.
	 *
	 * @param i The index of the star to initialize.
	 */
	private void InitStar(int i)
	{
		//The random values have 0.5 subtracted from them and are multiplied
		//by 2 to remap them from the range (0, 1) to (-1, 1).
		m_starX[i] = 2 * ((float)Math.random() - 0.5f) * m_spread;
		m_starY[i] = 2 * ((float)Math.random() - 0.5f) * m_spread;
		//For Z, the random value is only adjusted by a small amount to stop
		//a star from being generated at 0 on Z.
		m_starZ[i] = ((float)Math.random() + 0.00001f) * m_spread;
	}

	/**
	 * Updates every star to a new position, and draws the starfield in a
	 * bitmap.
	 *
	 * @param target The bitmap to render to.
	 * @param delta  How much time has passed since the last update.
	 */
	public void UpdateAndRender(RenderContext target, float delta)
	{
		final float tanHalfFOV = (float)Math.tan(Math.toRadians(90.0/2.0));
		//Stars are drawn on a black background
		target.Clear((byte)0x00);

		float halfWidth  = target.GetWidth()/2.0f;
		float halfHeight = target.GetHeight()/2.0f;
		int triangleBuilderCounter = 0;

		int x1 = 0;
		int y1 = 0;
		int x2 = 0;
		int y2 = 0;
		for(int i = 0; i < m_starX.length; i++)
		{
			//Update the Star.

			//Move the star towards the camera which is at 0 on Z.
			m_starZ[i] -= delta * m_speed;

			//If star is at or behind the camera, generate a new position for
			//it.
			if(m_starZ[i] <= 0)
			{
				InitStar(i);
			}

			//Render the Star.

			//Multiplying the position by (size/2) and then adding (size/2)
			//remaps the positions from range (-1, 1) to (0, size)

			//Division by z*tanHalfFOV moves things in to create a perspective effect.
			int x = (int)((m_starX[i]/(m_starZ[i] * tanHalfFOV)) * halfWidth + halfWidth);
			int y = (int)((m_starY[i]/(m_starZ[i] * tanHalfFOV)) * halfHeight + halfHeight);
//
//			int x = (int)((m_starX[i]) * halfWidth + halfWidth);
//			int y = (int)((m_starY[i]) * halfHeight + halfHeight);


			//If the star is not within range of the screen, then generate a
			//new position for it.
			if(x < 0 || x >= target.GetWidth() ||
				(y < 0 || y >= target.GetHeight()))
			{
				InitStar(i);
				continue;
			}
//			else
//			{
//				//Otherwise, it is safe to draw this star to the screen.
//				target.DrawPixel(x, y, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF);
//			}
			triangleBuilderCounter++;
			if(triangleBuilderCounter == 1)
			{
				x1 = x;
				y1 = y;
			}
			else if(triangleBuilderCounter == 2)
			{
				x2 = x;
				y2 = y;
			}
			else if(triangleBuilderCounter == 3)
			{
				triangleBuilderCounter = 0;
//				Vertex v1 = new Vertex(new Vector4f(x1/400.0f - 1.0f, y1/300.0f - 1.0f, 0.0f, 1.0f), 
//						new Vector4f(1.0f, 0.0f, 0.0f, 0.0f));
//				Vertex v2 = new Vertex(new Vector4f(x2/400.0f - 1.0f, y2/300.0f - 1.0f, 0.0f, 1.0f), 
//						new Vector4f(1.0f, 1.0f, 0.0f, 0.0f));
//				Vertex v3 = new Vertex(new Vector4f(x/400.0f - 1.0f, y/300.0f - 1.0f, 0.0f, 1.0f), 
//						new Vector4f(0.0f, 1.0f, 0.0f, 0.0f));
//
//				target.DrawTriangle(v1, v2, v3, m_bitmap);
			}
		}
	}
}

  1. Transform.java :
public class Transform
{
	private Vector4f   m_pos;
	private Quaternion m_rot;
	private Vector4f   m_scale;

	public Transform()
	{
		this(new Vector4f(0,0,0,0));
	}

	public Transform(Vector4f pos)
	{
		this(pos, new Quaternion(0,0,0,1), new Vector4f(1,1,1,1));
	}

	public Transform(Vector4f pos, Quaternion rot, Vector4f scale)
	{
		m_pos = pos;
		m_rot = rot;
		m_scale = scale;
	}

	public Transform SetPos(Vector4f pos)
	{
		return new Transform(pos, m_rot, m_scale);
	}

	public Transform Rotate(Quaternion rotation)
	{
		return new Transform(m_pos, rotation.Mul(m_rot).Normalized(), m_scale);
	}

	public Transform LookAt(Vector4f point, Vector4f up)
	{
		return Rotate(GetLookAtRotation(point, up));
	}

	public Quaternion GetLookAtRotation(Vector4f point, Vector4f up)
	{
		return new Quaternion(new Matrix4f().InitRotation(point.Sub(m_pos).Normalized(), up));
	}

	public Matrix4f GetTransformation()
	{
		Matrix4f translationMatrix = new Matrix4f().InitTranslation(m_pos.GetX(), m_pos.GetY(), m_pos.GetZ());
		Matrix4f rotationMatrix = m_rot.ToRotationMatrix();
		Matrix4f scaleMatrix = new Matrix4f().InitScale(m_scale.GetX(), m_scale.GetY(), m_scale.GetZ());

		return translationMatrix.Mul(rotationMatrix.Mul(scaleMatrix));
	}

	public Vector4f GetTransformedPos()
	{
		return m_pos;
	}

	public Quaternion GetTransformedRot()
	{
		return m_rot;
	}

	public Vector4f GetPos()
	{
		return m_pos;
	}

	public Quaternion GetRot()
	{
		return m_rot;
	}

	public Vector4f GetScale()
	{
		return m_scale;
	}
}
  1. Vector4f.java :
public class Vector4f
{
	private final float x;
	private final float y;
	private final float z;
	private final float w;

	public Vector4f(float x, float y, float z, float w)
	{
		this.x = x;
		this.y = y;
		this.z = z;
		this.w = w;
	}

	public Vector4f(float x, float y, float z)
	{
		this(x, y, z, 1.0f);
	}

	public float Length()
	{
		return (float)Math.sqrt(x * x + y * y + z * z + w * w);
	}

	public float Max()
	{
		return Math.max(Math.max(x, y), Math.max(z, w));
	}

	public float Dot(Vector4f r)
	{
		return x * r.GetX() + y * r.GetY() + z * r.GetZ() + w * r.GetW();
	}

	public Vector4f Cross(Vector4f r)
	{
		float x_ = y * r.GetZ() - z * r.GetY();
		float y_ = z * r.GetX() - x * r.GetZ();
		float z_ = x * r.GetY() - y * r.GetX();

		return new Vector4f(x_, y_, z_, 0);
	}

	public Vector4f Normalized()
	{
		float length = Length();

		return new Vector4f(x / length, y / length, z / length, w / length);
	}

	public Vector4f Rotate(Vector4f axis, float angle)
	{
		float sinAngle = (float)Math.sin(-angle);
		float cosAngle = (float)Math.cos(-angle);

		return this.Cross(axis.Mul(sinAngle)).Add(           //Rotation on local X
				(this.Mul(cosAngle)).Add(                     //Rotation on local Z
						axis.Mul(this.Dot(axis.Mul(1 - cosAngle))))); //Rotation on local Y
	}

	public Vector4f Rotate(Quaternion rotation)
	{
		Quaternion conjugate = rotation.Conjugate();

		Quaternion w = rotation.Mul(this).Mul(conjugate);

		return new Vector4f(w.GetX(), w.GetY(), w.GetZ(), 1.0f);
	}

	public Vector4f Lerp(Vector4f dest, float lerpFactor)
	{
		return dest.Sub(this).Mul(lerpFactor).Add(this);
	}

	public Vector4f Add(Vector4f r)
	{
		return new Vector4f(x + r.GetX(), y + r.GetY(), z + r.GetZ(), w + r.GetW());
	}

	public Vector4f Add(float r)
	{
		return new Vector4f(x + r, y + r, z + r, w + r);
	}

	public Vector4f Sub(Vector4f r)
	{
		return new Vector4f(x - r.GetX(), y - r.GetY(), z - r.GetZ(), w - r.GetW());
	}

	public Vector4f Sub(float r)
	{
		return new Vector4f(x - r, y - r, z - r, w - r);
	}

	public Vector4f Mul(Vector4f r)
	{
		return new Vector4f(x * r.GetX(), y * r.GetY(), z * r.GetZ(), w * r.GetW());
	}

	public Vector4f Mul(float r)
	{
		return new Vector4f(x * r, y * r, z * r, w * r);
	}

	public Vector4f Div(Vector4f r)
	{
		return new Vector4f(x / r.GetX(), y / r.GetY(), z / r.GetZ(), w / r.GetW());
	}

	public Vector4f Div(float r)
	{
		return new Vector4f(x / r, y / r, z / r, w / r);
	}

	public Vector4f Abs()
	{
		return new Vector4f(Math.abs(x), Math.abs(y), Math.abs(z), Math.abs(w));
	}

	public String toString()
	{
		return "(" + x + ", " + y + ", " + z + ", " + w + ")";
	}

	public float GetX()
	{
		return x;
	}

	public float GetY()
	{
		return y;
	}

	public float GetZ()
	{
		return z;
	}

	public float GetW()
	{
		return w;
	}

	public boolean equals(Vector4f r)
	{
		return x == r.GetX() && y == r.GetY() && z == r.GetZ() && w == r.GetW();
	}
}
  1. Vertex.java :
public class Vertex
{
	private Vector4f m_pos;
	private Vector4f m_texCoords;
	private Vector4f m_normal;

	/** Basic Getter */
	public float GetX() { return m_pos.GetX(); }
	/** Basic Getter */
	public float GetY() { return m_pos.GetY(); }

	public Vector4f GetPosition() { return m_pos; }
	public Vector4f GetTexCoords() { return m_texCoords; }
	public Vector4f GetNormal() { return m_normal; }

	/**
	 * Creates a new Vertex in a usable state.
	 */
	public Vertex(Vector4f pos, Vector4f texCoords, Vector4f normal)
	{
		m_pos = pos;
		m_texCoords = texCoords;
		m_normal = normal;
	}

	public Vertex Transform(Matrix4f transform, Matrix4f normalTransform)
	{
		// The normalized here is important if you're doing scaling.
		return new Vertex(transform.Transform(m_pos), m_texCoords, 
				normalTransform.Transform(m_normal).Normalized());
	}

	public Vertex PerspectiveDivide()
	{
		return new Vertex(new Vector4f(m_pos.GetX()/m_pos.GetW(), m_pos.GetY()/m_pos.GetW(), 
						m_pos.GetZ()/m_pos.GetW(), m_pos.GetW()),	
				m_texCoords, m_normal);
	}

	public float TriangleAreaTimesTwo(Vertex b, Vertex c)
	{
		float x1 = b.GetX() - m_pos.GetX();
		float y1 = b.GetY() - m_pos.GetY();

		float x2 = c.GetX() - m_pos.GetX();
		float y2 = c.GetY() - m_pos.GetY();

		return (x1 * y2 - x2 * y1);
	}

	public Vertex Lerp(Vertex other, float lerpAmt)
	{
		return new Vertex(
				m_pos.Lerp(other.GetPosition(), lerpAmt),
				m_texCoords.Lerp(other.GetTexCoords(), lerpAmt),
				m_normal.Lerp(other.GetNormal(), lerpAmt)
				);
	}

	public boolean IsInsideViewFrustum()
	{
		return 
			Math.abs(m_pos.GetX()) <= Math.abs(m_pos.GetW()) &&
			Math.abs(m_pos.GetY()) <= Math.abs(m_pos.GetW()) &&
			Math.abs(m_pos.GetZ()) <= Math.abs(m_pos.GetW());
	}

	public float Get(int index)
	{
		switch(index)
		{
			case 0:
				return m_pos.GetX();
			case 1:
				return m_pos.GetY();
			case 2:
				return m_pos.GetZ();
			case 3:
				return m_pos.GetW();
			default:
				throw new IndexOutOfBoundsException();
		}
	}
}



运行方式
  • Open your preferred Java IDE, and create a new project.
  • Import everything under the src folder as source files.
  • Copy the res folder into your Java IDE's folder for your project.
  • Build and run
mv res obj/res
cd obj

java Main

mv res ../res
cd ../




艾孜尔江撰
2021年6月22日

Gitee上的源码

Github上的源码

原文地址:https://www.cnblogs.com/ezhar/p/14917021.html