Split project to submodules

This commit is contained in:
2021-01-31 10:22:27 +01:00
parent aa81a9d608
commit 8d1f78cb1d
44 changed files with 73 additions and 29 deletions

89
engine/build.gradle Executable file
View File

@@ -0,0 +1,89 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This generated file contains a sample Java application project to get you started.
* For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle
* User Manual available at https://docs.gradle.org/6.8.1/userguide/building_java_projects.html
*/
plugins {
id 'org.springframework.boot' version "$springBootVersion"
id 'io.spring.dependency-management' version "$springDependencyManagementVersion"
id 'java'
}
import org.gradle.internal.os.OperatingSystem
switch (OperatingSystem.current()) {
case OperatingSystem.LINUX:
def osArch = System.getProperty("os.arch")
project.ext.lwjglNatives = osArch.startsWith("arm") || osArch.startsWith("aarch64")
? "natives-linux-${osArch.contains("64") || osArch.startsWith("armv8") ? "arm64" : "arm32"}"
: "natives-linux"
break
case OperatingSystem.MAC_OS:
project.ext.lwjglNatives = "natives-macos"
break
case OperatingSystem.WINDOWS:
project.ext.lwjglNatives = System.getProperty("os.arch").contains("64") ? "natives-windows" : "natives-windows-x86"
break
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
jcenter()
}
bootJar {
enabled = false
}
jar {
enabled = true
}
dependencies {
implementation platform("org.lwjgl:lwjgl-bom:$lwjglVersion")
// LWJGL
implementation "org.lwjgl:lwjgl"
implementation "org.lwjgl:lwjgl-assimp"
implementation "org.lwjgl:lwjgl-bgfx"
implementation "org.lwjgl:lwjgl-glfw"
implementation "org.lwjgl:lwjgl-nanovg"
implementation "org.lwjgl:lwjgl-nuklear"
implementation "org.lwjgl:lwjgl-openal"
implementation "org.lwjgl:lwjgl-opengl"
implementation "org.lwjgl:lwjgl-par"
implementation "org.lwjgl:lwjgl-stb"
runtimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-assimp::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-bgfx::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-nanovg::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-nuklear::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-openal::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-par::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
// Spring
implementation 'org.springframework.boot:spring-boot-starter'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// Use JUnit test framework.
testImplementation 'junit:junit:4.13'
// This dependency is used by the application.
implementation "com.google.guava:guava:${guavaVersion}"
implementation "org.joml:joml:${jomlVersion}"
}

View File

@@ -0,0 +1,106 @@
package com.bartlomiejpluta.base.core.engine;
import com.bartlomiejpluta.base.core.logic.GameLogic;
import com.bartlomiejpluta.base.core.thread.ThreadManager;
import com.bartlomiejpluta.base.core.time.ChronoMeter;
import com.bartlomiejpluta.base.core.ui.Window;
import com.bartlomiejpluta.base.core.ui.WindowManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class DefaultGameEngine implements GameEngine {
private static final String THREAD_NAME = "Game Main Thread";
private final WindowManager windowManager;
private final ThreadManager threadManager;
private final GameLogic logic;
private final Thread thread;
private final Window window;
private final ChronoMeter chrono;
private final int targetUps;
private boolean running = false;
@Autowired
public DefaultGameEngine(WindowManager windowManager,
ThreadManager threadManager,
GameLogic logic,
@Value("${app.window.title}") String title,
@Value("${app.window.width}") int width,
@Value("${app.window.height}") int height,
@Value("${app.core.targetUps}") int targetUps) {
this.windowManager = windowManager;
this.threadManager = threadManager;
this.logic = logic;
this.window = windowManager.createWindow(title, width, height);
this.thread = threadManager.createThread(THREAD_NAME, this::run);
this.chrono = new ChronoMeter();
this.targetUps = targetUps;
}
private void run() {
try {
init();
loop();
} finally {
cleanUp();
}
}
private void init() {
log.info("Initializing game engine");
window.init();
chrono.init();
logic.init(window);
}
private void loop() {
log.info("Starting game loop");
running = true;
var dt = 0.0f;
var accumulator = 0.0f;
var step = 1.0f / targetUps;
while (running && !window.shouldClose()) {
dt = chrono.getElapsedTime();
accumulator += dt;
input();
while (accumulator >= step) {
update(dt);
accumulator -= step;
}
render();
}
}
private void input() {
logic.input(window);
}
private void update(float dt) {
logic.update(dt);
}
private void render() {
window.update();
logic.render(window);
}
private void cleanUp() {
logic.cleanUp();
}
@Override
public void start() {
thread.start();
}
}

View File

@@ -0,0 +1,5 @@
package com.bartlomiejpluta.base.core.engine;
public interface GameEngine {
void start();
}

View File

