天天看點

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 ([email protected])
 */
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上的源碼​​