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();
}
}
}
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];
}
}
}
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));
}
}
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();
}
}
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;
}
}
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);
}
}
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; }
}
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;
}
}
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;
}
}
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);
}
}
}
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;
}
}
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();
}
}
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;
}
}
}
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);
}
}
}
}
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;
}
}
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();
}
}
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日