@@ -0,0 +1,22 @@
package com.bartlomiejpluta.base.core.error;
public class AppException extends RuntimeException {
public AppException() {
}
public AppException(String message, Object... args) {
super(String.format(message, args));
}
public AppException(String message, Throwable cause) {
super(message, cause);
}
public AppException(Throwable cause) {
super(cause);
}
public AppException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -0,0 +1,72 @@
package com.bartlomiejpluta.base.core.gl.object.material;
import com.bartlomiejpluta.base.core.gl.object.texture.Texture;
import lombok.Getter;
import org.joml.Vector2f;
import org.joml.Vector4f;
@Getter
public class Material {
private final Vector4f color = new Vector4f();
private final Vector2f spriteSize = new Vector2f(1, 1);
private final Vector2f spritePosition = new Vector2f(0, 0);
private final Texture texture;
private Material(Texture texture, float r, float g, float b, float alpha) {
this.texture = texture;
setColor(r, g, b, alpha);
}
public void setColor(float r, float g, float b, float alpha) {
color.x = r;
color.y = g;
color.z = b;
color.w = alpha;
}
public void setSpriteSize(Vector2f spriteSize) {
this.spriteSize.x = spriteSize.x;
this.spriteSize.y = spriteSize.y;
}
public void setSpriteSize(float w, float h) {
this.spriteSize.x = w;
this.spriteSize.y = h;
}
public void setSpritePosition(Vector2f spritePosition) {
this.spritePosition.x = spritePosition.x;
this.spritePosition.y = spritePosition.y;
}
public void setSpritePosition(float x, float y) {
this.spritePosition.x = x;
this.spritePosition.y = y;
}
public boolean hasTexture() {
return texture != null;
}
public void activateTextureIfExists() {
if(hasTexture()) {
texture.activate();
}
}
public static Material colored(float r, float g, float b, float alpha) {
return new Material(null, r, g, b, alpha);
}
public static Material textured(Texture texture) {
return new Material(texture, 1, 1, 1, 1);
}
public static Material textured(Texture texture, float r, float g, float b, float alpha) {
return new Material(texture, r, g, b, alpha);
}
public static Material textured(Texture texture, float alpha) {
return new Material(texture, 1, 1, 1, alpha);
}
}

View File

@@ -0,0 +1,96 @@
package com.bartlomiejpluta.base.core.gl.object.mesh;
import com.bartlomiejpluta.base.core.gl.render.Renderable;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.core.ui.Window;
import org.lwjgl.opengl.GL15;
import org.lwjgl.system.MemoryStack;
import java.util.ArrayList;
import java.util.List;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
public class Mesh implements Renderable {
private final int vaoId;
private final List<Integer> vboIds = new ArrayList<>(2);
private final int elementsCount;
public Mesh(float[] vertices, float[] texCoords, int[] elements) {
try(var stack = MemoryStack.stackPush()) {
elementsCount = elements.length;
var verticesBuffer = stack.mallocFloat(vertices.length);
var texCoordsBuffer = stack.mallocFloat(texCoords.length);
var elementsBuffer = stack.mallocInt(elementsCount);
verticesBuffer.put(vertices).flip();
texCoordsBuffer.put(texCoords).flip();
elementsBuffer.put(elements).flip();
vaoId = glGenVertexArrays();
glBindVertexArray(vaoId);
int vboId = glGenBuffers();
vboIds.add(vboId);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, 0);
vboId = glGenBuffers();
vboIds.add(vboId);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, texCoordsBuffer, GL_STATIC_DRAW);
glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0);
vboId = glGenBuffers();
vboIds.add(vboId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementsBuffer, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
}
@Override
public void render(Window window, ShaderManager shaderManager) {
glBindVertexArray(vaoId);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glDrawElements(GL_TRIANGLES, elementsCount, GL_UNSIGNED_INT, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glBindVertexArray(0);
}
@Override
public void cleanUp() {
glDisableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
vboIds.forEach(GL15::glDeleteBuffers);
glBindVertexArray(0);
glDeleteVertexArrays(vaoId);
}
public static Mesh quad(float width, float height) {
var halfWidth = width/2;
var halfHeight = height/2;
var vertices = new float[] {
-halfWidth, -halfHeight,
-halfWidth, halfHeight,
halfWidth, halfHeight,
halfWidth, -halfHeight
};
var texCoords = new float[] { 0, 0, 0, 1, 1, 1, 1, 0 };
var elements = new int[] { 0, 1, 2, 2, 3, 0 };
return new Mesh(vertices, texCoords, elements);
}
}

View File

@@ -0,0 +1,29 @@
package com.bartlomiejpluta.base.core.gl.object.texture;
import com.bartlomiejpluta.base.core.util.res.ResourcesManager;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DefaultTextureManager implements TextureManager {
private final ResourcesManager resourcesManager;
private final Map<String, Texture> loadedTextures = new HashMap<>();
@Override
public Texture loadTexture(String textureFileName) {
var texture = loadedTextures.get(textureFileName);
if(texture != null) {
return texture;
}
var buffer = resourcesManager.loadResourceAsByteBuffer(textureFileName);
texture = new Texture(textureFileName, buffer);
loadedTextures.put(textureFileName, texture);
return texture;
}
}

View File

@@ -0,0 +1,60 @@
package com.bartlomiejpluta.base.core.gl.object.texture;
import com.bartlomiejpluta.base.core.error.AppException;
import lombok.Getter;
import org.lwjgl.system.MemoryStack;
import java.nio.ByteBuffer;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.GL_TEXTURE0;
import static org.lwjgl.opengl.GL13.glActiveTexture;
import static org.lwjgl.stb.STBImage.stbi_failure_reason;
import static org.lwjgl.stb.STBImage.stbi_load_from_memory;
public class Texture {
private static final int DESIRED_CHANNELS = 4;
private final int textureId;
@Getter
private final String fileName;
@Getter
private final int width;
@Getter
private final int height;
Texture(String textureFilename, ByteBuffer buffer) {
try (var stack = MemoryStack.stackPush()) {
var widthBuffer = stack.mallocInt(1);
var heightBuffer = stack.mallocInt(1);
var channelsBuffer = stack.mallocInt(1);
buffer = stbi_load_from_memory(buffer, widthBuffer, heightBuffer, channelsBuffer, DESIRED_CHANNELS);
if (buffer == null) {
throw new AppException("Image file [%s] could not be loaded: %s", textureFilename, stbi_failure_reason());
}
width = widthBuffer.get();
height = heightBuffer.get();
textureId = glGenTextures();
glBindTexture(GL_TEXTURE_2D, textureId);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
fileName = textureFilename;
}
}
public void activate() {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureId);
}
}

