diff --git a/.gitignore b/.gitignore index a1c2a23..17505bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,11 @@ +# Mac +*.DS_Store + + +# IntelliJ Idea folder +.idea + + # Compiled class file *.class diff --git a/HeliumEngine/HeliumEngine.iml b/HeliumEngine/HeliumEngine.iml new file mode 100644 index 0000000..0913bd0 --- /dev/null +++ b/HeliumEngine/HeliumEngine.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/HeliumEngine/src/Test.java b/HeliumEngine/src/Test.java new file mode 100644 index 0000000..1c153f3 --- /dev/null +++ b/HeliumEngine/src/Test.java @@ -0,0 +1,79 @@ +import org.hl.engine.graphics.Mesh; +import org.hl.engine.graphics.Renderer; +import org.hl.engine.graphics.Shader; +import org.hl.engine.graphics.Vertex; +import org.hl.engine.io.Display; +import org.hl.engine.io.Input; +import org.hl.engine.math.Vector3f; +import org.lwjgl.glfw.GLFW; + + + +public class Test { + public final static int WIDTH = 640, HEIGHT = 480; + public final String windowName = "Game!"; + public Display display; + public Input i; + public Renderer renderer; + public Shader shader; + + public Mesh mesh = new Mesh(new Vertex[] { + new Vertex(new Vector3f(-0.5F, 0.5F, 0.0F), new Vector3f(0, 0, 1.0F)), + new Vertex(new Vector3f(-0.5F, -0.5F, 0.0F), new Vector3f(0, 0, 1.0F)), + new Vertex(new Vector3f(0.5F, -0.5F, 0.0F), new Vector3f(1.0F, 0, 1.0F)), + new Vertex(new Vector3f(0.5F, 0.5F, 0.0F), new Vector3f(1.0F, 0, 1.0F) ), + + }, new int[] { + 0, 1, 2, + 0, 2, 3 + + }); + + public void run() { + init(); + i = new Input(display); + while (!(display.shouldClose()) && !i.isKeyDown(GLFW.GLFW_KEY_ESCAPE)) { + update(); + render(); + } + + close(); + + } + public void init() { + // System.out.println("Initializing Game "); + display = new Display(WIDTH, HEIGHT, windowName); + shader = new Shader("/resources/shaders/mainVertex.glsl", "/resources/shaders/mainFragment.glsl"); + renderer = new Renderer(shader); + display.setBackgroundColor(1F, 0, 0); + display.create(); + mesh.create(); + shader.create(); + + } + private void update() { + // System.out.println("Updating "); + int frames = display.update(); + display.setWindowName(display.getWindowName().substring(0, 4) + " (Frames : " + frames + ")"); + + i.reset(); + + } + + private void render() { + // System.out.println("Rendering "); + renderer.renderMesh(mesh); + display.swapBuffers(); + + } + + private void close() { + display.destroy(); + mesh.destroy(); + shader.destroy(); + } + + public static void main(String[] args) { + new Test().run(); + } +} diff --git a/HeliumEngine/src/org/hl/engine/graphics/Mesh.java b/HeliumEngine/src/org/hl/engine/graphics/Mesh.java new file mode 100644 index 0000000..b6d6194 --- /dev/null +++ b/HeliumEngine/src/org/hl/engine/graphics/Mesh.java @@ -0,0 +1,111 @@ +package org.hl.engine.graphics; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL20; +import org.lwjgl.opengl.GL30; +import org.lwjgl.system.MemoryUtil; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +public class Mesh { + private Vertex[] vertices; + private int[] indices; + private int vertexArrayObject, positionBufferObject, indicesBufferObject, colorBufferObject; + + // A group of vertices combined based on the indexes + public Mesh(Vertex[] vertices, int[] indices) { + this.vertices = vertices; + this.indices = indices; + } + + // Destroy the mesh + public void destroy () { + GL15.glDeleteBuffers(positionBufferObject); + GL15.glDeleteBuffers(indicesBufferObject); + GL15.glDeleteBuffers(colorBufferObject); + + GL30.glDeleteVertexArrays(vertexArrayObject); + } + + // getters for the mesh + + public Vertex[] getVertices() { + return vertices; + } + + public int[] getIndices() { + return indices; + } + + public int getVertexArrayObject() { + return vertexArrayObject; + } + + public int getPositionBufferObject() { + return positionBufferObject; + } + + public int getIndicesBufferObject() { + return indicesBufferObject; + } + + public int getColorBufferObject() { + return colorBufferObject; + } + + public void create() { + + // Creates the mesh by formatting the vertices and indices and inputting them to OpenGL + vertexArrayObject = GL30.glGenVertexArrays(); + GL30.glBindVertexArray(vertexArrayObject); + + + // Putting the position of the vertex into the buffer so the renderer can read it + + FloatBuffer positionBuffer = MemoryUtil.memAllocFloat(vertices.length * 3); + float[] positionData = new float[vertices.length * 3]; + for (int i = 0; i < vertices.length; i ++ ) { + positionData[i * 3] = vertices[i].getPosition().getX(); + positionData[i * 3 + 1] = vertices[i].getPosition().getY(); + positionData[i * 3 + 2] = vertices[i].getPosition().getZ(); + } + positionBuffer.put(positionData).flip(); + + positionBufferObject = storeData(positionBuffer, 0, 3); + + // Putting the color into the buffer so renderer and shader can read it + + FloatBuffer colorBuffer = MemoryUtil.memAllocFloat(vertices.length * 3); + float[] colorData = new float[vertices.length * 3]; + for (int i = 0; i < vertices.length; i ++ ) { + colorData[i * 3] = vertices[i].getColor().getX(); + colorData[i * 3 + 1] = vertices[i].getColor().getY(); + colorData[i * 3 + 2] = vertices[i].getColor().getZ(); + } + colorBuffer.put(colorData).flip(); + + colorBufferObject = storeData(colorBuffer, 1, 3); + + IntBuffer indicesBuffer = MemoryUtil.memAllocInt(indices.length); + indicesBuffer.put(indices).flip(); + + indicesBufferObject = GL15.glGenBuffers(); + GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesBufferObject); + GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL15.GL_STATIC_DRAW); + GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0); + } + + // Storing data to the buffer at position index (helps with storing color / position) + + private int storeData(FloatBuffer buffer, int index, int size) { + int bufferID = GL15.glGenBuffers(); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferID); + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW); + GL20.glVertexAttribPointer(index, size, GL11.GL_FLOAT, false, 0, 0); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); + + return bufferID; + } +} diff --git a/HeliumEngine/src/org/hl/engine/graphics/Renderer.java b/HeliumEngine/src/org/hl/engine/graphics/Renderer.java new file mode 100644 index 0000000..17f7c53 --- /dev/null +++ b/HeliumEngine/src/org/hl/engine/graphics/Renderer.java @@ -0,0 +1,34 @@ +package org.hl.engine.graphics; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL30; + +public class Renderer { + + private Shader shader; + + public Renderer(Shader shader) { + this.shader = shader; + } + + + public void renderMesh(Mesh mesh) { + + // Renders the mesh by drawing it using triangles (least complicated) + GL30.glBindVertexArray(mesh.getVertexArrayObject()); + GL30.glEnableVertexAttribArray(0); + GL30.glEnableVertexAttribArray(1); + GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, mesh.getIndicesBufferObject()); + + shader.bind(); + + GL11.glDrawElements(GL11.GL_TRIANGLES, mesh.getIndices().length, GL11.GL_UNSIGNED_INT, 0); + + shader.unbind(); + GL30.glDisableVertexAttribArray(0); + GL30.glDisableVertexAttribArray(1); + GL30.glBindVertexArray(0); + + } +} diff --git a/HeliumEngine/src/org/hl/engine/graphics/Shader.java b/HeliumEngine/src/org/hl/engine/graphics/Shader.java new file mode 100644 index 0000000..5a62d04 --- /dev/null +++ b/HeliumEngine/src/org/hl/engine/graphics/Shader.java @@ -0,0 +1,88 @@ +package org.hl.engine.graphics; + +import org.hl.engine.utils.FileUtils; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL20; + +public class Shader { + private String vertexFile; + private String fragmentFile; + + private int vertexID, fragmentID, programID; + + public Shader(String vertexPath, String fragmentPath) { + vertexFile = FileUtils.loadAsString(vertexPath); + fragmentFile = FileUtils.loadAsString(fragmentPath); + + } + + public void create() { + + // Creates the program + programID = GL20.glCreateProgram(); + + + // loads the vertex shader + vertexID = GL20.glCreateShader(GL20.GL_VERTEX_SHADER); + + GL20.glShaderSource(vertexID, vertexFile); + GL20.glCompileShader(vertexID); + + if (GL20.glGetShaderi(vertexID, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) { + System.err.println("Vertex Shader: " + GL20.glGetShaderInfoLog(vertexID)); + System.exit(1); + + } + + // loads the fragment shader + fragmentID = GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER); + + GL20.glShaderSource(fragmentID, fragmentFile); + GL20.glCompileShader(fragmentID); + + if (GL20.glGetShaderi(fragmentID, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) { + System.err.println("Fragment Shader: " + GL20.glGetShaderInfoLog(fragmentID)); + System.exit(1); + + } + + // Attach shaders to program + GL20.glAttachShader(programID, vertexID); + GL20.glAttachShader(programID, fragmentID); + + // Link the program + GL20.glLinkProgram(programID); + if (GL20.glGetProgrami(programID, GL20.GL_LINK_STATUS) == GL11.GL_FALSE) { + System.err.println("Program Linking: " + GL20.glGetProgramInfoLog(programID)); + System.exit(1); + return; + } + + // Validate the program + GL20.glValidateProgram(programID); + if (GL20.glGetProgrami(programID, GL20.GL_VALIDATE_STATUS) == GL11.GL_FALSE) { + System.err.println("Program Validation: " + GL20.glGetProgramInfoLog(programID)); + System.exit(1); + return; + } + + GL20.glDeleteShader(vertexID); + GL20.glDeleteShader(fragmentID); + + } + + // Bind so we can use the shader + public void bind() { + GL20.glUseProgram(programID); + } + + // Unbind the shader after use + public void unbind() { + GL20.glUseProgram(0); + } + + // Destroy the program + public void destroy() { + GL20.glDeleteProgram(programID); + } +} diff --git a/HeliumEngine/src/org/hl/engine/graphics/Vertex.java b/HeliumEngine/src/org/hl/engine/graphics/Vertex.java new file mode 100644 index 0000000..5e2df5b --- /dev/null +++ b/HeliumEngine/src/org/hl/engine/graphics/Vertex.java @@ -0,0 +1,24 @@ +package org.hl.engine.graphics; + +import org.hl.engine.math.Vector3f; + +public class Vertex { + + // Just a vertex + + private Vector3f position; + private Vector3f color; + + public Vertex (Vector3f position, Vector3f color) { + this.position = position; + this.color = color; + } + + public Vector3f getPosition() { + return position; + } + + public Vector3f getColor() { + return color; + } +} diff --git a/HeliumEngine/src/org/hl/engine/io/Display.java b/HeliumEngine/src/org/hl/engine/io/Display.java new file mode 100644 index 0000000..0c0c5ec --- /dev/null +++ b/HeliumEngine/src/org/hl/engine/io/Display.java @@ -0,0 +1,212 @@ +package org.hl.engine.io; +import org.hl.engine.math.Vector3f; +import org.lwjgl.glfw.GLFWVidMode; +import org.lwjgl.glfw.GLFWWindowSizeCallback; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GL11; + +import static org.lwjgl.glfw.GLFW.*; + +public class Display { + private int width, height; + private String windowName; + private long window; + public int frames; + public int previousFrames = frames; + public long time; + public Input input; + private Vector3f background = new Vector3f(0, 0, 0); + private GLFWWindowSizeCallback resizeCallback; + private boolean isResized; + private boolean isFullscreen; + private int[] windowXPos = new int[1]; + private int[] windowYPos = new int[1]; + private GLFWVidMode videoMode; + private int savedPosX; + private int savedPosY; + private int savedWidth; + private int savedHeight; + + + + + // Constructor to create the display + public Display (int width, int height, String windowName) { + this.width = width; + this.height = height; + this.windowName = windowName; + } + + // Change the window name + public void setWindowName(String windowName) { + this.windowName = windowName; + glfwSetWindowTitle(window, windowName); + } + + // Getters for size, name, window, time, and fullScreen + public int getWidth() { + return width; + } + public int getHeight() { + return height; + } + public String getWindowName() { + return windowName; + } + public long getWindow() { + return window; + } + public long getTime() { + return time; + } + + public boolean isFullscreen() { + return isFullscreen; + } + + // Makes the screen fullscreen or not based on the argument + public void setFullscreen(boolean fullscreen) { + isFullscreen = fullscreen; + isResized = true; + GL11.glViewport(0, 0, width, height); + if (isFullscreen) { + + int[] xpos = {0}; + int[] ypos = {0}; + glfwGetWindowPos(this.window, xpos, ypos); + savedPosX = xpos[0]; + savedPosY = ypos[0]; + + savedWidth = width; + savedHeight = height; + glfwGetWindowPos(window, windowXPos, windowYPos); + glfwSetWindowMonitor(window, glfwGetPrimaryMonitor(), 0, 0, videoMode.width(), videoMode.height(), 0); + } else { + glfwSetWindowMonitor(window, 0, savedPosX, savedPosY, savedWidth, savedHeight, 0); + } + } + + // resized getter + + public boolean isResized() { + return isResized; + } + + + // Creates the window (should go in the init() function of your Main program) + public void create() { + + // initializing glfw + if (!glfwInit()) { + System.err.println("Failed to initialize GLFW! "); + System.exit(1); + } + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL11.GL_TRUE); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + //Creating window + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + window = glfwCreateWindow(this.width, this.height, this.windowName, isFullscreen ? glfwGetPrimaryMonitor():0, 0); + if (window == 0) { + System.err.println("Failed to create window! "); + System.exit(1); + } + + // Setting size of window + + videoMode = glfwGetVideoMode(glfwGetPrimaryMonitor()); + windowXPos[0] = (videoMode.width() - this.width) / 2; + windowYPos[0] = (videoMode.height() - this.height ) / 2; + glfwSetWindowPos(window, windowXPos[0], windowYPos[0]); + + + // Graphics + glfwMakeContextCurrent(window); + GL.createCapabilities(); + + GL11.glEnable(GL11.GL_DEPTH_TEST); + + callBacks(); + glfwShowWindow(window); + glfwSwapInterval(1); + + // setting time + + time = System.currentTimeMillis(); + + } + + + // Creating the resize callback (all other callbacks were removed and are now in Input class) + private void callBacks() { + + resizeCallback = new GLFWWindowSizeCallback() { + + @Override + public void invoke(long window, int w, int h) { + width = w; + height = h; + isResized = true; + } + }; + + + glfwSetWindowSizeCallback(window, resizeCallback); + } + + // Refreshes the screen, resets frame count + public int update() { + if (isResized) { + GL11.glViewport(0, 0, width, height); + isResized = false; + } + GL11.glClearColor(background.getX(), background.getY(), background.getZ(), 1.0F); + GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); + glfwPollEvents(); + frames++; + if (System.currentTimeMillis() > time + 1000) { + previousFrames = frames; + time = System.currentTimeMillis(); + frames = 0; + return frames; + } else { + return previousFrames; + + } + } + + // Terminates the program (making WindowShouldClose) + + public void terminate() { + glfwSetWindowShouldClose(window, true); + } + + + // Completely DESTROYS the window + public void destroy() { + resizeCallback.free(); + glfwDestroyWindow(window); + glfwTerminate(); + } + + // switches the buffers (for rendering) + + public void swapBuffers() { + glfwSwapBuffers(window); + } + + + // get whether the window should close + public boolean shouldClose() { + return glfwWindowShouldClose(window); + } + + // changes the background color + + public void setBackgroundColor(float r, float g, float b) { + background.setVector(r, g, b); + } +} \ No newline at end of file diff --git a/HeliumEngine/src/org/hl/engine/io/Input.java b/HeliumEngine/src/org/hl/engine/io/Input.java new file mode 100644 index 0000000..7b7f258 --- /dev/null +++ b/HeliumEngine/src/org/hl/engine/io/Input.java @@ -0,0 +1,121 @@ +package org.hl.engine.io; + +import org.lwjgl.glfw.GLFW; + +import java.util.Arrays; + +import static org.lwjgl.glfw.GLFW.*; + +public class Input { + private boolean[] keys = new boolean[GLFW.GLFW_KEY_LAST]; + private boolean[] buttons = new boolean[GLFW.GLFW_MOUSE_BUTTON_LAST]; + + private int[] keyState = new int[GLFW.GLFW_KEY_LAST]; + private int[] buttonState = new int[GLFW.GLFW_MOUSE_BUTTON_LAST]; + + private long window; + private Display display; + + private boolean inWindow; + + private double mouseX, mouseY; + private double scrollX, scrollY; + + // Sets up the callbacks based on the window + public Input(Display d) { + this.display = d; + this.window = this.display.getWindow(); + + glfwSetKeyCallback(this.window, (window, key, scancode, action, mods) -> { + keys[key] = action != GLFW.GLFW_RELEASE; + keyState[key] = action; + }); + + glfwSetMouseButtonCallback(this.window, (window, button, action, mods) -> { + buttons[button] = action != GLFW.GLFW_RELEASE; + buttonState[button] = action; + }); + + glfwSetCursorPosCallback(this.window, (window, xpos, ypos) -> { + mouseX = xpos; + mouseY = ypos; + }); + + glfwSetCursorEnterCallback(this.window, (window, entered) -> { + inWindow = entered; + }); + + glfwSetScrollCallback(this.window, (window, xoffset, yoffset) -> { + scrollX += xoffset; + scrollY += yoffset; + }); + + resetKeyboard(); + resetButtons(); + } + + // All states (isDown will return whether it has been held down but Press only returns a press) + + public boolean isKeyDown(int key) { + return keys[key]; + } + + public boolean keyPress(int key) { + return keyState[key] == GLFW.GLFW_PRESS; + } + + public boolean keyReleased(int key) { + return keyState[key] == GLFW.GLFW_RELEASE; + } + + public boolean isButtonDown(int button) { + return buttons[button]; + } + + public boolean buttonPress(int button) { + return buttonState[button] == GLFW.GLFW_PRESS; + } + + public boolean buttonReleased(int button) { + return buttonState[button] == GLFW.GLFW_RELEASE; + } + + // Resets keyboard and buttons so the presses will only be registered once + private void resetKeyboard() { + Arrays.fill(keyState, -1); + } + + private void resetButtons() { + Arrays.fill(buttonState, -1); + } + + + // This function should only be called after all input has been taken inside the loop. It must be called if keyPress and buttonPress should work. + + public void reset() { + resetKeyboard(); + resetButtons(); + } + + // Scroll, mouse, and window getters + + public double getMouseX() { + return mouseX; + } + + public double getMouseY() { + return mouseY; + } + + public boolean inWindow() { + return inWindow; + } + + public double getScrollX() { + return scrollX; + } + + public double getScrollY() { + return scrollY; + } +} diff --git a/HeliumEngine/src/org/hl/engine/math/Vector3f.java b/HeliumEngine/src/org/hl/engine/math/Vector3f.java new file mode 100644 index 0000000..fa1da5e --- /dev/null +++ b/HeliumEngine/src/org/hl/engine/math/Vector3f.java @@ -0,0 +1,44 @@ +package org.hl.engine.math; + +public class Vector3f { + private float x; + private float y; + private float z; + + // Just a vector if you know what I mean + public Vector3f (float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public void setVector(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public float getX() { + return x; + } + + public void setX(float x) { + this.x = x; + } + + public float getY() { + return y; + } + + public void setY(float y) { + this.y = y; + } + + public float getZ() { + return z; + } + + public void setZ(float z) { + this.z = z; + } +} diff --git a/HeliumEngine/src/org/hl/engine/utils/FileUtils.java b/HeliumEngine/src/org/hl/engine/utils/FileUtils.java new file mode 100644 index 0000000..f1a1390 --- /dev/null +++ b/HeliumEngine/src/org/hl/engine/utils/FileUtils.java @@ -0,0 +1,28 @@ +package org.hl.engine.utils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +public class FileUtils { + + // Reads a filepath and returns that as a String + public static String loadAsString(String filepath) { + StringBuilder result = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new + InputStreamReader(FileUtils.class.getResourceAsStream(filepath)))) { + String line = ""; + while ((line = reader.readLine()) != null) { + result.append(line) .append("\n"); + } + } catch (Exception e) { + System.err.println("Couldn't get the file at " + filepath); + System.exit(1); + + } + + + return result.toString(); + } +} diff --git a/HeliumEngine/src/resources/shaders/mainFragment.glsl b/HeliumEngine/src/resources/shaders/mainFragment.glsl new file mode 100644 index 0000000..85d4098 --- /dev/null +++ b/HeliumEngine/src/resources/shaders/mainFragment.glsl @@ -0,0 +1,9 @@ +#version 330 core + +in vec3 passColor; + +out vec4 outColor; + +void main() { + outColor = vec4(passColor, 1.0); +} diff --git a/HeliumEngine/src/resources/shaders/mainVertex.glsl b/HeliumEngine/src/resources/shaders/mainVertex.glsl new file mode 100644 index 0000000..9ae1698 --- /dev/null +++ b/HeliumEngine/src/resources/shaders/mainVertex.glsl @@ -0,0 +1,11 @@ +#version 330 core + +layout(location = 0) in vec3 position; +layout(location = 1) in vec3 color; + +out vec3 passColor; + +void main() { + gl_Position = vec4(position, 1.0); + passColor = color; +} \ No newline at end of file