Compare commits
10 Commits
9a4ef72765
...
c28ec45f8c
| Author | SHA1 | Date | |
|---|---|---|---|
| c28ec45f8c | |||
| f13d4e3902 | |||
| eb3a5e7dd1 | |||
| 9cf088cc6c | |||
| 42f162a03a | |||
| edff4b27db | |||
| 2a66ed1062 | |||
| 1b7b9e4ffd | |||
| 31d8cac12a | |||
| 38c5021090 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ target/
|
||||
|
||||
.classpath
|
||||
.project
|
||||
.factorypath
|
||||
|
||||
8
assets/shaders/default.frag.glsl
Normal file
8
assets/shaders/default.frag.glsl
Normal file
@@ -0,0 +1,8 @@
|
||||
#version 330 core
|
||||
|
||||
in vec4 fColour;
|
||||
out vec4 colour;
|
||||
|
||||
void main() {
|
||||
colour = fColour;
|
||||
}
|
||||
11
assets/shaders/default.vert.glsl
Normal file
11
assets/shaders/default.vert.glsl
Normal file
@@ -0,0 +1,11 @@
|
||||
#version 330 core
|
||||
|
||||
layout (location = 0) in vec3 aPos;
|
||||
layout (location = 1) in vec4 aColour;
|
||||
|
||||
out vec4 fColour;
|
||||
|
||||
void main() {
|
||||
fColour = aColour;
|
||||
gl_Position = vec4(aPos, 1.0);
|
||||
}
|
||||
17
pom.xml
17
pom.xml
@@ -35,9 +35,20 @@
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>5.19.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.38</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>5.13.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.lwjgl</groupId>
|
||||
|
||||
37
src/main/java/org/hirw/game/Keyboard.java
Normal file
37
src/main/java/org/hirw/game/Keyboard.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package org.hirw.game;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class Keyboard {
|
||||
private static Keyboard instance;
|
||||
private HashMap<Integer, Boolean> buttons;
|
||||
|
||||
private Keyboard() {
|
||||
this.buttons = new HashMap<Integer, Boolean>();
|
||||
}
|
||||
|
||||
public static Keyboard getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (Keyboard.class) {
|
||||
if (instance == null) {
|
||||
instance = new Keyboard();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static void keyCallback(long window, int keyCode, int _scan, int action, int _modifiers) {
|
||||
if (action == GLFW_PRESS) {
|
||||
getInstance().buttons.put(keyCode, true);
|
||||
} else if (action == GLFW_RELEASE) {
|
||||
getInstance().buttons.put(keyCode, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isPressed(int keyCode) {
|
||||
return getInstance().buttons.getOrDefault(keyCode, false);
|
||||
}
|
||||
}
|
||||
67
src/main/java/org/hirw/game/Mouse.java
Normal file
67
src/main/java/org/hirw/game/Mouse.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package org.hirw.game;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class Mouse {
|
||||
private double x, y, oldX, oldY;
|
||||
private HashMap<Integer, Boolean> buttons;
|
||||
|
||||
private static Mouse instance;
|
||||
|
||||
public interface Buttons {
|
||||
public final int LEFT = GLFW_MOUSE_BUTTON_LEFT;
|
||||
public final int RIGHT = GLFW_MOUSE_BUTTON_RIGHT;
|
||||
}
|
||||
|
||||
private Mouse() {
|
||||
this.buttons = new HashMap<Integer, Boolean>();
|
||||
}
|
||||
|
||||
public static Mouse getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (Mouse.class) { // Double checked locking
|
||||
if (instance == null) {
|
||||
instance = new Mouse();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static void cursorPositionCallback(long window, double newX, double newY) {
|
||||
getInstance().oldX = getInstance().x;
|
||||
getInstance().oldY = getInstance().y;
|
||||
getInstance().x = newX;
|
||||
getInstance().y = newY;
|
||||
}
|
||||
|
||||
public static void mouseButtonCallback(long window, int keyCode, int action, int _modifiers) {
|
||||
if (action == GLFW_PRESS) {
|
||||
getInstance().buttons.put(keyCode, true);
|
||||
} else if (action == GLFW_RELEASE) {
|
||||
getInstance().buttons.put(keyCode, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static double getX() {
|
||||
return getInstance().x;
|
||||
}
|
||||
|
||||
public static double getY() {
|
||||
return getInstance().y;
|
||||
}
|
||||
|
||||
public static double getDeltaX() {
|
||||
return getInstance().oldX - getInstance().x;
|
||||
}
|
||||
|
||||
public static double getDeltaY() {
|
||||
return getInstance().oldY - getInstance().y;
|
||||
}
|
||||
|
||||
public static boolean isPressed(int keyCode) {
|
||||
return getInstance().buttons.getOrDefault(keyCode, false);
|
||||
}
|
||||
}
|
||||
8
src/main/java/org/hirw/game/Scene.java
Normal file
8
src/main/java/org/hirw/game/Scene.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package org.hirw.game;
|
||||
|
||||
|
||||
public abstract class Scene {
|
||||
public Scene() {}
|
||||
|
||||
abstract void update();
|
||||
}
|
||||
20
src/main/java/org/hirw/game/SceneManager.java
Normal file
20
src/main/java/org/hirw/game/SceneManager.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package org.hirw.game;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import lombok.Getter;
|
||||
|
||||
final class SceneManager {
|
||||
private static final EnumMap<SceneType, Scene> SCENES =
|
||||
new EnumMap<>(
|
||||
Map.of(
|
||||
SceneType.SPLASH, new SplashScene(),
|
||||
SceneType.MENU, new SplashScene(),
|
||||
SceneType.GAME, new SplashScene()));
|
||||
|
||||
@Getter private static Scene scene = SCENES.get(SceneType.SPLASH);
|
||||
|
||||
public static void setScene(SceneType sType) {
|
||||
scene = SCENES.get(sType);
|
||||
}
|
||||
}
|
||||
7
src/main/java/org/hirw/game/SceneType.java
Normal file
7
src/main/java/org/hirw/game/SceneType.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package org.hirw.game;
|
||||
|
||||
public enum SceneType {
|
||||
SPLASH,
|
||||
MENU,
|
||||
GAME
|
||||
}
|
||||
115
src/main/java/org/hirw/game/Shader.java
Normal file
115
src/main/java/org/hirw/game/Shader.java
Normal file
@@ -0,0 +1,115 @@
|
||||
package org.hirw.game;
|
||||
|
||||
import static org.lwjgl.opengl.GL20.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hirw.game.util.Log;
|
||||
|
||||
public class Shader {
|
||||
private enum ShaderType {
|
||||
FRAG,
|
||||
VERT
|
||||
}
|
||||
|
||||
private static final EnumMap<ShaderType, Integer> SHADERS =
|
||||
new EnumMap<>(
|
||||
Map.of(
|
||||
ShaderType.FRAG, GL_FRAGMENT_SHADER,
|
||||
ShaderType.VERT, GL_VERTEX_SHADER));
|
||||
|
||||
private static final String DEFAULT_FRAG_PATH = "assets/shaders/default.frag.glsl";
|
||||
private static final String DEFAULT_VERT_PATH = "assets/shaders/default.vert.glsl";
|
||||
|
||||
@Getter private String vertexSource;
|
||||
@Getter private String fragmentSource;
|
||||
@Getter @Setter private int vertexID;
|
||||
@Getter @Setter private int fragmentID;
|
||||
@Getter @Setter private int shaderProgramID;
|
||||
|
||||
public Shader(String fragPath, String vertPath) {
|
||||
this.fragmentSource = readFromFile(fragPath);
|
||||
this.vertexSource = readFromFile(vertPath);
|
||||
}
|
||||
|
||||
public Shader() {
|
||||
this(DEFAULT_FRAG_PATH, DEFAULT_VERT_PATH);
|
||||
}
|
||||
|
||||
public void init() {
|
||||
compileShader(ShaderType.FRAG);
|
||||
compileShader(ShaderType.VERT);
|
||||
createProgram();
|
||||
}
|
||||
|
||||
private void compileShader(ShaderType shaderType) {
|
||||
int shaderID = glCreateShader(SHADERS.get(shaderType));
|
||||
|
||||
switch (shaderType) {
|
||||
case ShaderType.FRAG -> {
|
||||
setFragmentID(shaderID);
|
||||
glShaderSource(shaderID, getFragmentSource());
|
||||
}
|
||||
case ShaderType.VERT -> {
|
||||
setVertexID(shaderID);
|
||||
glShaderSource(shaderID, getVertexSource());
|
||||
}
|
||||
}
|
||||
|
||||
glCompileShader(shaderID);
|
||||
|
||||
if (glGetShaderi(shaderID, GL_COMPILE_STATUS) == GL_FALSE) {
|
||||
int len = glGetShaderi(shaderID, GL_INFO_LOG_LENGTH);
|
||||
Log.error(
|
||||
"Shader initialisation",
|
||||
String.format(
|
||||
"Failed to compile %s shader: %s",
|
||||
shaderType.toString(), glGetShaderInfoLog(shaderID, len)));
|
||||
}
|
||||
}
|
||||
|
||||
private void createProgram() {
|
||||
setShaderProgramID(glCreateProgram());
|
||||
glAttachShader(shaderProgramID, getVertexID());
|
||||
glAttachShader(shaderProgramID, getFragmentID());
|
||||
glLinkProgram(shaderProgramID);
|
||||
|
||||
int success = glGetProgrami(getShaderProgramID(), GL_LINK_STATUS);
|
||||
if (success == GL_FALSE) {
|
||||
int len = glGetProgrami(getShaderProgramID(), GL_INFO_LOG_LENGTH);
|
||||
|
||||
Log.error(
|
||||
"Shader initialisation",
|
||||
String.format(
|
||||
"Failed to create Shader Program: %s",
|
||||
glGetShaderInfoLog(getShaderProgramID(), len)));
|
||||
}
|
||||
}
|
||||
|
||||
private String readFromFile(String stringFilePath) {
|
||||
String source = "";
|
||||
|
||||
try {
|
||||
Path filePath = Paths.get(stringFilePath);
|
||||
source = Files.readString(filePath);
|
||||
|
||||
} catch (NoSuchFileException | InvalidPathException e) {
|
||||
Log.error(
|
||||
"Shader initialisation", "Couldn't open file (probably a bad path): " + stringFilePath);
|
||||
} catch (IOException e) {
|
||||
Log.error(
|
||||
"Shader initialisation",
|
||||
"An IO Exception occured while reading from file: " + stringFilePath);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
}
|
||||
19
src/main/java/org/hirw/game/SplashScene.java
Normal file
19
src/main/java/org/hirw/game/SplashScene.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package org.hirw.game;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.hirw.game.util.Time;
|
||||
|
||||
public class SplashScene extends Scene {
|
||||
private float ramp = 0.0f;
|
||||
|
||||
public void update() {
|
||||
if (Objects.isNull(Window.get().getGlfwWindow())) {
|
||||
return;
|
||||
}
|
||||
|
||||
glClearColor(this.ramp, this.ramp, this.ramp, 0.0f);
|
||||
this.ramp += 0.5f * Time.deltaTime();
|
||||
}
|
||||
}
|
||||
@@ -3,19 +3,18 @@ package org.hirw.game;
|
||||
import static org.lwjgl.glfw.Callbacks.*;
|
||||
import static org.lwjgl.glfw.GLFW.*;
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.system.MemoryStack.*;
|
||||
import static org.lwjgl.system.MemoryUtil.*;
|
||||
|
||||
import java.nio.*;
|
||||
import lombok.Getter;
|
||||
import org.hirw.game.util.Time;
|
||||
import org.lwjgl.Version;
|
||||
import org.lwjgl.glfw.*;
|
||||
import org.lwjgl.opengl.*;
|
||||
import org.lwjgl.system.*;
|
||||
|
||||
public class Window {
|
||||
private int width, height;
|
||||
private final String title;
|
||||
private long glfwWindow;
|
||||
@Getter private long glfwWindow;
|
||||
|
||||
private static Window window = null;
|
||||
|
||||
@@ -34,7 +33,6 @@ public class Window {
|
||||
}
|
||||
|
||||
public void blastOff() {
|
||||
logVersion();
|
||||
setup();
|
||||
loop();
|
||||
cleanUp();
|
||||
@@ -45,6 +43,8 @@ public class Window {
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
logVersion();
|
||||
|
||||
GLFWErrorCallback.createPrint(System.err).set();
|
||||
if (!glfwInit()) throw new IllegalStateException("Unable to initialize GLFW");
|
||||
|
||||
@@ -69,18 +69,25 @@ public class Window {
|
||||
// GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
||||
// }
|
||||
|
||||
glfwSetCursorPosCallback(glfwWindow, Mouse::cursorPositionCallback);
|
||||
glfwSetMouseButtonCallback(glfwWindow, Mouse::mouseButtonCallback);
|
||||
glfwSetKeyCallback(glfwWindow, Keyboard::keyCallback);
|
||||
|
||||
glfwSetWindowTitle(glfwWindow, this.title);
|
||||
glfwMakeContextCurrent(glfwWindow);
|
||||
glfwSwapInterval(1);
|
||||
glfwShowWindow(glfwWindow);
|
||||
|
||||
GL.createCapabilities();
|
||||
glClearColor(0.0f, 0.0f, 2.0f, 0.0f);
|
||||
Shader someShader = new Shader();
|
||||
someShader.init();
|
||||
}
|
||||
|
||||
private void loop() {
|
||||
// GL.createCapabilities(); // Does this maybe go in here?
|
||||
glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
while (!glfwWindowShouldClose(glfwWindow)) {
|
||||
Time.update();
|
||||
SceneManager.getScene().update();
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glfwSwapBuffers(glfwWindow);
|
||||
glfwPollEvents();
|
||||
|
||||
35
src/main/java/org/hirw/game/util/Log.java
Normal file
35
src/main/java/org/hirw/game/util/Log.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package org.hirw.game.util;
|
||||
|
||||
public final class Log {
|
||||
private interface Colours {
|
||||
final String BLACK = "\u001B[30m";
|
||||
final String BLACK_BG = "\u001B[40m";
|
||||
final String RED = "\u001B[31m";
|
||||
final String RED_BG = "\u001B[41m";
|
||||
final String GREEN = "\u001B[32m";
|
||||
final String GREEN_BG = "\u001B[42m";
|
||||
final String YELLOW = "\u001B[33m";
|
||||
final String YELLOW_BG = "\u001B[43m";
|
||||
final String BLUE = "\u001B[34m";
|
||||
final String PURPLE_BG = "\u001B[45m";
|
||||
final String CYAN = "\u001B[36m";
|
||||
final String CYAN_BG = "\u001B[46m";
|
||||
final String WHITE = "\u001B[37m";
|
||||
final String WHITE_BG = "\u001B[47m";
|
||||
final String ANSI_RESET = "\u001B[0m";
|
||||
}
|
||||
|
||||
public static void error(String errorStage, String errorDescription) {
|
||||
String fancyError = String.format("[%s] ", colourisedString(Colours.RED, "ERROR"));
|
||||
String fancyErrorStage = colourisedString(Colours.YELLOW, String.format("<%s> ", errorStage));
|
||||
System.err.println(fancyError + fancyErrorStage + errorDescription);
|
||||
}
|
||||
|
||||
private static String colourisedString(String colour, String string) {
|
||||
return colour + string + Colours.ANSI_RESET;
|
||||
}
|
||||
|
||||
private static String colourisedString(String colour, String otherColour, String string) {
|
||||
return colour + otherColour + string + Colours.ANSI_RESET;
|
||||
}
|
||||
}
|
||||
31
src/main/java/org/hirw/game/util/Time.java
Normal file
31
src/main/java/org/hirw/game/util/Time.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package org.hirw.game.util;
|
||||
|
||||
public final class Time {
|
||||
private static long lastTime = NanoTimer.system().nanoTime();
|
||||
private static long currentTime = NanoTimer.system().nanoTime();
|
||||
private static NanoTimer nanoTimer = NanoTimer.system();
|
||||
|
||||
public interface NanoTimer {
|
||||
long nanoTime();
|
||||
|
||||
static NanoTimer system() {
|
||||
return System::nanoTime;
|
||||
}
|
||||
}
|
||||
|
||||
static void setNanoTimer(NanoTimer timer) {
|
||||
nanoTimer = timer;
|
||||
lastTime = nanoTimer.nanoTime();
|
||||
currentTime = nanoTimer.nanoTime();
|
||||
}
|
||||
|
||||
public static void update() {
|
||||
lastTime = currentTime;
|
||||
currentTime = nanoTimer.nanoTime();
|
||||
}
|
||||
|
||||
public static float deltaTime() {
|
||||
final float ONE_SECOND = 1_000_000_000f;
|
||||
return (currentTime - lastTime) / ONE_SECOND;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package org.hirw.game;
|
||||
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestCase;
|
||||
import junit.framework.TestSuite;
|
||||
|
||||
/**
|
||||
* Unit test for simple App.
|
||||
*/
|
||||
public class AppTest
|
||||
extends TestCase
|
||||
{
|
||||
/**
|
||||
* Create the test case
|
||||
*
|
||||
* @param testName name of the test case
|
||||
*/
|
||||
public AppTest( String testName )
|
||||
{
|
||||
super( testName );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the suite of tests being tested
|
||||
*/
|
||||
public static Test suite()
|
||||
{
|
||||
return new TestSuite( AppTest.class );
|
||||
}
|
||||
|
||||
/**
|
||||
* Rigourous Test :-)
|
||||
*/
|
||||
public void testApp()
|
||||
{
|
||||
assertTrue( true );
|
||||
}
|
||||
}
|
||||
64
src/test/java/org/hirw/game/util/TimeTest.java
Normal file
64
src/test/java/org/hirw/game/util/TimeTest.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package org.hirw.game.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
class TimeTest {
|
||||
final float ONE_SECOND = 1_000_000_000f;
|
||||
final long ONE_MILLISECOND = 1_000_000L;
|
||||
final float MOE = 0.00000001f; // Margin of error
|
||||
|
||||
private Time.NanoTimer nanoTimer;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
nanoTimer = Mockito.mock(Time.NanoTimer.class);
|
||||
when(nanoTimer.nanoTime()).thenReturn(0L);
|
||||
Time.setNanoTimer(nanoTimer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeltaTimeInitialState() {
|
||||
float deltaTime = Time.deltaTime();
|
||||
assertEquals(deltaTime, 0f, MOE, "should be 0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeltaTimeAfterUpdate() {
|
||||
when(nanoTimer.nanoTime()).thenReturn(ONE_MILLISECOND);
|
||||
|
||||
Time.update();
|
||||
float deltaTime = Time.deltaTime();
|
||||
|
||||
assertEquals(deltaTime, ONE_MILLISECOND / ONE_SECOND, MOE, "should reflect time difference");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultipleUpdates() {
|
||||
when(nanoTimer.nanoTime()).thenReturn(ONE_MILLISECOND).thenReturn(ONE_MILLISECOND * 4);
|
||||
|
||||
Time.update();
|
||||
float delta1 = Time.deltaTime();
|
||||
|
||||
Time.update();
|
||||
float delta2 = Time.deltaTime();
|
||||
|
||||
assertEquals(delta1, ONE_MILLISECOND / ONE_SECOND, MOE, "should be 1/1000th second");
|
||||
assertEquals(delta2, (ONE_MILLISECOND * 3) / ONE_SECOND, MOE, "should be 3/1000th second");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConsistentStateAcrossCalls() {
|
||||
when(nanoTimer.nanoTime()).thenReturn(ONE_MILLISECOND);
|
||||
|
||||
Time.update();
|
||||
float delta1 = Time.deltaTime();
|
||||
float delta2 = Time.deltaTime();
|
||||
|
||||
assertEquals(delta1, delta2, MOE, "should be equal delta time");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user