View File

@@ -0,0 +1,5 @@
package com.bartlomiejpluta.base.core.gl.object.texture;
public interface TextureManager {
Texture loadTexture(String textureFileName);
}

View File

@@ -0,0 +1,62 @@
package com.bartlomiejpluta.base.core.gl.render;
import com.bartlomiejpluta.base.core.gl.shader.constant.UniformName;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.core.ui.Window;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import static org.lwjgl.opengl.GL15.*;
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DefaultRenderer implements Renderer {
private final ShaderManager shaderManager;
@Override
public void init() {
log.info("Initializing renderer");
shaderManager
.createShader("default", "/shaders/default.vs", "/shaders/default.fs")
.selectShader("default")
.createUniform(UniformName.UNI_MODEL_MATRIX)
.createUniform(UniformName.UNI_VIEW_MATRIX)
.createUniform(UniformName.UNI_PROJECTION_MATRIX)
.createUniform(UniformName.UNI_OBJECT_COLOR)
.createUniform(UniformName.UNI_HAS_OBJECT_TEXTURE)
.createUniform(UniformName.UNI_TEXTURE_SAMPLER)
.createUniform(UniformName.UNI_SPRITE_SIZE)
.createUniform(UniformName.UNI_SPRITE_POSITION);
}
@Override
public void render(Window window, Renderable renderable) {
clear();
updateViewport(window);
shaderManager.selectShader("default").useSelectedShader();
renderable.render(window, shaderManager);
shaderManager.detachCurrentShader();
}
private void clear() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
private void updateViewport(Window window) {
if (window.isResized()) {
glViewport(0, 0, window.getWidth(), window.getHeight());
window.setResized(false);
}
}
@Override
public void cleanUp() {
shaderManager.cleanUp();
}
}

View File

@@ -0,0 +1,9 @@
package com.bartlomiejpluta.base.core.gl.render;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.core.ui.Window;
public interface Renderable {
void render(Window window, ShaderManager shaderManager);
void cleanUp();
}

View File

@@ -0,0 +1,11 @@
package com.bartlomiejpluta.base.core.gl.render;
import com.bartlomiejpluta.base.core.ui.Window;
public interface Renderer {
void init();
void render(Window window, Renderable renderable);
void cleanUp();
}

View File

@@ -0,0 +1,12 @@
package com.bartlomiejpluta.base.core.gl.shader.constant;
public interface UniformName {
String UNI_MODEL_MATRIX = "modelMatrix";
String UNI_VIEW_MATRIX = "viewMatrix";
String UNI_PROJECTION_MATRIX = "projectionMatrix";
String UNI_OBJECT_COLOR = "objectColor";
String UNI_HAS_OBJECT_TEXTURE = "hasTexture";
String UNI_TEXTURE_SAMPLER = "sampler";
String UNI_SPRITE_SIZE = "spriteSize";
String UNI_SPRITE_POSITION = "spritePosition";
}

View File

