Create scaffolding of batched rendering for tile layer
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package com.bartlomiejpluta.base.api.camera;
|
||||
|
||||
import com.bartlomiejpluta.base.api.context.Context;
|
||||
import com.bartlomiejpluta.base.api.move.Movable;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.internal.object.Placeable;
|
||||
import com.bartlomiejpluta.base.internal.render.BoundingBox;
|
||||
@@ -11,6 +10,8 @@ import org.joml.Matrix4fc;
|
||||
public interface Camera extends Placeable, BoundingBox {
|
||||
Matrix4fc computeViewModelMatrix(Matrix4fc modelMatrix);
|
||||
|
||||
Matrix4fc getProjectionMatrix();
|
||||
|
||||
boolean insideFrustum(float x, float y, float radius);
|
||||
|
||||
boolean insideFrustum(Context context, float x, float y);
|
||||
|
||||
@@ -5,7 +5,5 @@ import com.bartlomiejpluta.base.api.map.layer.base.Layer;
|
||||
public interface TileLayer extends Layer {
|
||||
void setTile(int row, int column, int tileId);
|
||||
|
||||
void setTile(int row, int column, int tileSetRow, int tileSetColumn);
|
||||
|
||||
void clearTile(int row, int column);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
package com.bartlomiejpluta.base.engine.core.gl.object.mesh;
|
||||
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.internal.gc.Disposable;
|
||||
import com.bartlomiejpluta.base.internal.render.Renderable;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.Getter;
|
||||
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 BatchedQuads implements Renderable, Disposable {
|
||||
// Texture: Quad:
|
||||
// (0,1) ---- (1,1) 1 ----------- 2
|
||||
// | | | |
|
||||
// | | => | TEXTURE |
|
||||
// | | | |
|
||||
// (0,0) ---- (1,0) 0 ----------- 3
|
||||
private static final float[] DEFAULT_TEXTURE_COORDINATES = new float[] {
|
||||
0, 0, // 0 - bottom left
|
||||
0, 1, // 1 - top left
|
||||
1, 1, // 2 - top right
|
||||
1, 0 // 3 - bottom right
|
||||
};
|
||||
|
||||
private static final int POSITION_VECTOR_LAYOUT_INDEX = 0;
|
||||
private static final int POSITION_VECTOR_LENGTH = 2;
|
||||
private static final int TEXTURE_COORDINATES_VECTOR_LAYOUT_INDEX = 1;
|
||||
private static final int TEXTURE_COORDINATES_VECTOR_LENGTH = 2;
|
||||
|
||||
private static final int VERTICES_PER_QUAD = 4;
|
||||
private static final int INDICES_PER_QUAD = 6;
|
||||
|
||||
private final int maxQuads;
|
||||
private final int vaoId;
|
||||
private final List<Integer> vboIds = new ArrayList<>(3);
|
||||
private final int maxVertices;
|
||||
private final int maxIndices;
|
||||
|
||||
private final float[] vertexBuffer;
|
||||
private final float[] texCoordBuffer;
|
||||
private final int[] indexBuffer;
|
||||
|
||||
@Getter
|
||||
private int currentQuadCount = 0;
|
||||
|
||||
public BatchedQuads(int maxQuads) {
|
||||
this.maxQuads = maxQuads;
|
||||
this.maxVertices = maxQuads * VERTICES_PER_QUAD * 2;
|
||||
this.maxIndices = maxQuads * INDICES_PER_QUAD;
|
||||
|
||||
this.vertexBuffer = new float[maxVertices];
|
||||
this.texCoordBuffer = new float[maxVertices];
|
||||
this.indexBuffer = new int[maxIndices];
|
||||
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
vaoId = glGenVertexArrays();
|
||||
glBindVertexArray(vaoId);
|
||||
|
||||
// Vertex buffer
|
||||
var vertexVboId = glGenBuffers();
|
||||
vboIds.add(vertexVboId);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertexVboId);
|
||||
glBufferData(GL_ARRAY_BUFFER, maxVertices * Float.BYTES, GL_DYNAMIC_DRAW);
|
||||
glVertexAttribPointer(POSITION_VECTOR_LAYOUT_INDEX, POSITION_VECTOR_LENGTH, GL_FLOAT, false, 0, 0);
|
||||
glEnableVertexAttribArray(0);
|
||||
|
||||
// Texture coordinates buffer
|
||||
var texCoordVboId = glGenBuffers();
|
||||
vboIds.add(texCoordVboId);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, texCoordVboId);
|
||||
glBufferData(GL_ARRAY_BUFFER, maxVertices * Float.BYTES, GL_DYNAMIC_DRAW);
|
||||
glVertexAttribPointer(TEXTURE_COORDINATES_VECTOR_LAYOUT_INDEX, TEXTURE_COORDINATES_VECTOR_LENGTH, GL_FLOAT, false, 0, 0);
|
||||
glEnableVertexAttribArray(1);
|
||||
|
||||
// Index buffer
|
||||
var indexVboId = glGenBuffers();
|
||||
vboIds.add(indexVboId);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexVboId);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, maxIndices * Integer.BYTES, GL_DYNAMIC_DRAW);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
|
||||
public int addQuad(Quad quad, float x, float y) {
|
||||
return addQuad(quad, x, y, DEFAULT_TEXTURE_COORDINATES);
|
||||
}
|
||||
|
||||
public int addQuad(Quad quad, float x, float y, float[] textureCoordinates) {
|
||||
if (currentQuadCount >= maxQuads) {
|
||||
throw new RuntimeException("Batch is full!");
|
||||
}
|
||||
|
||||
int quadId = currentQuadCount;
|
||||
|
||||
// Vertices
|
||||
var localVertices = quad.getVertices();
|
||||
int vertexStartIndex = currentQuadCount * VERTICES_PER_QUAD * 2;
|
||||
for (int i = 0; i < VERTICES_PER_QUAD; i++) {
|
||||
float localX = localVertices[i * 2];
|
||||
float localY = localVertices[i * 2 + 1];
|
||||
|
||||
vertexBuffer[vertexStartIndex + i * 2] = x + localX;
|
||||
vertexBuffer[vertexStartIndex + i * 2 + 1] = y + localY;
|
||||
}
|
||||
|
||||
// Texture
|
||||
var texCoordStartIndex = currentQuadCount * VERTICES_PER_QUAD * 2;
|
||||
System.arraycopy(textureCoordinates, 0, texCoordBuffer, texCoordStartIndex, textureCoordinates.length);
|
||||
|
||||
// Indices
|
||||
var indexStartIndex = currentQuadCount * INDICES_PER_QUAD;
|
||||
var vertexOffset = currentQuadCount * VERTICES_PER_QUAD;
|
||||
|
||||
int[] quadIndices = {
|
||||
// First triangle: indices 0-1-2
|
||||
// 1 ----------- 2
|
||||
// |XXXXXXXXXXX╱ |
|
||||
// |XXXXXXXX╱ |
|
||||
// |XXXXX╱ |
|
||||
// |XX╱ |
|
||||
// 0 ----------- 3
|
||||
vertexOffset + 0, vertexOffset + 1, vertexOffset + 2,
|
||||
|
||||
// Second triangle: indices 2-3-0
|
||||
// 1 ----------- 2
|
||||
// | ╱X|
|
||||
// | ╱XXXX|
|
||||
// | ╱XXXXXXX|
|
||||
// | ╱XXXXXXXXXX|
|
||||
// 0 ----------- 3
|
||||
vertexOffset + 2, vertexOffset + 3, vertexOffset + 0
|
||||
};
|
||||
|
||||
System.arraycopy(quadIndices, 0, indexBuffer, indexStartIndex, quadIndices.length);
|
||||
currentQuadCount++;
|
||||
|
||||
return quadId;
|
||||
}
|
||||
|
||||
public void setQuadTextureCoordinates(int quadId, float[] textureCoordinates) {
|
||||
if (quadId < 0 || quadId >= currentQuadCount) {
|
||||
throw new IllegalArgumentException("Invalid quad ID: " + quadId);
|
||||
}
|
||||
|
||||
var texCoordsIndex = quadId * VERTICES_PER_QUAD * 2;
|
||||
System.arraycopy(textureCoordinates, 0, texCoordBuffer, texCoordsIndex, textureCoordinates.length);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
currentQuadCount = 0;
|
||||
}
|
||||
|
||||
public void removeQuad(int quadId) {
|
||||
if (quadId < 0 || quadId >= currentQuadCount) {
|
||||
throw new IllegalArgumentException("Invalid quad ID: " + quadId);
|
||||
}
|
||||
|
||||
int lastQuadId = currentQuadCount - 1;
|
||||
|
||||
if (quadId != lastQuadId) {
|
||||
swapQuads(quadId, lastQuadId);
|
||||
}
|
||||
|
||||
currentQuadCount--;
|
||||
}
|
||||
|
||||
private void swapQuads(int quad1, int quad2) {
|
||||
// Swap vertices
|
||||
swapQuadData(vertexBuffer, quad1, quad2, VERTICES_PER_QUAD * 2);
|
||||
|
||||
// Swap texture coordinates
|
||||
swapQuadData(texCoordBuffer, quad1, quad2, VERTICES_PER_QUAD * 2);
|
||||
|
||||
// Swap indices
|
||||
swapQuadIndices(quad1, quad2);
|
||||
}
|
||||
|
||||
private void swapQuadData(float[] buffer, int quad1, int quad2, int dataSize) {
|
||||
var start1 = quad1 * dataSize;
|
||||
var start2 = quad2 * dataSize;
|
||||
|
||||
for (int i = 0; i < dataSize; i++) {
|
||||
var temp = buffer[start1 + i];
|
||||
buffer[start1 + i] = buffer[start2 + i];
|
||||
buffer[start2 + i] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
private void swapQuadIndices(int quad1, int quad2) {
|
||||
var start1 = quad1 * INDICES_PER_QUAD;
|
||||
var start2 = quad2 * INDICES_PER_QUAD;
|
||||
|
||||
var vertexOffset1 = quad1 * VERTICES_PER_QUAD;
|
||||
int[] newIndices1 = {
|
||||
vertexOffset1 + 0, vertexOffset1 + 1, vertexOffset1 + 2,
|
||||
vertexOffset1 + 2, vertexOffset1 + 3, vertexOffset1 + 0
|
||||
};
|
||||
|
||||
var vertexOffset2 = quad2 * VERTICES_PER_QUAD;
|
||||
int[] newIndices2 = {
|
||||
vertexOffset2 + 0, vertexOffset2 + 1, vertexOffset2 + 2,
|
||||
vertexOffset2 + 2, vertexOffset2 + 3, vertexOffset2 + 0
|
||||
};
|
||||
|
||||
System.arraycopy(newIndices1, 0, indexBuffer, start1, INDICES_PER_QUAD);
|
||||
System.arraycopy(newIndices2, 0, indexBuffer, start2, INDICES_PER_QUAD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
if (currentQuadCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
glBindVertexArray(vaoId);
|
||||
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
// Vertex buffer
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vboIds.get(0));
|
||||
var vertexFloatBuffer = stack.mallocFloat(currentQuadCount * VERTICES_PER_QUAD * 2);
|
||||
for (int i = 0; i < currentQuadCount * VERTICES_PER_QUAD * 2; i++) {
|
||||
vertexFloatBuffer.put(vertexBuffer[i]);
|
||||
}
|
||||
vertexFloatBuffer.flip();
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexFloatBuffer);
|
||||
|
||||
// Texture coordinate buffer
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vboIds.get(1));
|
||||
var texCoordFloatBuffer = stack.mallocFloat(currentQuadCount * VERTICES_PER_QUAD * 2);
|
||||
for (int i = 0; i < currentQuadCount * VERTICES_PER_QUAD * 2; i++) {
|
||||
texCoordFloatBuffer.put(texCoordBuffer[i]);
|
||||
}
|
||||
texCoordFloatBuffer.flip();
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, texCoordFloatBuffer);
|
||||
|
||||
// Index buffer
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIds.get(2));
|
||||
var indexIntBuffer = stack.mallocInt(currentQuadCount * INDICES_PER_QUAD);
|
||||
for (int i = 0; i < currentQuadCount * INDICES_PER_QUAD; i++) {
|
||||
indexIntBuffer.put(indexBuffer[i]);
|
||||
}
|
||||
indexIntBuffer.flip();
|
||||
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, indexIntBuffer);
|
||||
}
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
|
||||
glDrawElements(GL_TRIANGLES, currentQuadCount * INDICES_PER_QUAD, GL_UNSIGNED_INT, 0);
|
||||
|
||||
glDisableVertexAttribArray(0);
|
||||
glDisableVertexAttribArray(1);
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
glDisableVertexAttribArray(0);
|
||||
glDisableVertexAttribArray(1);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
|
||||
vboIds.forEach(GL15::glDeleteBuffers);
|
||||
|
||||
glBindVertexArray(0);
|
||||
glDeleteVertexArrays(vaoId);
|
||||
}
|
||||
|
||||
public boolean isFull() {
|
||||
return currentQuadCount >= maxQuads;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return currentQuadCount == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.bartlomiejpluta.base.engine.core.gl.object.mesh;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class Quad {
|
||||
private final float width;
|
||||
private final float height;
|
||||
private final float originX;
|
||||
private final float originY;
|
||||
private final float[] vertices;
|
||||
|
||||
public Quad(float width, float height, float originX, float originY) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.originX = originX;
|
||||
this.originY = originY;
|
||||
|
||||
// 1 ----------- 2
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// 0 ----------- 3
|
||||
this.vertices = new float[]{
|
||||
-originX, -originY, // 0 - bottom left
|
||||
-originX, height - originY, // 1 - top left
|
||||
width - originX, height - originY, // 2 - top right
|
||||
width - originX, -originY // 3 - bottom right
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
static float[] textureCoordinates = new float[]{
|
||||
0, 0, // 0 - bottom left
|
||||
0, 1, // 1 - top left
|
||||
1, 1, // 2 - top right
|
||||
1, 0 // 3 - bottom right
|
||||
};
|
||||
|
||||
static int[] elements = new int[]{
|
||||
// First triangle: indices 0-1-2
|
||||
// 1 ----------- 2
|
||||
// |XXXXXXXXXXX╱ |
|
||||
// |XXXXXXXX╱ |
|
||||
// |XXXXX╱ |
|
||||
// |XX╱ |
|
||||
// 0 ----------- 3
|
||||
0, 1, 2,
|
||||
|
||||
// Second triangle: indices 2-3-0
|
||||
// 1 ----------- 2
|
||||
// | ╱X|
|
||||
// | ╱XXXX|
|
||||
// | ╱XXXXXXX|
|
||||
// | ╱XXXXXXXXXX|
|
||||
// 0 ----------- 3
|
||||
2, 3, 0
|
||||
};
|
||||
}
|
||||
@@ -74,6 +74,31 @@ public class Texture implements Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
public float[] getTextureCoordinates(int id) {
|
||||
return getTextureCoordinates(id % columns, id / columns);
|
||||
}
|
||||
|
||||
public float[] getTextureCoordinates(int x, int y) {
|
||||
return getTextureCoordinates(x * (int) spriteSize.x(), y * (int) spriteSize.y(), (int) spriteSize.x(), (int) spriteSize.y());
|
||||
}
|
||||
|
||||
public float[] getTextureCoordinates(int textureX, int textureY, int tileWidth, int tileHeight) {
|
||||
var normalizedX = (float) textureX / width;
|
||||
var normalizedY = (float) textureY / height;
|
||||
var normalizedWidth = (float) tileWidth / width;
|
||||
var normalizedHeight = (float) tileHeight / height;
|
||||
|
||||
var xEnd = (normalizedX + normalizedWidth);
|
||||
var yEnd = (normalizedY + normalizedHeight);
|
||||
|
||||
return new float[]{
|
||||
normalizedX, normalizedY, // bottom left
|
||||
normalizedX, yEnd, // top left
|
||||
xEnd, yEnd, // top right
|
||||
xEnd, normalizedY // bottom right
|
||||
};
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||
|
||||
@@ -2,10 +2,7 @@ package com.bartlomiejpluta.base.engine.core.gl.render;
|
||||
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.CounterName;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.RenderConstants;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.Shader;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName;
|
||||
import com.bartlomiejpluta.base.internal.render.Renderable;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -13,8 +10,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static org.lwjgl.opengl.GL15.*;
|
||||
|
||||
@Slf4j
|
||||
@@ -28,32 +24,7 @@ public class DefaultRenderer implements Renderer {
|
||||
log.info("Initializing renderer");
|
||||
|
||||
log.info("Registering shaders");
|
||||
for (var shader : Shader.values()) {
|
||||
shaderManager.createShader(shader.name, shader.vertex, shader.fragment);
|
||||
}
|
||||
|
||||
log.info("Registering uniforms");
|
||||
shaderManager
|
||||
.activateShader(Shader.DEFAULT.name)
|
||||
.createUniform(UniformName.UNI_VIEW_MODEL_MATRIX)
|
||||
.createUniform(UniformName.UNI_MODEL_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)
|
||||
.createUniform(UniformName.UNI_AMBIENT)
|
||||
.createUniform(UniformName.UNI_ACTIVE_LIGHTS)
|
||||
.createCounter(CounterName.LIGHT);
|
||||
|
||||
for(int i=0; i<RenderConstants.MAX_LIGHTS; ++i) {
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].position");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].intensity");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].constantAttenuation");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].linearAttenuation");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].quadraticAttenuation");
|
||||
}
|
||||
stream(Shader.values()).forEach(shader -> shader.init(shaderManager));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,6 +40,7 @@ public class DefaultRenderer implements Renderer {
|
||||
// due to the fact, that the method updates projection and view matrices, that
|
||||
// are used to compute proper vertex coordinates of rendered objects (renderables).
|
||||
camera.render(screen, shaderManager);
|
||||
|
||||
renderable.render(screen, camera, shaderManager);
|
||||
|
||||
shaderManager.deactivateShader();
|
||||
|
||||
@@ -1,12 +1,50 @@
|
||||
package com.bartlomiejpluta.base.engine.core.gl.shader.constant;
|
||||
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public enum Shader {
|
||||
DEFAULT("default", "/shaders/default.vs", "/shaders/default.fs");
|
||||
DEFAULT("default", "/shaders/default.vs", "/shaders/default.fs", shaderManager -> {
|
||||
shaderManager.createUniform(UniformName.UNI_VIEW_MODEL_MATRIX)
|
||||
.createUniform(UniformName.UNI_MODEL_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)
|
||||
.createUniform(UniformName.UNI_AMBIENT)
|
||||
.createUniform(UniformName.UNI_ACTIVE_LIGHTS)
|
||||
.createCounter(CounterName.LIGHT);
|
||||
|
||||
for (int i = 0; i < RenderConstants.MAX_LIGHTS; ++i) {
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].position");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].intensity");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].constantAttenuation");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].linearAttenuation");
|
||||
shaderManager.createUniform(UniformName.UNI_LIGHTS + "[" + i + "].quadraticAttenuation");
|
||||
}
|
||||
}),
|
||||
|
||||
BATCH("batch", "/shaders/batch.vs", "/shaders/batch.fs", shaderManager -> {
|
||||
shaderManager
|
||||
.createUniform(UniformName.UNI_VIEW_MODEL_MATRIX)
|
||||
.createUniform(UniformName.UNI_PROJECTION_MATRIX)
|
||||
.createUniform(UniformName.UNI_TEXTURE_SAMPLER);
|
||||
});
|
||||
|
||||
public final String name;
|
||||
public final String vertex;
|
||||
public final String fragment;
|
||||
private final String vertex;
|
||||
private final String fragment;
|
||||
private final Consumer<ShaderManager> activator;
|
||||
|
||||
public void init(ShaderManager manager) {
|
||||
manager.createShader(name, vertex, fragment);
|
||||
manager.activateShader(name);
|
||||
this.activator.accept(manager);
|
||||
manager.deactivateShader();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,11 @@ public class DefaultShaderManager implements ShaderManager {
|
||||
current.detach();
|
||||
stack.pop();
|
||||
current = stack.peek();
|
||||
current.use();
|
||||
|
||||
if (current != null) {
|
||||
current.use();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -152,7 +156,7 @@ public class DefaultShaderManager implements ShaderManager {
|
||||
@Override
|
||||
public ShaderManager createCounter(String counterName) {
|
||||
if (counters.containsKey(counterName)) {
|
||||
throw new AppException(format("The [%s] counter already exists", counterName));
|
||||
return this;
|
||||
}
|
||||
|
||||
log.info("Creating {} uniform counter", counterName);
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.bartlomiejpluta.base.engine.world.camera;
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.context.Context;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName;
|
||||
import com.bartlomiejpluta.base.engine.world.object.Model;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.Getter;
|
||||
@@ -12,6 +11,7 @@ import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
|
||||
public class DefaultCamera extends Model implements Camera {
|
||||
@Getter
|
||||
private final Matrix4f projectionMatrix = new Matrix4f();
|
||||
private final Matrix4f viewMatrix = new Matrix4f();
|
||||
private final Matrix4f projectionViewMatrix = new Matrix4f();
|
||||
@@ -71,7 +71,5 @@ public class DefaultCamera extends Model implements Camera {
|
||||
this.maxX = position.x + screenWidth / scaleX;
|
||||
this.minY = position.y;
|
||||
this.maxY = position.y + screenHeight / scaleY;
|
||||
|
||||
shaderManager.setUniform(UniformName.UNI_PROJECTION_MATRIX, projectionMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.bartlomiejpluta.base.engine.world.map.layer.tile;
|
||||
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import com.bartlomiejpluta.base.internal.gc.Disposable;
|
||||
import com.bartlomiejpluta.base.internal.render.Renderable;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ChunkManager implements Renderable, Disposable {
|
||||
private static final int DEFAULT_CHUNK_SIZE = 10;
|
||||
|
||||
private final Map<Long, TileChunk> chunks = new HashMap<>();
|
||||
private final Map<Long, Map<Integer, Integer>> chunkTileIds = new HashMap<>();
|
||||
|
||||
private final Texture tileSet;
|
||||
private final int chunkSize;
|
||||
private final float tileWidth, tileHeight;
|
||||
private final int mapRows, mapColumns;
|
||||
|
||||
public ChunkManager(Texture tileSet, int rows, int columns) {
|
||||
this(tileSet, rows, columns, DEFAULT_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
public ChunkManager(Texture tileSet, int rows, int columns, int chunkSize) {
|
||||
this.tileSet = tileSet;
|
||||
this.mapRows = rows;
|
||||
this.mapColumns = columns;
|
||||
this.tileWidth = tileSet.getSpriteSize().x();
|
||||
this.tileHeight = tileSet.getSpriteSize().y();
|
||||
this.chunkSize = chunkSize;
|
||||
}
|
||||
|
||||
private long getChunkKey(int row, int column) {
|
||||
var chunkX = column / chunkSize;
|
||||
var chunkY = row / chunkSize;
|
||||
return ((long) chunkX << 32) | (chunkY & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
private int getLocalTileKey(int row, int column) {
|
||||
var localRow = row % chunkSize;
|
||||
var localColumn = column % chunkSize;
|
||||
return localRow * chunkSize + localColumn;
|
||||
}
|
||||
|
||||
private TileChunk getOrCreateChunk(int row, int column) {
|
||||
var chunkKey = getChunkKey(row, column);
|
||||
|
||||
var chunk = chunks.get(chunkKey);
|
||||
if (chunk == null) {
|
||||
int chunkX = (column / chunkSize) * (int) tileWidth * chunkSize;
|
||||
int chunkY = (row / chunkSize) * (int) tileHeight * chunkSize;
|
||||
|
||||
chunk = new TileChunk(tileSet, chunkSize);
|
||||
chunk.setPosition(chunkX, chunkY);
|
||||
chunks.put(chunkKey, chunk);
|
||||
chunkTileIds.put(chunkKey, new HashMap<>());
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
|
||||
public void setTile(int row, int column, int tileId) {
|
||||
if (row < 0 || row >= mapRows || column < 0 || column >= mapColumns) {
|
||||
throw new IllegalArgumentException("Tile coordinates out of bounds");
|
||||
}
|
||||
|
||||
clearTile(row, column);
|
||||
|
||||
var chunk = getOrCreateChunk(row, column);
|
||||
var chunkKey = getChunkKey(row, column);
|
||||
var localTileKey = getLocalTileKey(row, column);
|
||||
|
||||
var quadId = chunk.addTile(column % chunkSize * (int) this.tileHeight, row % chunkSize * (int) this.tileWidth, tileId);
|
||||
|
||||
chunkTileIds.get(chunkKey).put(localTileKey, quadId);
|
||||
}
|
||||
|
||||
public void clearTile(int row, int column) {
|
||||
if (row < 0 || row >= mapRows || column < 0 || column >= mapColumns) {
|
||||
return;
|
||||
}
|
||||
|
||||
var chunkKey = getChunkKey(row, column);
|
||||
var localTileKey = getLocalTileKey(row, column);
|
||||
|
||||
var chunk = chunks.get(chunkKey);
|
||||
if (chunk == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var tileMap = chunkTileIds.get(chunkKey);
|
||||
var quadId = tileMap.remove(localTileKey);
|
||||
|
||||
if (quadId != null) {
|
||||
chunk.removeTile(quadId);
|
||||
|
||||
if (chunk.isEmpty()) {
|
||||
chunks.remove(chunkKey);
|
||||
chunkTileIds.remove(chunkKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
for (TileChunk chunk : chunks.values()) {
|
||||
if (camera.containsBox(chunk)) {
|
||||
chunk.render(screen, camera, shaderManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
chunks.values().forEach(TileChunk::dispose);
|
||||
chunks.clear();
|
||||
chunkTileIds.clear();
|
||||
}
|
||||
}
|
||||
@@ -4,55 +4,36 @@ import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.map.layer.tile.TileLayer;
|
||||
import com.bartlomiejpluta.base.api.map.model.GameMap;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import com.bartlomiejpluta.base.engine.world.map.layer.base.BaseLayer;
|
||||
import com.bartlomiejpluta.base.engine.world.tileset.model.Tile;
|
||||
import com.bartlomiejpluta.base.engine.world.tileset.model.TileSet;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class DefaultTileLayer extends BaseLayer implements TileLayer {
|
||||
private final TileSet tileSet;
|
||||
private final Tile[][] layer;
|
||||
private final Texture tileSet;
|
||||
private final ChunkManager chunkManager;
|
||||
|
||||
public DefaultTileLayer(@NonNull GameMap map, @NonNull TileSet tileSet, int rows, int columns) {
|
||||
super(map);
|
||||
this.tileSet = tileSet;
|
||||
layer = new Tile[rows][columns];
|
||||
Arrays.stream(layer).forEach(tiles -> Arrays.fill(tiles, null));
|
||||
|
||||
this.tileSet = tileSet.getTileSet();
|
||||
|
||||
this.chunkManager = new ChunkManager(tileSet.getTileSet(), rows, columns);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTile(int row, int column, int tileId) {
|
||||
var tile = tileSet.tileById(tileId);
|
||||
tile.setLocation(row, column);
|
||||
layer[row][column] = tile;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTile(int row, int column, int tileSetRow, int tileSetColumn) {
|
||||
var tile = tileSet.tileAt(tileSetRow, tileSetColumn);
|
||||
tile.setLocation(row, column);
|
||||
layer[row][column] = tile;
|
||||
chunkManager.setTile(row, column, tileId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTile(int row, int column) {
|
||||
layer[row][column] = null;
|
||||
chunkManager.clearTile(row, column);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
for (var row : layer) {
|
||||
for (var tile : row) {
|
||||
if (tile != null) {
|
||||
tile.render(screen, camera, shaderManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.render(screen, camera, shaderManager);
|
||||
chunkManager.render(screen, camera, shaderManager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.bartlomiejpluta.base.engine.world.map.layer.tile;
|
||||
|
||||
import com.bartlomiejpluta.base.api.camera.Camera;
|
||||
import com.bartlomiejpluta.base.api.screen.Screen;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.BatchedQuads;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Quad;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.Shader;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName;
|
||||
import com.bartlomiejpluta.base.engine.world.object.Model;
|
||||
import com.bartlomiejpluta.base.internal.gc.Disposable;
|
||||
import com.bartlomiejpluta.base.internal.object.Placeable;
|
||||
import com.bartlomiejpluta.base.internal.render.BoundingBox;
|
||||
import com.bartlomiejpluta.base.internal.render.Renderable;
|
||||
import com.bartlomiejpluta.base.internal.render.ShaderManager;
|
||||
|
||||
public class TileChunk extends Model implements Placeable, Renderable, Disposable, BoundingBox {
|
||||
private final Texture tileSet;
|
||||
private final BatchedQuads mesh;
|
||||
private final int chunkSize;
|
||||
private final Quad quad;
|
||||
private final float originX;
|
||||
private final float originY;
|
||||
|
||||
public TileChunk(Texture tileSet, int chunkSize) {
|
||||
this(tileSet, chunkSize, 0, 0);
|
||||
}
|
||||
|
||||
public TileChunk(Texture tileSet, int chunkSize, float originX, float originY) {
|
||||
this.tileSet = tileSet;
|
||||
this.chunkSize = chunkSize;
|
||||
this.mesh = new BatchedQuads(chunkSize * chunkSize);
|
||||
this.quad = new Quad(tileSet.getSpriteSize().x(), tileSet.getSpriteSize().y(), originX, originY);
|
||||
this.originX = originX;
|
||||
this.originY = originY;
|
||||
}
|
||||
|
||||
public int addTile(int x, int y, int tileId) {
|
||||
return mesh.addQuad(quad, x, y, tileSet.getTextureCoordinates(tileId));
|
||||
}
|
||||
|
||||
public void removeTile(int quadId) {
|
||||
mesh.removeQuad(quadId);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mesh.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Screen screen, Camera camera, ShaderManager shaderManager) {
|
||||
tileSet.activate();
|
||||
|
||||
shaderManager.activateShader(Shader.BATCH.name);
|
||||
shaderManager.setUniform(UniformName.UNI_PROJECTION_MATRIX, camera.getProjectionMatrix());
|
||||
shaderManager.setUniform(UniformName.UNI_VIEW_MODEL_MATRIX, camera.computeViewModelMatrix(getModelMatrix()));
|
||||
shaderManager.setUniform(UniformName.UNI_TEXTURE_SAMPLER, 0);
|
||||
|
||||
mesh.render(screen, camera, shaderManager);
|
||||
shaderManager.deactivateShader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMinX() {
|
||||
float scaledOriginX = originX * scaleX;
|
||||
return getPosition().x() - scaledOriginX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxX() {
|
||||
float scaledOriginX = originX * scaleX;
|
||||
float scaledChunkWidth = chunkSize * tileSet.getSpriteSize().x() *scaleX;
|
||||
return getPosition().x() + scaledChunkWidth - scaledOriginX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMinY() {
|
||||
float scaledOriginY = originY * scaleY;
|
||||
return getPosition().y() - scaledOriginY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxY() {
|
||||
float scaledOriginY = originY * scaleY;
|
||||
float scaledChunkHeight = chunkSize * tileSet.getSpriteSize().y() * scaleY;
|
||||
return getPosition().y() + scaledChunkHeight - scaledOriginY;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
mesh.dispose();
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ public abstract class Sprite extends LocationableModel implements Renderable {
|
||||
return;
|
||||
}
|
||||
|
||||
shaderManager.setUniform(UniformName.UNI_PROJECTION_MATRIX, camera.getProjectionMatrix());
|
||||
shaderManager.setUniform(UniformName.UNI_VIEW_MODEL_MATRIX, camera.computeViewModelMatrix(getModelMatrix()));
|
||||
shaderManager.setUniform(UniformName.UNI_MODEL_MATRIX, getModelMatrix());
|
||||
material.render(screen, camera, shaderManager);
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
package com.bartlomiejpluta.base.engine.world.tileset.model;
|
||||
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.material.Material;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh;
|
||||
import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class TileSet {
|
||||
private final Texture tileSet;
|
||||
private final Mesh mesh;
|
||||
private final Material material;
|
||||
|
||||
public TileSet(@NonNull Texture tileSet, @NonNull Mesh mesh) {
|
||||
this.tileSet = tileSet;
|
||||
this.mesh = mesh;
|
||||
this.material = Material.textured(tileSet);
|
||||
}
|
||||
|
||||
public Tile tileById(int id) {
|
||||
return new Tile(mesh, tileSet, id);
|
||||
|
||||
15
engine/src/main/resources/shaders/batch.fs
Normal file
15
engine/src/main/resources/shaders/batch.fs
Normal file
@@ -0,0 +1,15 @@
|
||||
#version 330
|
||||
|
||||
in vec2 fragmentTexCoord;
|
||||
|
||||
uniform sampler2D sampler;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
fragColor = texture(sampler, fragmentTexCoord);
|
||||
|
||||
if (fragColor.a < 0.1) {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
15
engine/src/main/resources/shaders/batch.vs
Normal file
15
engine/src/main/resources/shaders/batch.vs
Normal file
@@ -0,0 +1,15 @@
|
||||
#version 330
|
||||
|
||||
uniform mat4 viewModelMatrix;
|
||||
uniform mat4 projectionMatrix;
|
||||
|
||||
layout(location=0) in vec2 position;
|
||||
layout(location=1) in vec2 texCoord;
|
||||
|
||||
out vec2 fragmentTexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = projectionMatrix * viewModelMatrix * vec4(position, 0.0, 1.0);
|
||||
fragmentTexCoord = texCoord;
|
||||
}
|
||||
Reference in New Issue
Block a user