diff --git a/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/maps/Map.groovy b/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/maps/Map.groovy index cc3aadf4..e07eb964 100644 --- a/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/maps/Map.groovy +++ b/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/maps/Map.groovy @@ -364,7 +364,7 @@ class Map extends Node { return scene .requestCreateOrGet(new MeshRequest(MeshType.TRIANGLES, new VertexBufferLayout(Attribute.POSITION, Attribute.COLOUR, Attribute.TEXTURE_UVS), - allVertices, Colour.WHITE, allTextureUVs, allIndices, false)) + allVertices, Colour.WHITE, allTextureUVs, allIndices)) .thenAcceptAsync { newMesh -> fullMesh = newMesh } @@ -376,6 +376,14 @@ class Map extends Node { }, tileSetSpriteSheetFuture.thenApplyAsync { spriteSheet -> material.texture = spriteSheet.texture + material.with { + texture = spriteSheet.texture + framesHorizontal = spriteSheet.framesHorizontal + framesVertical = spriteSheet.framesVertical + frameStepX = spriteSheet.frameStepX + frameStepY = spriteSheet.frameStepY + frame = 0 + } return spriteSheet } ) @@ -469,7 +477,7 @@ class Map extends Node { var overlay = new PalettedSprite(tileFile) overlay.name = "${tile.name} - Variant ${imageVariant}" - overlay.initialFrame = imageVariant + overlay.frame = imageVariant overlay.position = new Vector2f(tilePos).asWorldCoords(1) addChild(overlay) diff --git a/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/nodes/PalettedSprite.groovy b/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/nodes/PalettedSprite.groovy index c490d3e4..db18cdf9 100644 --- a/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/nodes/PalettedSprite.groovy +++ b/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/nodes/PalettedSprite.groovy @@ -18,7 +18,6 @@ package nz.net.ultraq.redhorizon.classic.nodes import nz.net.ultraq.redhorizon.classic.Faction import nz.net.ultraq.redhorizon.classic.shaders.Shaders -import nz.net.ultraq.redhorizon.engine.graphics.GraphicsRenderer import nz.net.ultraq.redhorizon.engine.graphics.GraphicsRequests.ShaderRequest import nz.net.ultraq.redhorizon.engine.graphics.GraphicsRequests.SpriteMeshRequest import nz.net.ultraq.redhorizon.engine.scenegraph.Scene @@ -92,8 +91,19 @@ class PalettedSprite extends Sprite implements FactionColours { CompletableFuture onSceneAddedAsync(Scene scene) { return CompletableFuture.allOf( - scene - .requestCreateOrGet(new SpriteMeshRequest(bounds, region)) + spriteSheetGenerator.generate(scene) + .thenComposeAsync { newSpriteSheet -> + spriteSheet = newSpriteSheet + material.with { + texture = spriteSheet.texture + framesHorizontal = spriteSheet.framesHorizontal + framesVertical = spriteSheet.framesVertical + frameStepX = spriteSheet.frameStepX + frameStepY = spriteSheet.frameStepY + frame = this.frame + } + return scene.requestCreateOrGet(new SpriteMeshRequest(bounds, spriteSheet.textureRegion.scale(repeatX, repeatY))) + } .thenAcceptAsync { newMesh -> mesh = newMesh }, @@ -102,37 +112,12 @@ class PalettedSprite extends Sprite implements FactionColours { .thenAcceptAsync { requestedShader -> shader = requestedShader }, - spriteSheetGenerator.generate(scene) - .thenApplyAsync { newSpriteSheet -> - spriteSheet = newSpriteSheet - material.texture = spriteSheet.texture - - // TODO: Some uses are a repeating tile, others aren't. There should be a unified way of doing this 🤔 - if (repeatX != 1f || repeatY != 1f) { - region.setMax(repeatX, repeatY) - } - else { - region.set(spriteSheet[initialFrame]) - } - }, CompletableFuture.runAsync { -> material.adjustmentMap = buildAdjustmentMap(faction) } ) } - @Override - void render(GraphicsRenderer renderer) { - - if (material.adjustmentMap) { - if (factionChanged) { - material.adjustmentMap = buildAdjustmentMap(faction) - factionChanged = false - } - super.render(renderer) - } - } - /** * Update the faction being applied to this sprite. */ @@ -142,4 +127,14 @@ class PalettedSprite extends Sprite implements FactionColours { FactionColours.super.setFaction(faction) factionChanged = true } + + @Override + void update() { + + if (material.adjustmentMap && factionChanged) { + material.adjustmentMap = buildAdjustmentMap(faction) + factionChanged = false + } + super.update() + } } diff --git a/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/shaders/PalettedSprite.vert.glsl b/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/shaders/PalettedSprite.vert.glsl index d73ad7fa..5589fb17 100644 --- a/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/shaders/PalettedSprite.vert.glsl +++ b/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/shaders/PalettedSprite.vert.glsl @@ -12,6 +12,11 @@ layout (std140) uniform Camera { mat4 view; }; uniform mat4 model; +uniform int framesHorizontal; +uniform int framesVertical; +uniform float frameStepX; +uniform float frameStepY; +uniform int frame; /** * Vertex shader main function, mostly passes geometry information along to the @@ -21,5 +26,9 @@ void main() { gl_Position = projection * view * model * position; v_vertexColour = colour; - v_textureUVs = textureUVs; + + // Adjust textureUVs to the location of the selected frame in the spritesheet + float textureU = (frame % framesHorizontal) * frameStepX; + float textureV = floor(frame / framesHorizontal) * frameStepY; + v_textureUVs = textureUVs + vec2(textureU, textureV); } diff --git a/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/shaders/PalettedSpriteShader.groovy b/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/shaders/PalettedSpriteShader.groovy index 8b2834fc..b08b047f 100644 --- a/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/shaders/PalettedSpriteShader.groovy +++ b/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/shaders/PalettedSpriteShader.groovy @@ -37,6 +37,13 @@ class PalettedSpriteShader extends ShaderConfig { }, { shader, material, window -> shader.setUniform('adjustmentMap', material.adjustmentMap) + }, + { shader, material, window -> + shader.setUniform('frame', material.frame) + shader.setUniform('framesHorizontal', material.framesHorizontal) + shader.setUniform('framesVertical', material.framesVertical) + shader.setUniform('frameStepX', material.frameStepX) + shader.setUniform('frameStepY', material.frameStepY) } ] } diff --git a/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/units/Unit.groovy b/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/units/Unit.groovy index 56998cca..558daa49 100644 --- a/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/units/Unit.groovy +++ b/redhorizon-classic/source/nz/net/ultraq/redhorizon/classic/units/Unit.groovy @@ -210,7 +210,7 @@ class Unit extends Node implements FactionColours, Rotatable, Temporal { var closestHeading = Math.round(heading / degreesPerHeading) var rotationFrame = closestHeading ? (headings - closestHeading) * frames as int : 0 var animationFrame = frames ? Math.floor((currentTimeMs - animationStartTime) / 1000 * FRAMERATE) % frames as int : 0 - region.set(spriteSheet.getFrame(unitData.shpFile.getStateFramesOffset(currentState) + rotationFrame + animationFrame)) + frame = unitData.shpFile.getStateFramesOffset(currentState) + rotationFrame + animationFrame } super.update() @@ -233,7 +233,7 @@ class Unit extends Node implements FactionColours, Rotatable, Temporal { var turretHeadings = unitData.shpFile.parts.turret.headings var closestTurretHeading = Math.round(heading / degreesPerHeading) var turretRotationFrame = closestTurretHeading ? turretHeadings - closestTurretHeading as int : 0 - region.set(spriteSheet.getFrame(unitData.shpFile.parts.body.headings + turretRotationFrame)) + frame = unitData.shpFile.parts.body.headings + turretRotationFrame } super.update() diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsRenderer.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsRenderer.groovy index a33d3c2b..7f681ca3 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsRenderer.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsRenderer.groovy @@ -58,14 +58,14 @@ interface GraphicsRenderer extends AutoCloseable, EventTarget { */ default Mesh createMesh(MeshType type, VertexBufferLayout layout, Vector2f[] vertices, Colour colour) { - return createMesh(type, layout, vertices, colour, null, null, false) + return createMesh(type, layout, vertices, colour, null, null) } /** * Create a mesh with all of the mesh parts. */ Mesh createMesh(MeshType type, VertexBufferLayout layout, Vector2f[] vertices, Colour colour, Vector2f[] textureUVs, - int[] indices, boolean dynamic) + int[] indices) /** * Create a new shader program from the given configuration, or return the @@ -87,10 +87,7 @@ interface GraphicsRenderer extends AutoCloseable, EventTarget { * Create a mesh to represent a surface onto which a texture will go. This is * a convenience method for {@link #createMesh}. */ - default Mesh createSpriteMesh(Rectanglef surface) { - - return createSpriteMesh(surface, new Rectanglef(0, 0, 1, 1)) - } + Mesh createSpriteMesh(Rectanglef surface) /** * Create a mesh to represent a surface onto which a texture will go. This is diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsRequests.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsRequests.groovy index 322a927b..50bf3e9f 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsRequests.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsRequests.groovy @@ -40,21 +40,21 @@ interface GraphicsRequests { @ImmutableOptions(knownImmutables = ['layout', 'colour']) static record MeshRequest(MeshType type, VertexBufferLayout layout, Vector2f[] vertices, Colour colour, - Vector2f[] textureUVs, int[] indices, boolean dynamic) implements Request { + Vector2f[] textureUVs, int[] indices) implements Request { - MeshRequest(MeshType type, VertexBufferLayout layout, Vector2f[] vertices, Colour colour, int[] indices, boolean dynamic) { - this(type, layout, vertices, colour, null, indices, dynamic) + MeshRequest(MeshType type, VertexBufferLayout layout, Vector2f[] vertices, Colour colour, int[] indices) { + this(type, layout, vertices, colour, null, indices) } MeshRequest(MeshType type, VertexBufferLayout layout, Vector2f[] vertices, Colour colour) { - this(type, layout, vertices, colour, null, null, false) + this(type, layout, vertices, colour, null, null) } } @ImmutableOptions(knownImmutables = ['surface', 'textureUVs']) static record SpriteMeshRequest(Rectanglef surface, Rectanglef textureUVs) implements Request { SpriteMeshRequest(Rectanglef surface) { - this(surface, null) + this(surface, new Rectanglef(0, 0, 1, 1)) } } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsSystem.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsSystem.groovy index 8cd29159..9f3dd493 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsSystem.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsSystem.groovy @@ -136,15 +136,8 @@ class GraphicsSystem extends EngineSystem implements GraphicsRequests { var resource = switch (request) { case ShaderRequest -> renderer.createShader(request.shaderConfig()) case MeshRequest -> renderer.createMesh(request.type(), request.layout(), request.vertices(), request.colour(), - request.textureUVs(), request.indices(), request.dynamic()) - case SpriteMeshRequest -> { - if (request.textureUVs() != null) { - yield renderer.createSpriteMesh(request.surface(), request.textureUVs()) - } - else { - yield renderer.createSpriteMesh(request.surface()) - } - } + request.textureUVs(), request.indices()) + case SpriteMeshRequest -> renderer.createSpriteMesh(request.surface(), request.textureUVs()) case TextureRequest -> renderer.createTexture(request.width(), request.height(), request.format(), request.data()) case SpriteSheetRequest -> renderer.createSpriteSheet(request.width(), request.height(), request.format(), request.data()) case UniformBufferRequest -> renderer.createUniformBuffer(request.name(), request.data()) diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Material.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Material.groovy index 1a1cff34..08ccd2db 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Material.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Material.groovy @@ -26,6 +26,52 @@ package nz.net.ultraq.redhorizon.engine.graphics */ class Material { + private static final String KEY_FRAME = 'frame' + private static final String KEY_FRAMES_HORIZONTAL = 'framesHorizontal' + private static final String KEY_FRAMES_VERTICAL = 'framesVertical' + private static final String KEY_FRAME_STEP_X = 'frameStepX' + private static final String KEY_FRAME_STEP_Y = 'frameStepY' + final Map attributes = [:] Texture texture + + int getFrame() { + return attributes[KEY_FRAME] + } + + float getFrameStepX() { + return attributes[KEY_FRAME_STEP_X] + } + + float getFrameStepY() { + return attributes[KEY_FRAME_STEP_Y] + } + + int getFramesHorizontal() { + return attributes[KEY_FRAMES_HORIZONTAL] + } + + int getFramesVertical() { + return attributes[KEY_FRAMES_VERTICAL] + } + + void setFrame(int frame) { + attributes[KEY_FRAME] = frame + } + + void setFrameStepX(float frameStepX) { + attributes[KEY_FRAME_STEP_X] = frameStepX + } + + void setFrameStepY(float frameStepY) { + attributes[KEY_FRAME_STEP_Y] = frameStepY + } + + void setFramesHorizontal(int framesHorizontal) { + attributes[KEY_FRAMES_HORIZONTAL] = framesHorizontal + } + + void setFramesVertical(int framesVertical) { + attributes[KEY_FRAMES_VERTICAL] = framesVertical + } } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Mesh.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Mesh.groovy index b5d21f14..1018e240 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Mesh.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Mesh.groovy @@ -40,10 +40,4 @@ abstract class Mesh implements GraphicsResource { * Use this mesh in upcoming render operations. */ abstract void bind() - - /** - * Update the textureUVs part of the mesh data. This is only allowed on - * meshes that have been configured to use dynamic buffer data. - */ - abstract void updateTextureUvs(Vector2f[] textureUVs) } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Shader.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Shader.groovy index 3b1b6c32..d4404900 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Shader.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Shader.groovy @@ -46,37 +46,13 @@ abstract class Shader implements GraphicsResource { } /** - * Apply a data uniform to the shader. The type of data is determined by the - * size of the data array. - * - * @param name - * @param data - */ - abstract void setUniform(String name, float[] data) - - /** - * Apply a data uniform to the shader. The type of data is determined by the - * size of the data array. - * - * @param name - * @param data + * Apply a data uniform to the shader. If {@code data} is an array, it can be + * used to determine the shader type (eg: 2 floats = vec2). */ - abstract void setUniform(String name, int[] data) - - /** - * Apply a matrix uniform to the shader. - * - * @param name - * @param matrix - */ - abstract void setUniform(String name, Matrix4f matrix) + abstract void setUniform(String name, Object data) /** * Apply a texture uniform using the given texture ID. - * - * @param name - * @param textureUnit - * @param textureId */ abstract void setUniformTexture(String name, int textureUnit, Texture texture) diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/SpriteSheet.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/SpriteSheet.groovy index db7a693e..1f8711df 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/SpriteSheet.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/SpriteSheet.groovy @@ -53,6 +53,9 @@ class SpriteSheet implements GraphicsResource { /** * Return coordinates on the sprite sheet that would locate the sprite with * the corresponding index from the raw data. + *

+ * Note that with texture adjustments calculated in the shader, there + * shouldn't be a need for this any more. */ Rectanglef getFrame(int index) { @@ -65,4 +68,12 @@ class SpriteSheet implements GraphicsResource { textureV + frameStepY as float ) } + + /** + * Return a texture region for use with this spritesheet. + */ + Rectanglef getTextureRegion() { + + return getFrame(0) + } } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/OpenGLMesh.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/OpenGLMesh.groovy index 78968484..525426a8 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/OpenGLMesh.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/OpenGLMesh.groovy @@ -39,15 +39,13 @@ class OpenGLMesh extends Mesh { final int vertexArrayId final int vertexBufferId final int elementBufferId - final boolean dynamic private Vector2f[] lastTextureUVs /** * Constructor, creates a new OpenGL mesh. */ - OpenGLMesh(int type, VertexBufferLayout layout, Vector2f[] vertices, Colour colour, Vector2f[] textureUVs, - int[] indices, boolean dynamic) { + OpenGLMesh(int type, VertexBufferLayout layout, Vector2f[] vertices, Colour colour, Vector2f[] textureUVs, int[] indices) { super(type, layout, vertices, colour, textureUVs, indices) @@ -73,7 +71,7 @@ class OpenGLMesh extends Mesh { } } vertexBuffer.flip() - glBufferData(GL_ARRAY_BUFFER, vertexBuffer, dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW) + glBufferData(GL_ARRAY_BUFFER, vertexBuffer, GL_STATIC_DRAW) layout.attributes.each { attribute -> glEnableVertexAttribArray(attribute.location) @@ -96,7 +94,6 @@ class OpenGLMesh extends Mesh { elementBufferId = 0 } - this.dynamic = dynamic lastTextureUVs = textureUVs } @@ -115,28 +112,4 @@ class OpenGLMesh extends Mesh { glDeleteBuffers(vertexBufferId) glDeleteVertexArrays(vertexArrayId) } - - @Override - void updateTextureUvs(Vector2f[] textureUVs) { - - if (!this.textureUVs) { - throw new IllegalStateException('Cannot update textureUVs on a mesh that has no textureUVs to begin with') - } - - if (!dynamic) { - throw new IllegalStateException('Cannot update textureUVs on a mesh that was created without a dynamic buffer') - } - - if (textureUVs != lastTextureUVs) { - stackPush().withCloseable { stack -> - glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId) - textureUVs.eachWithIndex { textureUv, index -> - glBufferSubData(GL_ARRAY_BUFFER, - (layout.sizeInBytes() * index) + layout.offsetOfInBytes(Attribute.TEXTURE_UVS), - textureUv.get(stack.mallocFloat(Vector2f.FLOATS))) - } - } - lastTextureUVs = textureUVs - } - } } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/OpenGLRenderer.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/OpenGLRenderer.groovy index 3a051567..20d1b0d3 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/OpenGLRenderer.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/OpenGLRenderer.groovy @@ -192,10 +192,10 @@ class OpenGLRenderer implements GraphicsRenderer { @Override Mesh createMesh(MeshType type, VertexBufferLayout layout, Vector2f[] vertices, Colour colour, Vector2f[] textureUVs, - int[] indices, boolean dynamic) { + int[] indices) { var mesh = new OpenGLMesh(type == MeshType.LINES ? GL_LINES : type == MeshType.LINE_LOOP ? GL_LINE_LOOP : GL_TRIANGLES, - layout, vertices, colour, textureUVs, indices, dynamic) + layout, vertices, colour, textureUVs, indices) trigger(new MeshCreatedEvent(mesh)) return mesh } @@ -221,7 +221,7 @@ class OpenGLRenderer implements GraphicsRenderer { } @Override - Mesh createSpriteMesh(Rectanglef surface, Rectanglef textureUVs) { + Mesh createSpriteMesh(Rectanglef surface, Rectanglef textureUVs = new Rectanglef(0, 0, 1, 1)) { return createMesh( MeshType.TRIANGLES, @@ -229,8 +229,7 @@ class OpenGLRenderer implements GraphicsRenderer { surface as Vector2f[], Colour.WHITE, textureUVs as Vector2f[], - [0, 1, 3, 1, 2, 3] as int[], - true + 0, 1, 3, 1, 2, 3 ) } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/OpenGLShader.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/OpenGLShader.groovy index a93d74f6..c5445429 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/OpenGLShader.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/OpenGLShader.groovy @@ -111,9 +111,6 @@ class OpenGLShader extends Shader { /** * Cached function for looking up a uniform location in a shader program. - * - * @param name - * @return */ @Memoized private int getUniformLocation(String name) { @@ -122,29 +119,25 @@ class OpenGLShader extends Shader { } @Override - void setUniform(String name, float[] data) { + void setUniform(String name, Object data) { - stackPush().withCloseable { stack -> - switch (data.length) { - case 2 -> glUniform2fv(getUniformLocation(name), stack.floats(data)) - default -> glUniform1fv(getUniformLocation(name), stack.floats(data)) - } - } - } - - @Override - void setUniform(String name, int[] data) { + var uniformLocation = getUniformLocation(name) stackPush().withCloseable { stack -> - glUniform1iv(getUniformLocation(name), stack.ints(data)) - } - } - - @Override - void setUniform(String name, Matrix4f matrix) { - - stackPush().withCloseable { stack -> - glUniformMatrix4fv(getUniformLocation(name), false, matrix.get(stack.mallocFloat(16))) + switch (data) { + case Float -> glUniform1f(uniformLocation, data) + case float[] -> { + var floatData = (float[])data + switch (floatData.length) { + case 2 -> glUniform2fv(uniformLocation, stack.floats(floatData)) + default -> glUniform1fv(uniformLocation, stack.floats(floatData)) + } + } + case Integer -> glUniform1i(uniformLocation, data) + case int[] -> glUniform1iv(uniformLocation, (int[])data) + case Matrix4f -> glUniformMatrix4fv(uniformLocation, false, data.get(stack.mallocFloat(16))) + default -> throw new UnsupportedOperationException("Data type of ${data.class.simpleName} not supported") + } } } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/Sprite.vert.glsl b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/Sprite.vert.glsl index d73ad7fa..5589fb17 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/Sprite.vert.glsl +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/Sprite.vert.glsl @@ -12,6 +12,11 @@ layout (std140) uniform Camera { mat4 view; }; uniform mat4 model; +uniform int framesHorizontal; +uniform int framesVertical; +uniform float frameStepX; +uniform float frameStepY; +uniform int frame; /** * Vertex shader main function, mostly passes geometry information along to the @@ -21,5 +26,9 @@ void main() { gl_Position = projection * view * model * position; v_vertexColour = colour; - v_textureUVs = textureUVs; + + // Adjust textureUVs to the location of the selected frame in the spritesheet + float textureU = (frame % framesHorizontal) * frameStepX; + float textureV = floor(frame / framesHorizontal) * frameStepY; + v_textureUVs = textureUVs + vec2(textureU, textureV); } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/SpriteShader.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/SpriteShader.groovy index d8e80688..9c474e69 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/SpriteShader.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/opengl/SpriteShader.groovy @@ -34,6 +34,13 @@ class SpriteShader extends ShaderConfig { final Uniform[] uniforms = [ { shader, material, window -> shader.setUniformTexture('mainTexture', 0, material.texture) + }, + { shader, material, window -> + shader.setUniform('frame', material.frame) + shader.setUniform('framesHorizontal', material.framesHorizontal) + shader.setUniform('framesVertical', material.framesVertical) + shader.setUniform('frameStepX', material.frameStepX) + shader.setUniform('frameStepY', material.frameStepY) } ] } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/RenderPipeline.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/RenderPipeline.groovy index f6159f03..dea3c393 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/RenderPipeline.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/RenderPipeline.groovy @@ -31,7 +31,6 @@ import nz.net.ultraq.redhorizon.engine.input.InputEventStream import nz.net.ultraq.redhorizon.engine.scenegraph.GraphicsElement import nz.net.ultraq.redhorizon.engine.scenegraph.Node import nz.net.ultraq.redhorizon.engine.scenegraph.Scene -import nz.net.ultraq.redhorizon.engine.scenegraph.nodes.Camera import org.joml.FrustumIntersection import org.joml.Matrix4f @@ -195,7 +194,7 @@ class RenderPipeline implements AutoCloseable { void render(GraphicsRenderer renderer, Void unused) { if (scene) { - var camera = (Camera)scene.findNode { node -> node instanceof Camera } + var camera = scene.camera if (camera) { camera.update() diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/Scene.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/Scene.groovy index 281964ad..1a3ee5a3 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/Scene.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/Scene.groovy @@ -23,6 +23,7 @@ import nz.net.ultraq.redhorizon.engine.graphics.MainMenu import nz.net.ultraq.redhorizon.engine.graphics.Window import nz.net.ultraq.redhorizon.engine.graphics.imgui.ImGuiLayer.GameWindow import nz.net.ultraq.redhorizon.engine.input.InputEventStream +import nz.net.ultraq.redhorizon.engine.scenegraph.nodes.Camera import nz.net.ultraq.redhorizon.engine.scenegraph.partioning.QuadTree import nz.net.ultraq.redhorizon.engine.time.TimeSystem import nz.net.ultraq.redhorizon.events.EventTarget @@ -54,6 +55,7 @@ class Scene implements EventTarget { InputEventStream inputEventStream Window window + Camera camera Listener listener MainMenu gameMenu GameWindow gameWindow @@ -67,7 +69,11 @@ class Scene implements EventTarget { Scene() { on(NodeAddedEvent) { event -> - if (event.node instanceof GraphicsElement) { + var node = event.node + if (node instanceof Camera) { + camera = node + } + else if (event.node instanceof GraphicsElement) { quadTree.add(event.node) } } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/Primitive.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/Primitive.groovy index c9358a2f..f61211f0 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/Primitive.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/Primitive.groovy @@ -80,7 +80,7 @@ class Primitive extends Node implements GraphicsElement { scene .requestCreateOrGet(type == MeshType.TRIANGLES ? new MeshRequest(type, new VertexBufferLayout(Attribute.POSITION, Attribute.COLOUR), this.points, colour, - [0, 1, 3, 1, 2, 3] as int[], false) : + [0, 1, 3, 1, 2, 3] as int[]) : new MeshRequest(type, new VertexBufferLayout(Attribute.POSITION, Attribute.COLOUR), this.points, colour)) .thenAcceptAsync { newMesh -> mesh = newMesh diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/Sprite.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/Sprite.groovy index ae114c46..99351c37 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/Sprite.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/Sprite.groovy @@ -31,9 +31,6 @@ import nz.net.ultraq.redhorizon.engine.scenegraph.Scene import nz.net.ultraq.redhorizon.filetypes.ImageFile import nz.net.ultraq.redhorizon.filetypes.ImagesFile -import org.joml.Vector2f -import org.joml.primitives.Rectanglef - import java.util.concurrent.CompletableFuture /** @@ -47,12 +44,11 @@ class Sprite extends Node implements GraphicsElement { final int numImages final SpriteSheetGenerator spriteSheetGenerator - final Rectanglef region = new Rectanglef(0, 0, 1, 1) /** - * The frame to load from the sprite sheet in {@link #onSceneAddedAsync}. + * The frame to select from the underlying spritesheet. */ - int initialFrame = 0 + int frame = 0 SpriteSheet spriteSheet @@ -97,8 +93,19 @@ class Sprite extends Node implements GraphicsElement { CompletableFuture onSceneAddedAsync(Scene scene) { return CompletableFuture.allOf( - scene - .requestCreateOrGet(new SpriteMeshRequest(bounds, region)) + spriteSheetGenerator.generate(scene) + .thenComposeAsync { newSpriteSheet -> + spriteSheet = newSpriteSheet + material.with { + texture = spriteSheet.texture + framesHorizontal = spriteSheet.framesHorizontal + framesVertical = spriteSheet.framesVertical + frameStepX = spriteSheet.frameStepX + frameStepY = spriteSheet.frameStepY + frame = this.frame + } + return scene.requestCreateOrGet(new SpriteMeshRequest(bounds, spriteSheet.textureRegion)) + } .thenAcceptAsync { newMesh -> mesh = newMesh }, @@ -106,12 +113,6 @@ class Sprite extends Node implements GraphicsElement { .requestCreateOrGet(new ShaderRequest(Shaders.spriteShader)) .thenAcceptAsync { requestedShader -> shader = requestedShader - }, - spriteSheetGenerator.generate(scene) - .thenAcceptAsync { newSpriteSheet -> - spriteSheet = newSpriteSheet - material.texture = spriteSheet.texture - region.set(spriteSheet[initialFrame]) } ) } @@ -133,9 +134,7 @@ class Sprite extends Node implements GraphicsElement { @Override void update() { - if (mesh) { - mesh.updateTextureUvs(region as Vector2f[]) - } + material.frame = frame } /** diff --git a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/scripts/SpriteShowcaseScript.groovy b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/scripts/SpriteShowcaseScript.groovy index b602bad1..7ef9fed7 100644 --- a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/scripts/SpriteShowcaseScript.groovy +++ b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/scripts/SpriteShowcaseScript.groovy @@ -58,11 +58,11 @@ class SpriteShowcaseScript extends Script { removeControlFunctions << scene.inputEventStream.addControl(new KeyControl(GLFW_KEY_A, 'Previous frame', { -> Math.wrap(currentFrame--, 0, numImages) - region.set(spriteSheet[currentFrame]) + frame = currentFrame }, true)) removeControlFunctions << scene.inputEventStream.addControl(new KeyControl(GLFW_KEY_D, 'Next frame', { -> Math.wrap(currentFrame++, 0, numImages) - region.set(spriteSheet[currentFrame]) + frame = currentFrame }, true)) var Faction[] factions = Faction.values()