@@ -0,0 +1,147 @@
package com.bartlomiejpluta.base.core.gl.shader.manager;
import com.bartlomiejpluta.base.core.gl.shader.program.ShaderProgram;
import com.bartlomiejpluta.base.core.gl.shader.uniform.Uniform;
import com.bartlomiejpluta.base.core.util.res.ResourcesManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joml.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DefaultShaderManager implements ShaderManager {
private final ResourcesManager resourcesManager;
private final Map<String, ShaderProgram> shaders = new HashMap<>();
private ShaderProgram current;
@Override
public ShaderManager createShader(String programName, String vertexShaderFilename, String fragmentShaderFilename) {
log.info("Creating {} shader", programName);
var vertexShaderCode = resourcesManager.loadResourceAsString(vertexShaderFilename);
var fragmentShaderCode = resourcesManager.loadResourceAsString(fragmentShaderFilename);
var program = ShaderProgram.compile(vertexShaderCode, fragmentShaderCode);
shaders.put(programName, program);
return this;
}
@Override
public ShaderManager selectShader(String programName) {
current = shaders.get(programName);
return this;
}
@Override
public ShaderManager useSelectedShader() {
current.use();
return this;
}
@Override
public ShaderManager detachCurrentShader() {
current.detach();
return this;
}
@Override
public ShaderManager createUniform(String uniformName) {
current.createUniform(uniformName);
return this;
}
@Override
public ShaderManager createUniform(String uniformName, Uniform uniform) {
current.createUniform(uniformName, uniform);
return this;
}
@Override
public ShaderManager createUniforms(String uniformName, int size) {
current.createUniforms(uniformName, size);
return this;
}
@Override
public ShaderManager createUniforms(String uniformName, int size, Uniform uniform) {
current.createUniforms(uniformName, size, uniform);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, int value) {
current.setUniform(uniformName, value);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, boolean value) {
current.setUniform(uniformName, value);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, float value) {
current.setUniform(uniformName, value);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, Vector2f value) {
current.setUniform(uniformName, value);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, Vector3f value) {
current.setUniform(uniformName, value);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, Vector4f value) {
current.setUniform(uniformName, value);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, Matrix3f value) {
current.setUniform(uniformName, value);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, Matrix4f value) {
current.setUniform(uniformName, value);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, Uniform uniform) {
current.setUniform(uniformName, uniform);
return this;
}
@Override
public ShaderManager setUniform(String uniformName, int index, Uniform uniform) {
current.setUniform(uniformName, index, uniform);
return this;
}
@Override
public ShaderManager setUniforms(String uniformName, Uniform[] uniforms) {
current.setUniforms(uniformName, uniforms);
return this;
}
@Override
public void cleanUp() {
shaders.forEach((name, program) -> program.cleanUp());
}
}

View File

@@ -0,0 +1,46 @@
package com.bartlomiejpluta.base.core.gl.shader.manager;
import com.bartlomiejpluta.base.core.gl.shader.uniform.Uniform;
import org.joml.*;
public interface ShaderManager {
ShaderManager createShader(String programName, String vertexShaderFilename, String fragmentShaderFilename);
ShaderManager selectShader(String programName);
ShaderManager useSelectedShader();
ShaderManager detachCurrentShader();
ShaderManager createUniform(String uniformName);
ShaderManager createUniform(String uniformName, Uniform uniform);
ShaderManager createUniforms(String uniformName, int size);
ShaderManager createUniforms(String uniformName, int size, Uniform uniform);
ShaderManager setUniform(String uniformName, int value);
ShaderManager setUniform(String uniformName, boolean value);
ShaderManager setUniform(String uniformName, float value);
ShaderManager setUniform(String uniformName, Vector2f value);
ShaderManager setUniform(String uniformName, Vector3f value);
ShaderManager setUniform(String uniformName, Vector4f value);
ShaderManager setUniform(String uniformName, Matrix3f value);
ShaderManager setUniform(String uniformName, Matrix4f value);
ShaderManager setUniform(String uniformName, Uniform uniform);
ShaderManager setUniform(String uniformName, int index, Uniform uniform);
ShaderManager setUniforms(String uniformName, Uniform[] uniforms);
void cleanUp();
}

View File

@@ -0,0 +1,173 @@
package com.bartlomiejpluta.base.core.gl.shader.program;
import com.bartlomiejpluta.base.core.error.AppException;
import com.bartlomiejpluta.base.core.gl.shader.uniform.Uniform;
import lombok.extern.slf4j.Slf4j;
import org.joml.*;
import org.lwjgl.system.MemoryStack;
import java.util.HashMap;
import java.util.Map;
import static java.lang.String.format;
import static org.lwjgl.opengl.GL20.*;
@Slf4j
public class ShaderProgram {
private final int programId;
private final int vertexShaderId;
private final int fragmentShaderId;
private final Map<String, Integer> uniforms = new HashMap<>();
private ShaderProgram(String vertexShaderCode, String fragmentShaderCode) {
this.programId = glCreateProgram();
if(this.programId == 0) {
throw new AppException("Could not create shader program");
}
this.vertexShaderId = createShader(vertexShaderCode, GL_VERTEX_SHADER);
this.fragmentShaderId = createShader(fragmentShaderCode, GL_FRAGMENT_SHADER);
linkProgram();
}
private int createShader(String shaderCode, int shaderType) {
int shaderId = glCreateShader(shaderType);
if(shaderId == 0) {
throw new AppException("Could not create shader of type: %s", shaderType);
}
glShaderSource(shaderId, shaderCode);
glCompileShader(shaderId);
if(glGetShaderi(shaderId, GL_COMPILE_STATUS) == 0) {
throw new AppException("Could not compile shader code: %s", glGetShaderInfoLog(shaderId, 1024));
}
glAttachShader(programId, shaderId);
return shaderId;
}
private void linkProgram() {
glLinkProgram(programId);
if(glGetProgrami(programId, GL_LINK_STATUS) == 0) {
throw new AppException("Could not link shader program: %s", glGetProgramInfoLog(programId, 1024));
}
if(vertexShaderId != 0) {
glDetachShader(programId, vertexShaderId);
}
if(fragmentShaderId != 0) {
glDetachShader(programId, fragmentShaderId);
}
glValidateProgram(programId);
if(glGetProgrami(programId, GL_VALIDATE_STATUS) == 0) {
log.warn("Program validation failed: {}", glGetProgramInfoLog(programId, 1024));
}
}
public void createUniform(String uniformName) {
int location = glGetUniformLocation(programId, uniformName);
if(location < 0) {
throw new AppException("Could not find uniform: %s", uniformName);
}
uniforms.put(uniformName, location);
}
public void createUniform(String uniformName, Uniform uniform) {
uniform.createUniform(this, uniformName);
}
public void createUniforms(String uniformName, int size) {
for(int i=0; i<size; ++i) {
createUniform(format("%s[%d]", uniformName, i));
}
}
public void createUniforms(String uniformName, int size, Uniform uniform) {
for(int i=0; i<size; ++i) {
createUniform(format("%s[%d]", uniformName, i), uniform);
}
}
public void setUniform(String uniformName, int value) {
glUniform1i(uniforms.get(uniformName), value);
}
public void setUniform(String uniformName, boolean value) {
glUniform1i(uniforms.get(uniformName), value ? 1 : 0);
}
public void setUniform(String uniformName, float value) {
glUniform1f(uniforms.get(uniformName), value);
}
public void setUniform(String uniformName, Vector2f value) {
glUniform2f(uniforms.get(uniformName), value.x, value.y);
}
public void setUniform(String uniformName, Vector3f value) {
glUniform3f(uniforms.get(uniformName), value.x, value.y, value.z);
}
public void setUniform(String uniformName, Vector4f value) {
glUniform4f(uniforms.get(uniformName), value.x, value.y, value.z, value.w);
}
public void setUniform(String uniformName, Matrix3f value) {
try(var stack = MemoryStack.stackPush()) {
var buffer = stack.mallocFloat(3 * 3);
value.get(buffer);
glUniformMatrix4fv(uniforms.get(uniformName), false, buffer);
}
}
public void setUniform(String uniformName, Matrix4f value) {
try(var stack = MemoryStack.stackPush()) {
var buffer = stack.mallocFloat(4 * 4);
value.get(buffer);
glUniformMatrix4fv(uniforms.get(uniformName), false, buffer);
}
}
public void setUniform(String uniformName, Uniform uniform) {
uniform.setUniform(this, uniformName);
}
public void setUniform(String uniformName, int index, Uniform uniform) {
setUniform(format("%s[%d]", uniformName, index), uniform);
}
public void setUniforms(String uniformName, Uniform[] uniforms) {
var size = uniforms != null ? uniforms.length : 0;
for(int i=0; i<size; ++i) {
setUniform(format("%s[%d]", uniformName, i), uniforms[i]);
}
}
public void use() {
glUseProgram(programId);
}
public void detach() {
glUseProgram(0);
}
public void cleanUp() {
glUseProgram(0);
if(programId != 0) {
glDeleteProgram(programId);
}
}
public static ShaderProgram compile(String vertexShaderCode, String fragmentShaderCode) {
return new ShaderProgram(vertexShaderCode, fragmentShaderCode);
}
}

View File

@@ -0,0 +1,9 @@
package com.bartlomiejpluta.base.core.gl.shader.uniform;
import com.bartlomiejpluta.base.core.gl.shader.program.ShaderProgram;
public interface Uniform {
void createUniform(ShaderProgram shaderProgram, String uniformName);
void setUniform(ShaderProgram shaderProgram, String uniformName);
}

View File

@@ -0,0 +1,15 @@
package com.bartlomiejpluta.base.core.logic;
import com.bartlomiejpluta.base.core.ui.Window;
public interface GameLogic {
void init(Window window);
void input(Window window);
void update(float dt);
void render(Window window);
void cleanUp();
}

View File

@@ -0,0 +1,10 @@
package com.bartlomiejpluta.base.core.thread;
import org.springframework.stereotype.Component;
@Component
public class ThreadManager {
public Thread createThread(String name, Runnable runnable) {
return new Thread(runnable, name);
}
}

View File

@@ -0,0 +1,20 @@
package com.bartlomiejpluta.base.core.time;
public class ChronoMeter {
private double latchedTime;
public void init() {
latchedTime = getTime();
}
private double getTime() {
return System.nanoTime() / 1_000_000_000.0;
}
public float getElapsedTime() {
double time = getTime();
float elapsedTime = (float) (time - latchedTime);
latchedTime = time;
return elapsedTime;
}
}

View File

@@ -0,0 +1,114 @@
package com.bartlomiejpluta.base.core.ui;
import com.bartlomiejpluta.base.core.error.AppException;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.opengl.GL;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.system.MemoryUtil.NULL;
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Window {
private final String title;
private long windowHandle;
@Getter
private int width;
@Getter
private int height;
@Getter
@Setter
private boolean resized;
public void init() {
// Setup an error callback. The default implementation
// will print the error message in System.err.
GLFWErrorCallback.createPrint(System.err).set();
// Initialize GLFW. Most GLFW functions will not work before doing this.
if (!glfwInit()) {
throw new AppException("Unable to initialize GLFW");
}
glfwDefaultWindowHints(); // optional, the current window hints are already the default
glfwWindowHint(GLFW_VISIBLE, GL_FALSE); // the window will stay hidden after creation
glfwWindowHint(GLFW_RESIZABLE, GL_TRUE); // the window will be resizable
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
// Create the window
windowHandle = glfwCreateWindow(width, height, title, NULL, NULL);
if (windowHandle == NULL) {
throw new AppException("Failed to create the GLFW window");
}
// Setup resize callback
glfwSetFramebufferSizeCallback(windowHandle, (window, width, height) -> {
Window.this.width = width;
Window.this.height = height;
Window.this.resized = true;
});
// Setup a key callback. It will be called every time a key is pressed, repeated or released.
glfwSetKeyCallback(windowHandle, (window, key, scancode, action, mods) -> {
if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
glfwSetWindowShouldClose(window, true); // We will detect this in the rendering loop
}
});
// Get the resolution of the primary monitor
GLFWVidMode videoMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
// Center our window
glfwSetWindowPos(windowHandle, (videoMode.width() - width) / 2, (videoMode.height() - height) / 2);
// Make the OpenGL context current
glfwMakeContextCurrent(windowHandle);
// Enable V-Sync
glfwSwapInterval(1);
// Make the window visible
glfwShowWindow(windowHandle);
GL.createCapabilities();
// Support for transparencies
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Set the clear color
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
}
public void update() {
glfwSwapBuffers(windowHandle);
glfwPollEvents();
}
public boolean isKeyPressed(int keyCode) {
return glfwGetKey(windowHandle, keyCode) == GLFW_PRESS;
}
public void clear(float r, float g, float b, float alpha) {
glClearColor(r, g, b, alpha);
}
public boolean shouldClose() {
return glfwWindowShouldClose(windowHandle);
}
public static Window create(String title, int width, int height) {
return new Window(title, -1, width, height, false);
}
}

View File

@@ -0,0 +1,10 @@
package com.bartlomiejpluta.base.core.ui;
import org.springframework.stereotype.Component;
@Component
public class WindowManager {
public Window createWindow(String title, int width, int height) {
return Window.create(title, width, height);
}
}

View File

@@ -0,0 +1,35 @@
package com.bartlomiejpluta.base.core.util.res;
import com.bartlomiejpluta.base.core.error.AppException;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Scanner;
@Component
public class ResourcesManager {
public String loadResourceAsString(String fileName) {
try (InputStream in = ResourcesManager.class.getResourceAsStream(fileName);
Scanner scanner = new Scanner(in, java.nio.charset.StandardCharsets.UTF_8.name())) {
return scanner.useDelimiter("\\A").next();
} catch (Exception e) {
throw new AppException(e);
}
}
public ByteBuffer loadResourceAsByteBuffer(String fileName) {
try {
var bytes = ResourcesManager.class.getResourceAsStream(fileName).readAllBytes();
return ByteBuffer
.allocateDirect(bytes.length)
.order(ByteOrder.nativeOrder())
.put(bytes)
.flip();
} catch (IOException e) {
throw new AppException(e);
}
}
}

View File

@@ -0,0 +1,25 @@
package com.bartlomiejpluta.base.core.world.animation;
import com.bartlomiejpluta.base.core.gl.object.material.Material;
import com.bartlomiejpluta.base.core.gl.object.mesh.Mesh;
import com.bartlomiejpluta.base.core.world.object.RenderableObject;
import org.joml.Vector2f;
import org.joml.Vector2i;
public abstract class AnimationableObject extends RenderableObject {
public AnimationableObject(Mesh mesh, Material material) {
super(mesh);
setMaterial(material);
var dimensions = getSpriteSheetDimensions();
material.setSpriteSize(1/(float) dimensions.x, 1/(float) dimensions.y);
}
// Returns time in ms between frames
public abstract int getAnimationSpeed();
public abstract Vector2i getSpriteSheetDimensions();
public abstract boolean shouldAnimate();
public abstract Vector2f[] getSpriteAnimationFramesPositions();
}

View File

@@ -0,0 +1,5 @@
package com.bartlomiejpluta.base.core.world.animation;
public interface Animator {
void animate(AnimationableObject[] objects);
}

View File

@@ -0,0 +1,25 @@
package com.bartlomiejpluta.base.core.world.animation;
import org.springframework.stereotype.Component;
@Component
public class DefaultAnimator implements Animator {
@Override
public void animate(AnimationableObject[] objects) {
for (var object : objects) {
animate(object);
}
}
private void animate(AnimationableObject object) {
if(object.shouldAnimate()) {
var positions = object.getSpriteAnimationFramesPositions();
var delay = object.getAnimationSpeed();
var currentPosition = (int) (System.currentTimeMillis() % (positions.length * delay)) / delay;
var spriteSize = object.getMaterial().getSpriteSize();
var current = positions[currentPosition];
object.getMaterial().setSpritePosition(spriteSize.x * current.x, spriteSize.y * current.y);
}
}
}

View File

@@ -0,0 +1,22 @@
package com.bartlomiejpluta.base.core.world.camera;
import com.bartlomiejpluta.base.core.ui.Window;
import com.bartlomiejpluta.base.core.world.object.Object;
import org.joml.Matrix4f;
public class Camera extends Object {
private final Matrix4f projectionMatrix = new Matrix4f();
private final Matrix4f viewMatrix = new Matrix4f();
public Matrix4f getProjectionMatrix(Window window) {
return projectionMatrix
.identity()
.setOrtho2D(0, window.getWidth(), window.getHeight(), 0);
}
public Matrix4f getViewMatrix() {
return viewMatrix
.identity()
.translate(-position.x, -position.y, 0);
}
}

View File

@@ -0,0 +1,47 @@
package com.bartlomiejpluta.base.core.world.map;
import com.bartlomiejpluta.base.core.world.tileset.model.Tile;
import lombok.Getter;
import java.util.Arrays;
import java.util.Objects;
public class GameMap {
private static final int LAYERS = 4;
private final Tile[][] map;
private final float scale;
@Getter
private final int rows;
@Getter
private final int cols;
public GameMap(int rows, int cols, float scale) {
this.rows = rows;
this.cols = cols;
this.scale = scale;
map = new Tile[LAYERS][rows * cols];
}
public void setTile(int layer, int row, int col, Tile tile) {
recalculateTileGeometry(tile, row, col);
map[layer][row * cols + col] = tile;
}
private void recalculateTileGeometry(Tile tile, int i, int j) {
tile.setScale(scale);
var size = tile.getWidth();
var offset = size * scale;
tile.setPosition(i * offset, j * offset);
}
public Tile[] getLayer(int layer) {
return map[layer];
}
public void cleanUp() {
Arrays.stream(map).flatMap(Arrays::stream).filter(Objects::nonNull).forEach(Tile::cleanUp);
}
}

View File

@@ -0,0 +1,60 @@
package com.bartlomiejpluta.base.core.world.object;
import lombok.Getter;
import lombok.Setter;
import org.joml.Matrix4f;
import org.joml.Vector2f;
import static java.lang.Math.toRadians;
public abstract class Object {
private final Matrix4f modelMatrix = new Matrix4f();
@Getter
protected final Vector2f position = new Vector2f(0, 0);
@Getter
@Setter
protected float rotation;
@Getter
@Setter
protected float scale = 1.0f;
public Object setPosition(float x, float y) {
position.x = x;
position.y = y;
return this;
}
public Object setPosition(Vector2f position) {
this.position.x = position.x;
this.position.y = position.y;
return this;
}
public Object movePosition(float x, float y) {
position.x += x;
position.y += y;
return this;
}
public Object movePosition(Vector2f position) {
this.position.x += position.x;
this.position.y += position.y;
return this;
}
public Object moveRotation(float rotation) {
this.rotation += rotation;
return this;
}
public Matrix4f getModelMatrix() {
return modelMatrix
.identity()
.translate(position.x, position.y, 0)
.rotateZ((float) toRadians(-rotation))
.scale(scale);
}
}

View File

@@ -0,0 +1,30 @@
package com.bartlomiejpluta.base.core.world.object;
import com.bartlomiejpluta.base.core.gl.object.material.Material;
import com.bartlomiejpluta.base.core.gl.object.mesh.Mesh;
import com.bartlomiejpluta.base.core.gl.render.Renderable;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.core.ui.Window;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
@RequiredArgsConstructor
public abstract class RenderableObject extends Object implements Renderable {
private final Mesh mesh;
@Getter
@Setter
private Material material;
@Override
public void render(Window window, ShaderManager shaderManager) {
getMaterial().activateTextureIfExists();
mesh.render(window, shaderManager);
}
@Override
public void cleanUp() {
mesh.cleanUp();
}
}

View File

@@ -0,0 +1,57 @@
package com.bartlomiejpluta.base.core.world.scene;
import com.bartlomiejpluta.base.core.gl.render.Renderable;
import com.bartlomiejpluta.base.core.gl.shader.constant.UniformName;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.core.ui.Window;
import com.bartlomiejpluta.base.core.world.camera.Camera;
import com.bartlomiejpluta.base.core.world.object.RenderableObject;
import com.bartlomiejpluta.base.core.world.map.GameMap;
import lombok.AllArgsConstructor;
import lombok.Setter;
@AllArgsConstructor
public class Scene implements Renderable {
private final Camera camera;
@Setter
private GameMap map;
@Override
public void render(Window window, ShaderManager shaderManager) {
shaderManager.setUniform(UniformName.UNI_PROJECTION_MATRIX, camera.getProjectionMatrix(window));
shaderManager.setUniform(UniformName.UNI_VIEW_MATRIX, camera.getViewMatrix());
renderArray(map.getLayer(0), window, shaderManager);
renderArray(map.getLayer(1), window, shaderManager);
// Player will be rendered here
renderArray(map.getLayer(2), window, shaderManager);
renderArray(map.getLayer(3), window, shaderManager);
}
private <T extends RenderableObject> void renderArray(T[] objects, Window window, ShaderManager shaderManager) {
for (var object : objects) {
if (object != null) {
renderObject(object, window, shaderManager);
}
}
}
private <T extends RenderableObject> void renderObject(T object, Window window, ShaderManager shaderManager) {
shaderManager.setUniform(UniformName.UNI_MODEL_MATRIX, object.getModelMatrix());
shaderManager.setUniform(UniformName.UNI_OBJECT_COLOR, object.getMaterial().getColor());
shaderManager.setUniform(UniformName.UNI_HAS_OBJECT_TEXTURE, object.getMaterial().hasTexture());
shaderManager.setUniform(UniformName.UNI_TEXTURE_SAMPLER, 0);
shaderManager.setUniform(UniformName.UNI_SPRITE_SIZE, object.getMaterial().getSpriteSize());
shaderManager.setUniform(UniformName.UNI_SPRITE_POSITION, object.getMaterial().getSpritePosition());
object.render(window, shaderManager);
}
@Override
public void cleanUp() {
map.cleanUp();
}
}

View File

@@ -0,0 +1,29 @@
package com.bartlomiejpluta.base.core.world.tileset.manager;
import com.bartlomiejpluta.base.core.gl.object.texture.TextureManager;
import com.bartlomiejpluta.base.core.world.tileset.model.TileSet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class DefaultTileSetManager implements TileSetManager {
private final TextureManager textureManager;
private final int tileWidth;
private final int tileHeight;
@Autowired
public DefaultTileSetManager(
TextureManager textureManager,
@Value("${app.map.tile.width}") int tileWidth,
@Value("${app.map.tile.width}") int tileHeight) {
this.textureManager = textureManager;
this.tileWidth = tileWidth;
this.tileHeight = tileHeight;
}
@Override
public TileSet createTileSet(String tileSetFileName, int rows, int columns) {
return new TileSet(textureManager.loadTexture(tileSetFileName), rows, columns, tileWidth, tileHeight);
}
}

View File

@@ -0,0 +1,7 @@
package com.bartlomiejpluta.base.core.world.tileset.manager;
import com.bartlomiejpluta.base.core.world.tileset.model.TileSet;
public interface TileSetManager {
TileSet createTileSet(String tileSetFileName, int rows, int columns);
}

View File

@@ -0,0 +1,19 @@
package com.bartlomiejpluta.base.core.world.tileset.model;
import com.bartlomiejpluta.base.core.gl.object.material.Material;
import com.bartlomiejpluta.base.core.gl.object.mesh.Mesh;
import com.bartlomiejpluta.base.core.world.object.RenderableObject;
import lombok.Getter;
@Getter
public class Tile extends RenderableObject {
private final int width;
private final int height;
Tile(Mesh mesh, Material material, int width, int height) {
super(mesh);
this.width = width;
this.height = height;
setMaterial(material);
}
}

View File

@@ -0,0 +1,34 @@
package com.bartlomiejpluta.base.core.world.tileset.model;
import com.bartlomiejpluta.base.core.gl.object.material.Material;
import com.bartlomiejpluta.base.core.gl.object.mesh.Mesh;
import com.bartlomiejpluta.base.core.gl.object.texture.Texture;
public class TileSet {
private final Texture texture;
private final int rows;
private final int columns;
private final float columnStep;
private final float rowStep;
private final int tileWidth;
private final int tileHeight;
private final Mesh mesh;
public TileSet(Texture texture, int rows, int columns, int tileWidth, int tileHeight) {
this.texture = texture;
this.rows = rows;
this.columns = columns;
this.columnStep = 1/(float) columns;
this.rowStep = 1/(float) rows;
this.tileWidth = tileWidth;
this.tileHeight = tileHeight;
this.mesh = Mesh.quad(tileWidth, tileHeight);
}
public Tile getTile(int m, int n) {
var material = Material.textured(texture);
material.setSpriteSize(columnStep, rowStep);
material.setSpritePosition(n * columnStep, m * rowStep);
return new Tile(mesh, material, tileWidth, tileHeight);
}
}

View File

@@ -0,0 +1,23 @@
#version 330
uniform vec4 objectColor;
uniform int hasTexture;
uniform sampler2D sampler;
uniform vec2 spriteSize;
uniform vec2 spritePosition;
in vec2 fragmentTexCoord;
out vec4 fragColor;
void main()
{
if(hasTexture == 1)
{
fragColor = objectColor * texture(sampler, fragmentTexCoord * spriteSize + spritePosition);
}
else
{
fragColor = objectColor;
}
}

View File

@@ -0,0 +1,16 @@
#version 330
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
layout(location=0) in vec2 position;
layout(location=1) in vec2 texCoord;
out vec2 fragmentTexCoord;
void main()
{
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 0.0, 1.0);
fragmentTexCoord = texCoord;
}