From 613fbc7955c21d46eec8be208ea2108f387f404b Mon Sep 17 00:00:00 2001 From: Stanislav Mishchenko Date: Sun, 22 Oct 2023 18:58:59 +0300 Subject: [PATCH] add more rendering logic --- .../kotlin/solve/constants/ResourcesPaths.kt | 5 + .../solve/rendering/canvas/OpenGLCanvas.kt | 1 - .../kotlin/solve/rendering/engine/Window.kt | 38 +++++ .../solve/rendering/engine/camera/Camera.kt | 4 + .../{structure => components}/Component.kt | 6 +- .../rendering/engine/components/Sprite.kt | 36 +++++ .../engine/components/SpriteRenderer.kt | 22 +++ .../engine/rendering/batch/PrimitiveType.kt | 19 +++ .../engine/rendering/batch/RenderBatch.kt | 136 ++++++++++++++++++ .../engine/rendering/renderers/Renderer.kt | 63 ++++++++ .../rendering/renderers/SpriteRenderer.kt | 78 ++++++++++ .../engine/rendering/texture/Texture.kt | 125 ++++++++++++++++ .../rendering/texture/TextureChannelsType.kt | 20 +++ .../engine/{structure => scene}/GameObject.kt | 19 ++- .../engine/{structure => scene}/Scene.kt | 2 +- .../engine/{structure => scene}/SceneData.kt | 2 +- .../engine/{structure => scene}/Transform.kt | 4 +- .../engine/shader/ShaderAttributeType.kt | 18 +++ .../rendering/engine/shader/ShaderProgram.kt | 2 +- .../rendering/engine/structures/Color.kt | 42 ++++++ src/main/kotlin/solve/utils/ResourcesUtils.kt | 2 + .../engine/shaders/default/default.frag | 44 ++++++ .../engine/shaders/default/default.vert | 21 +++ 23 files changed, 697 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/solve/rendering/engine/Window.kt rename src/main/kotlin/solve/rendering/engine/{structure => components}/Component.kt (51%) create mode 100644 src/main/kotlin/solve/rendering/engine/components/Sprite.kt create mode 100644 src/main/kotlin/solve/rendering/engine/components/SpriteRenderer.kt create mode 100644 src/main/kotlin/solve/rendering/engine/rendering/batch/PrimitiveType.kt create mode 100644 src/main/kotlin/solve/rendering/engine/rendering/batch/RenderBatch.kt create mode 100644 src/main/kotlin/solve/rendering/engine/rendering/renderers/Renderer.kt create mode 100644 src/main/kotlin/solve/rendering/engine/rendering/renderers/SpriteRenderer.kt create mode 100644 src/main/kotlin/solve/rendering/engine/rendering/texture/Texture.kt create mode 100644 src/main/kotlin/solve/rendering/engine/rendering/texture/TextureChannelsType.kt rename src/main/kotlin/solve/rendering/engine/{structure => scene}/GameObject.kt (62%) rename src/main/kotlin/solve/rendering/engine/{structure => scene}/Scene.kt (96%) rename src/main/kotlin/solve/rendering/engine/{structure => scene}/SceneData.kt (76%) rename src/main/kotlin/solve/rendering/engine/{structure => scene}/Transform.kt (66%) create mode 100644 src/main/kotlin/solve/rendering/engine/shader/ShaderAttributeType.kt create mode 100644 src/main/kotlin/solve/rendering/engine/structures/Color.kt create mode 100644 src/main/resources/engine/shaders/default/default.frag create mode 100644 src/main/resources/engine/shaders/default/default.vert diff --git a/src/main/kotlin/solve/constants/ResourcesPaths.kt b/src/main/kotlin/solve/constants/ResourcesPaths.kt index c964a032a..ec85d8be4 100644 --- a/src/main/kotlin/solve/constants/ResourcesPaths.kt +++ b/src/main/kotlin/solve/constants/ResourcesPaths.kt @@ -1,5 +1,6 @@ package solve.constants +// Application icons. const val IconsCatalogueApplyPath = "icons/catalogue/apply.png" const val IconsCatalogueImagePath = "icons/catalogue/image.png" const val IconsImporterCheckCirclePath = "icons/importer/check_circle.png" @@ -27,3 +28,7 @@ const val IconsLayers = "/icons/sidepanel/Layers.png" const val IconsLayersFilled = "/icons/sidepanel/LayersFilled.png" const val IconsGrid = "/icons/sidepanel/Grid.png" const val IconsGridSelected = "/icons/sidepanel/GridSelected.png" + +// Engine shaders. +const val ShadersDefaultVertexPath = "engine/shaders/default/default.vert" +const val ShadersDefaultFragmentPath = "engine/shaders/default/default.frag" \ No newline at end of file diff --git a/src/main/kotlin/solve/rendering/canvas/OpenGLCanvas.kt b/src/main/kotlin/solve/rendering/canvas/OpenGLCanvas.kt index 55be21267..d2ba1e641 100644 --- a/src/main/kotlin/solve/rendering/canvas/OpenGLCanvas.kt +++ b/src/main/kotlin/solve/rendering/canvas/OpenGLCanvas.kt @@ -6,7 +6,6 @@ import com.huskerdev.openglfx.events.GLRenderEvent import com.huskerdev.openglfx.events.GLReshapeEvent import com.huskerdev.openglfx.OpenGLCanvas as OpenGLFXCanvas import com.huskerdev.openglfx.lwjgl.LWJGLExecutor -import org.lwjgl.opengl.GL import org.lwjgl.opengl.GL.createCapabilities import org.lwjgl.opengl.GL11 import org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT diff --git a/src/main/kotlin/solve/rendering/engine/Window.kt b/src/main/kotlin/solve/rendering/engine/Window.kt new file mode 100644 index 000000000..0e6d35143 --- /dev/null +++ b/src/main/kotlin/solve/rendering/engine/Window.kt @@ -0,0 +1,38 @@ +package solve.rendering.engine + +import org.joml.Matrix4f +import org.joml.Vector2f +import solve.rendering.engine.camera.Camera +import solve.rendering.engine.scene.Scene + +class Window( + width: Int, + height: Int, + camera: Camera = Camera(), + scene: Scene? = null +) { + var width = width + private set + var height = height + private set + + var scene: Scene? = scene + private set + var camera: Camera = camera + private set + + fun changeScene(newScene: Scene) { + scene = newScene + } + + fun changeCamera(newCamera: Camera) { + camera = newCamera + } + + fun resize(newWidth: Int, newHeight: Int) { + width = newWidth + height = newHeight + } + + fun calculateProjectionMatrix() = camera.calculateProjectionMatrix(Vector2f(width.toFloat(), height.toFloat())) +} \ No newline at end of file diff --git a/src/main/kotlin/solve/rendering/engine/camera/Camera.kt b/src/main/kotlin/solve/rendering/engine/camera/Camera.kt index cae870ed1..68bd66c8d 100644 --- a/src/main/kotlin/solve/rendering/engine/camera/Camera.kt +++ b/src/main/kotlin/solve/rendering/engine/camera/Camera.kt @@ -2,6 +2,7 @@ package solve.rendering.engine.camera import org.joml.Matrix4f import org.joml.Vector2f +import org.joml.Vector3f import solve.utils.times class Camera(var position: Vector2f = Vector2f(), zoom: Float = 1f) { @@ -42,5 +43,8 @@ class Camera(var position: Vector2f = Vector2f(), zoom: Float = 1f) { companion object { private const val ProjectionOrthoZNear = 0.01f private const val ProjectionOrthoZFar = 100f + + private val cameraFrontVector = Vector3f(0f, 0f, -1f) + private val cameraUpVector = Vector3f(0f, 1f, 0f) } } diff --git a/src/main/kotlin/solve/rendering/engine/structure/Component.kt b/src/main/kotlin/solve/rendering/engine/components/Component.kt similarity index 51% rename from src/main/kotlin/solve/rendering/engine/structure/Component.kt rename to src/main/kotlin/solve/rendering/engine/components/Component.kt index 073fb4450..a119b20c6 100644 --- a/src/main/kotlin/solve/rendering/engine/structure/Component.kt +++ b/src/main/kotlin/solve/rendering/engine/components/Component.kt @@ -1,6 +1,8 @@ -package solve.rendering.engine.structure +package solve.rendering.engine.components -open class Component(private val gameObject: GameObject) { +import solve.rendering.engine.scene.GameObject + +abstract class Component(val gameObject: GameObject) { open fun start() { } open fun update(deltaTime: Float) { } diff --git a/src/main/kotlin/solve/rendering/engine/components/Sprite.kt b/src/main/kotlin/solve/rendering/engine/components/Sprite.kt new file mode 100644 index 000000000..5a1778cb3 --- /dev/null +++ b/src/main/kotlin/solve/rendering/engine/components/Sprite.kt @@ -0,0 +1,36 @@ +package solve.rendering.engine.components + +import org.joml.Vector2f +import solve.rendering.engine.rendering.texture.Texture + +class Sprite(val texture: Texture, uvCoordinates: List = defaultUVCoordinates) { + val uvCoordinates: List + + val width: Int + get() = texture.width + val height: Int + get() = texture.height + + init { + if (uvCoordinates.count() != UVCoordinatesNumber) { + println("The number of the uv coordinates is incorrect!") + this.uvCoordinates = defaultUVCoordinates + } else { + this.uvCoordinates = uvCoordinates + } + } + + fun setTexture(texture: Texture) { + + } + + companion object { + private const val UVCoordinatesNumber = 4 + private val defaultUVCoordinates = listOf( + Vector2f(0f, 0f), + Vector2f(0f, 1f), + Vector2f(1f, 1f), + Vector2f(1f, 0f) + ) + } +} diff --git a/src/main/kotlin/solve/rendering/engine/components/SpriteRenderer.kt b/src/main/kotlin/solve/rendering/engine/components/SpriteRenderer.kt new file mode 100644 index 000000000..0a002ad1a --- /dev/null +++ b/src/main/kotlin/solve/rendering/engine/components/SpriteRenderer.kt @@ -0,0 +1,22 @@ +package solve.rendering.engine.components + +import solve.rendering.engine.rendering.texture.Texture +import solve.rendering.engine.scene.GameObject +import solve.rendering.engine.structures.Color + +class SpriteRenderer(gameObject: GameObject) : Component(gameObject) { + var color = Color.white + private set + var sprite: Sprite? = null + private set + + fun setColor(color: Color) { + this.color = color + } + + fun setTexture(texture: Texture) { + if (sprite?.texture != texture) { + sprite = Sprite(texture) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/solve/rendering/engine/rendering/batch/PrimitiveType.kt b/src/main/kotlin/solve/rendering/engine/rendering/batch/PrimitiveType.kt new file mode 100644 index 000000000..ea5a98111 --- /dev/null +++ b/src/main/kotlin/solve/rendering/engine/rendering/batch/PrimitiveType.kt @@ -0,0 +1,19 @@ +package solve.rendering.engine.rendering.batch + +import org.lwjgl.opengl.GL11.GL_LINES + +import org.lwjgl.opengl.GL11.GL_POINTS +import org.lwjgl.opengl.GL11.GL_TRIANGLES + +enum class PrimitiveType( + val verticesNumber: Int, + val openGLPrimitive: Int, + val verticesDrawingOrder: List +) { + Point(1, GL_POINTS, listOf(0)), + Line(2, GL_LINES, listOf(0, 1)), + Triangle(3, GL_TRIANGLES, listOf(0, 1, 2)), + Quad(4, GL_TRIANGLES, listOf(3, 2, 0, 0, 2, 1)); + + val drawingOrderElementsNumber = verticesDrawingOrder.count() +} diff --git a/src/main/kotlin/solve/rendering/engine/rendering/batch/RenderBatch.kt b/src/main/kotlin/solve/rendering/engine/rendering/batch/RenderBatch.kt new file mode 100644 index 000000000..d71d9721c --- /dev/null +++ b/src/main/kotlin/solve/rendering/engine/rendering/batch/RenderBatch.kt @@ -0,0 +1,136 @@ +package solve.rendering.engine.rendering.batch + +import org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER +import org.lwjgl.opengl.GL15.GL_DYNAMIC_DRAW +import org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER +import org.lwjgl.opengl.GL15.GL_STATIC_DRAW +import org.lwjgl.opengl.GL15.glBindBuffer +import org.lwjgl.opengl.GL15.glBufferData +import org.lwjgl.opengl.GL15.glBufferSubData +import org.lwjgl.opengl.GL15.glDeleteBuffers +import org.lwjgl.opengl.GL15.glGenBuffers +import org.lwjgl.opengl.GL20.glEnableVertexAttribArray +import org.lwjgl.opengl.GL20.glVertexAttribPointer +import org.lwjgl.opengl.GL30.glBindVertexArray +import org.lwjgl.opengl.GL30.glDeleteVertexArrays +import org.lwjgl.opengl.GL30.glGenVertexArrays +import solve.rendering.engine.rendering.texture.Texture +import solve.rendering.engine.shader.ShaderAttributeType + +open class RenderBatch( + private val maxBatchSize: Int, + val zIndex: Int, + val primitiveType: PrimitiveType, + private val attributes: List +) { + var isFull = false + private set + + private var vboID = 0 + private var vaoID = 0 + private var eboID = 0 + + private val textureIDs = mutableListOf() + private val textures = mutableListOf() + + private val attributesNumber = attributes.sumOf { it.number } + private val attributesTotalSize = attributes.sumOf { it.size } + + private val verticesDataBuffer = FloatArray(maxBatchSize * primitiveType.verticesNumber * attributesNumber) + private var verticesDataBufferIndexPointer = 0 + + init { + initializeBuffers() + initializeAttributes() + } + + fun bind() { + glBindVertexArray(vaoID) + textures.forEachIndexed { index, texture -> texture.bindToSlot(index + 1) } + } + + fun unbind() { + textures.forEach { it.unbind() } + glBindVertexArray(0) + } + + fun rebuffer() { + glBindBuffer(GL_ARRAY_BUFFER, vboID) + glBufferSubData(GL_ARRAY_BUFFER, 0, verticesDataBuffer) + } + + fun deleteBuffers() { + glDeleteBuffers(vboID) + glDeleteBuffers(eboID) + glDeleteVertexArrays(vaoID) + } + + fun addTexture(texture: Texture): Int { + var textureID = 0 + if (textures.contains(texture)) { + textureID = textures.indexOf(texture) + 1 + } else { + textures.add(texture) + textureID = textures.lastIndex + 1 + + if (textures.count() >= maxBatchSize) + isFull = true + } + + return textureID + } + + fun removeTexture(texture: Texture): Boolean = textures.remove(texture) + + fun containsTexture(texture: Texture) = textures.contains(texture) + + fun getVerticesNumber(): Int { + if (verticesDataBufferIndexPointer % attributesNumber != 0) + println("The vertices data buffer seems to not have correct amount of data!") + + return (verticesDataBufferIndexPointer * primitiveType.drawingOrderElementsNumber / + (attributesNumber * primitiveType.verticesNumber)) + } + + private fun initializeBuffers() { + vaoID = glGenVertexArrays() + glBindVertexArray(vaoID) + + vboID = glGenBuffers() + glBindBuffer(GL_ARRAY_BUFFER, vboID) + val verticesBufferSize = (verticesDataBuffer.size * Float.SIZE_BYTES).toLong() + glBufferData(GL_ARRAY_BUFFER, verticesBufferSize, GL_DYNAMIC_DRAW) + + eboID = glGenBuffers() + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboID) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, generateElementsIndices(), GL_STATIC_DRAW) + } + + private fun initializeAttributes() { + var attributesSizeOffset = 0L + for (i in 0 until attributes.count()) { + val attribute = attributes[i] + glVertexAttribPointer( + i, + attribute.number, + attribute.openGLType, + false, + attributesTotalSize, + attributesSizeOffset + ) + glEnableVertexAttribArray(i) + attributesSizeOffset += attribute.size + } + } + + private fun generateElementsIndices(): IntArray { + val elementsBuffer = IntArray(maxBatchSize * primitiveType.verticesNumber) + for (i in 0 until maxBatchSize) { + primitiveType.verticesDrawingOrder.forEach { vertexIndex -> + elementsBuffer[i * primitiveType.verticesNumber] = i + vertexIndex + } + } + + return elementsBuffer + } +} diff --git a/src/main/kotlin/solve/rendering/engine/rendering/renderers/Renderer.kt b/src/main/kotlin/solve/rendering/engine/rendering/renderers/Renderer.kt new file mode 100644 index 000000000..5a4b099ea --- /dev/null +++ b/src/main/kotlin/solve/rendering/engine/rendering/renderers/Renderer.kt @@ -0,0 +1,63 @@ +package solve.rendering.engine.rendering.renderers + +import org.lwjgl.opengl.GL11.GL_UNSIGNED_INT +import org.lwjgl.opengl.GL11.glDrawElements +import solve.rendering.engine.Window +import solve.rendering.engine.rendering.batch.RenderBatch +import solve.rendering.engine.shader.ShaderProgram +import solve.rendering.engine.scene.GameObject +import solve.rendering.engine.scene.Scene + +abstract class Renderer(protected val window: Window) { + protected var needToRebuffer = true + private lateinit var shaderProgram: ShaderProgram + private val batches = mutableListOf() + + init { + initialize() + } + + open fun render() { + beforeRender() + shaderProgram.use() + uploadUniforms(shaderProgram) + + if (needToRebuffer) { + updateBatchesData() + rebufferBatches() + } + + batches.forEach { batch -> + batch.bind() + glDrawElements(batch.primitiveType.openGLPrimitive, batch.getVerticesNumber(), GL_UNSIGNED_INT, 0) + batch.unbind() + } + shaderProgram.detach() + } + + fun cleanup() { + batches.forEach { it.deleteBuffers() } + } + + protected open fun beforeRender() { } + + protected abstract fun createShaderProgram(): ShaderProgram + + protected abstract fun createNewBatch(zIndex: Int): RenderBatch + + protected abstract fun uploadUniforms(shaderProgram: ShaderProgram) + + protected abstract fun updateBatchesData() + + protected abstract fun addGameObject(gameObject: GameObject) + + protected abstract fun removeGameObject(gameObject: GameObject): Boolean + + private fun initialize() { + shaderProgram = createShaderProgram() + } + + private fun rebufferBatches() { + batches.forEach { it.rebuffer() } + } +} diff --git a/src/main/kotlin/solve/rendering/engine/rendering/renderers/SpriteRenderer.kt b/src/main/kotlin/solve/rendering/engine/rendering/renderers/SpriteRenderer.kt new file mode 100644 index 000000000..f063cbe36 --- /dev/null +++ b/src/main/kotlin/solve/rendering/engine/rendering/renderers/SpriteRenderer.kt @@ -0,0 +1,78 @@ +package solve.rendering.engine.rendering.renderers + +import org.joml.Matrix4f +import solve.constants.ShadersDefaultFragmentPath +import solve.constants.ShadersDefaultVertexPath +import solve.rendering.engine.Window +import solve.rendering.engine.components.SpriteRenderer +import solve.rendering.engine.rendering.batch.PrimitiveType +import solve.rendering.engine.rendering.batch.RenderBatch +import solve.rendering.engine.shader.ShaderAttributeType +import solve.rendering.engine.shader.ShaderProgram +import solve.rendering.engine.shader.ShaderType +import solve.rendering.engine.scene.GameObject + +class SpriteRenderer(window: Window) : Renderer(window) { + private var modelsCommonMatrix: Matrix4f = Matrix4f().identity() + private val spriteRenderers = mutableListOf() + + fun changeModelsCommonMatrix(newMatrix: Matrix4f) { + modelsCommonMatrix = newMatrix + } + + override fun createShaderProgram(): ShaderProgram { + val shaderProgram = ShaderProgram() + shaderProgram.addShader(ShadersDefaultVertexPath, ShaderType.VERTEX) + shaderProgram.addShader(ShadersDefaultFragmentPath, ShaderType.FRAGMENT) + shaderProgram.link() + + return shaderProgram + } + + override fun createNewBatch(zIndex: Int): RenderBatch { + val shaderAttributesTypes = listOf( + ShaderAttributeType.FLOAT2, + ShaderAttributeType.FLOAT4, + ShaderAttributeType.FLOAT2, + ShaderAttributeType.FLOAT + ) + + return RenderBatch( + MaxBatchSize, + zIndex, + PrimitiveType.Quad, + shaderAttributesTypes + ) + } + + override fun uploadUniforms(shaderProgram: ShaderProgram) { + shaderProgram.uploadMatrix4f(projectionUniformName, window.calculateProjectionMatrix()) + shaderProgram.uploadMatrix4f(modelUniformName, modelsCommonMatrix) + } + + override fun updateBatchesData() { + + } + + override fun addGameObject(gameObject: GameObject) { + val spriteRenderer = gameObject.getComponentOfType() + if (spriteRenderer == null) { + println("The adding gameobject does not has a sprite renderer component!") + return + } + + spriteRenderers.add(spriteRenderer) + } + + override fun removeGameObject(gameObject: GameObject): Boolean { + val spriteRenderer = gameObject.getComponentOfType() ?: return false + return spriteRenderers.remove(spriteRenderer) + } + + companion object { + private const val MaxBatchSize = 1000 + + private const val projectionUniformName = "uProjection" + private const val modelUniformName = "uModel" + } +} \ No newline at end of file diff --git a/src/main/kotlin/solve/rendering/engine/rendering/texture/Texture.kt b/src/main/kotlin/solve/rendering/engine/rendering/texture/Texture.kt new file mode 100644 index 000000000..456815e05 --- /dev/null +++ b/src/main/kotlin/solve/rendering/engine/rendering/texture/Texture.kt @@ -0,0 +1,125 @@ +package solve.rendering.engine.rendering.texture + +import com.huskerdev.openglfx.core.GL_NEAREST +import com.huskerdev.openglfx.core.GL_TEXTURE_MIN_FILTER +import com.huskerdev.openglfx.core.GL_UNSIGNED_BYTE +import org.lwjgl.opengl.GL11.GL_REPEAT +import org.lwjgl.opengl.GL11.GL_TEXTURE_2D +import org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER +import org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_S +import org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_T +import org.lwjgl.opengl.GL11.glBindTexture +import org.lwjgl.opengl.GL11.glGenTextures +import org.lwjgl.opengl.GL11.glTexImage2D +import org.lwjgl.opengl.GL11.glTexParameteri +import org.lwjgl.opengl.GL13.GL_TEXTURE0 +import org.lwjgl.opengl.GL13.glActiveTexture +import org.lwjgl.stb.STBImage.stbi_image_free +import org.lwjgl.stb.STBImage.stbi_load +import org.lwjgl.stb.STBImage.stbi_set_flip_vertically_on_load +import solve.utils.getResourceAbsolutePath + +class Texture(private val resourcesPath: String) { + val textureID = glGenTextures() + var width = 0 + private set + var height = 0 + private set + var channelsType = TextureChannelsType.RGB + private set + + init { + initializeTextureParams() + loadTexture() + } + + fun bind() { + glBindTexture(GL_TEXTURE_2D, textureID) + } + + fun unbind() { + glBindTexture(GL_TEXTURE_2D, 0) + } + + fun bindToSlot(unit: Int) { + if (unit <= 0) { + println("The slot unit should be a positive number!") + return + } + + glActiveTexture(GL_TEXTURE0 + unit) + glBindTexture(GL_TEXTURE_2D, textureID) + } + + private fun initializeTextureParams() { + glBindTexture(GL_TEXTURE_2D, textureID) + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT) + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) + } + + private fun loadTexture() { + val absoluteImagePath = getResourceAbsolutePath(resourcesPath) + if (absoluteImagePath == null) { + println("The path of the reading texture is null!") + return + } + + val widthBuffer = IntArray(1) + val heightBuffer = IntArray(1) + val channelsNumberBuffer = IntArray(1) + + stbi_set_flip_vertically_on_load(true) + val image = stbi_load(absoluteImagePath, widthBuffer, heightBuffer, channelsNumberBuffer, 0) + + if (image == null) { + println("The image of the read texture is null!") + return + } + + width = widthBuffer.first() + height = heightBuffer.first() + val imageChannelsType = TextureChannelsType.getTextureChannelsType(channelsNumberBuffer.first()) + if (imageChannelsType == null) { + println("Wrong type of the texture image channels!") + return + } + + glTexImage2D( + GL_TEXTURE_2D, + 0, + channelsType.openGLType, + width, + height, + 0, + channelsType.openGLType, + GL_UNSIGNED_BYTE, + image + ) + stbi_image_free(image) + } + + override fun equals(other: Any?): Boolean { + if (other !is Texture) + return false + + return width == other.width && + height == other.height && + textureID == other.textureID && + resourcesPath == other.resourcesPath + } + + override fun hashCode(): Int { + val primeNumber = 31 + var result = resourcesPath.hashCode() + result = primeNumber * result + textureID + result = primeNumber * result + width + result = primeNumber * result + height + result = primeNumber * result + channelsType.hashCode() + + return result + } +} diff --git a/src/main/kotlin/solve/rendering/engine/rendering/texture/TextureChannelsType.kt b/src/main/kotlin/solve/rendering/engine/rendering/texture/TextureChannelsType.kt new file mode 100644 index 000000000..3c52ac363 --- /dev/null +++ b/src/main/kotlin/solve/rendering/engine/rendering/texture/TextureChannelsType.kt @@ -0,0 +1,20 @@ +package solve.rendering.engine.rendering.texture + +import com.huskerdev.openglfx.core.GL_RGBA +import org.lwjgl.opengl.GL11.GL_RGB + +enum class TextureChannelsType(val channelsNumber: Int, val openGLType: Int) { + RGB(3, GL_RGB), + RGBA(4, GL_RGBA); + + companion object { + fun getTextureChannelsType(channelsNumber: Int) = when (channelsNumber) { + 3 -> RGB + 4 -> RGBA + else -> { + println("Unknown texture channels type format!") + null + } + } + } +} diff --git a/src/main/kotlin/solve/rendering/engine/structure/GameObject.kt b/src/main/kotlin/solve/rendering/engine/scene/GameObject.kt similarity index 62% rename from src/main/kotlin/solve/rendering/engine/structure/GameObject.kt rename to src/main/kotlin/solve/rendering/engine/scene/GameObject.kt index 2f0cae6e6..6db4ba768 100644 --- a/src/main/kotlin/solve/rendering/engine/structure/GameObject.kt +++ b/src/main/kotlin/solve/rendering/engine/scene/GameObject.kt @@ -1,8 +1,13 @@ -package solve.rendering.engine.structure +package solve.rendering.engine.scene + +import solve.rendering.engine.components.Component +import kotlin.reflect.KClass class GameObject(private val name: String) { private val transform = Transform() - private val components = mutableListOf() + private val _components = mutableListOf() + val components: List + get() = _components var isDestroyed = false private set @@ -20,11 +25,17 @@ class GameObject(private val name: String) { } fun addComponent(component: Component) { - components.add(component) + _components.add(component) } fun removeComponent(component: Component) { - components.remove(component) + _components.remove(component) + } + + fun hasComponent(component: Component) = components.contains(component) + + inline fun getComponentOfType(): T? { + return components.firstOrNull { it is T } as? T? } fun enable() { diff --git a/src/main/kotlin/solve/rendering/engine/structure/Scene.kt b/src/main/kotlin/solve/rendering/engine/scene/Scene.kt similarity index 96% rename from src/main/kotlin/solve/rendering/engine/structure/Scene.kt rename to src/main/kotlin/solve/rendering/engine/scene/Scene.kt index 65756e990..d67295168 100644 --- a/src/main/kotlin/solve/rendering/engine/structure/Scene.kt +++ b/src/main/kotlin/solve/rendering/engine/scene/Scene.kt @@ -1,4 +1,4 @@ -package solve.rendering.engine.structure +package solve.rendering.engine.scene import solve.rendering.engine.camera.Camera diff --git a/src/main/kotlin/solve/rendering/engine/structure/SceneData.kt b/src/main/kotlin/solve/rendering/engine/scene/SceneData.kt similarity index 76% rename from src/main/kotlin/solve/rendering/engine/structure/SceneData.kt rename to src/main/kotlin/solve/rendering/engine/scene/SceneData.kt index f922f21e2..637b855cc 100644 --- a/src/main/kotlin/solve/rendering/engine/structure/SceneData.kt +++ b/src/main/kotlin/solve/rendering/engine/scene/SceneData.kt @@ -1,4 +1,4 @@ -package solve.rendering.engine.structure +package solve.rendering.engine.scene import solve.rendering.engine.camera.Camera diff --git a/src/main/kotlin/solve/rendering/engine/structure/Transform.kt b/src/main/kotlin/solve/rendering/engine/scene/Transform.kt similarity index 66% rename from src/main/kotlin/solve/rendering/engine/structure/Transform.kt rename to src/main/kotlin/solve/rendering/engine/scene/Transform.kt index 38e22f402..335a90ca2 100644 --- a/src/main/kotlin/solve/rendering/engine/structure/Transform.kt +++ b/src/main/kotlin/solve/rendering/engine/scene/Transform.kt @@ -1,10 +1,10 @@ -package solve.rendering.engine.structure +package solve.rendering.engine.scene import org.joml.Vector2f data class Transform( private val position: Vector2f = Vector2f(), - private val rotation: Vector2f = Vector2f(), + private val rotation: Float = 0f, private val scale: Vector2f = Vector2f(), private val zIndex: Float = 0f ) diff --git a/src/main/kotlin/solve/rendering/engine/shader/ShaderAttributeType.kt b/src/main/kotlin/solve/rendering/engine/shader/ShaderAttributeType.kt new file mode 100644 index 000000000..da2efc44d --- /dev/null +++ b/src/main/kotlin/solve/rendering/engine/shader/ShaderAttributeType.kt @@ -0,0 +1,18 @@ +package solve.rendering.engine.shader + +import org.lwjgl.opengl.GL11.GL_FLOAT +import org.lwjgl.opengl.GL11.GL_INT + +enum class ShaderAttributeType(val number: Int, val size: Int, val openGLType: Int) { + INT(1, Int.SIZE_BYTES, GL_INT), + INT2(2, 2 * Int.SIZE_BYTES, GL_INT), + INT3(3, 3 * Int.SIZE_BYTES, GL_INT), + INT4(4, 4 * Int.SIZE_BYTES, GL_INT), + FLOAT(1, Float.SIZE_BYTES, GL_FLOAT), + FLOAT2(2, 2 * Float.SIZE_BYTES, GL_FLOAT), + FLOAT3(3, 3 * Float.SIZE_BYTES, GL_FLOAT), + FLOAT4(4, 4 * Float.SIZE_BYTES, GL_FLOAT), + MAT2(9, 2 * 2 * Float.SIZE_BYTES, GL_FLOAT), + MAT3(9, 3 * 3 * Float.SIZE_BYTES, GL_FLOAT), + MAT4(16, 4 * 4 * Float.SIZE_BYTES, GL_FLOAT) +} diff --git a/src/main/kotlin/solve/rendering/engine/shader/ShaderProgram.kt b/src/main/kotlin/solve/rendering/engine/shader/ShaderProgram.kt index f5c69ba00..20e62bc37 100644 --- a/src/main/kotlin/solve/rendering/engine/shader/ShaderProgram.kt +++ b/src/main/kotlin/solve/rendering/engine/shader/ShaderProgram.kt @@ -61,7 +61,7 @@ class ShaderProgram { compileShader(shaderText, shaderType) } - fun compile() { + fun link() { glLinkProgram(shaderProgramID) val linkStatus = glGetProgrami(shaderProgramID, GL_LINK_STATUS) diff --git a/src/main/kotlin/solve/rendering/engine/structures/Color.kt b/src/main/kotlin/solve/rendering/engine/structures/Color.kt new file mode 100644 index 000000000..0afdd8384 --- /dev/null +++ b/src/main/kotlin/solve/rendering/engine/structures/Color.kt @@ -0,0 +1,42 @@ +package solve.rendering.engine.structures + +import org.joml.Vector4f + +class Color(r: Float, g: Float, b: Float, a: Float = 1f) { + var r = r + private set + var g = g + private set + var b = b + private set + var a = a + private set + + init { + if (r !in componentValueRange || + g !in componentValueRange || + b !in componentValueRange || + a !in componentValueRange + ) { + println("The colors components values should be in 0..1 range!") + white.copyTo(this) + } + } + + fun toVector4f(): Vector4f { + return Vector4f(r, g, b, a) + } + + fun copyTo(otherColor: Color) { + otherColor.r = r + otherColor.g = g + otherColor.b = b + otherColor.a = a + } + + companion object { + private val componentValueRange = 0f..1f + val white = Color(1f, 1f, 1f, 1f) + val black = Color(0f, 0f, 0f, 0f) + } +} diff --git a/src/main/kotlin/solve/utils/ResourcesUtils.kt b/src/main/kotlin/solve/utils/ResourcesUtils.kt index 6a6c01019..e7289458f 100644 --- a/src/main/kotlin/solve/utils/ResourcesUtils.kt +++ b/src/main/kotlin/solve/utils/ResourcesUtils.kt @@ -51,4 +51,6 @@ fun loadBufferedImage(filePath: String): BufferedImage? { return image } +fun getResourceAbsolutePath(resourcesPath: String) = getResource(resourcesPath)?.path + private fun getResource(resourcesPath: String) = Any::class::class.java.getResource("/$resourcesPath") diff --git a/src/main/resources/engine/shaders/default/default.frag b/src/main/resources/engine/shaders/default/default.frag new file mode 100644 index 000000000..48228fddd --- /dev/null +++ b/src/main/resources/engine/shaders/default/default.frag @@ -0,0 +1,44 @@ +#version 330 core + +in vec4 fColor; +in vec2 fTexCoords; +in float fTexId; + +uniform sampler2D uTextures[8]; + +out vec4 color; + +void main() +{ + if (fTexId > 0) { + int id = int(fTexId); + switch (id) { + case 0: + color = fColor * texture(uTextures[0], fTexCoords); + break; + case 1: + color = fColor * texture(uTextures[1], fTexCoords); + break; + case 2: + color = fColor * texture(uTextures[2], fTexCoords); + break; + case 3: + color = fColor * texture(uTextures[3], fTexCoords); + break; + case 4: + color = fColor * texture(uTextures[4], fTexCoords); + break; + case 5: + color = fColor * texture(uTextures[5], fTexCoords); + break; + case 6: + color = fColor * texture(uTextures[6], fTexCoords); + break; + case 7: + color = fColor * texture(uTextures[7], fTexCoords); + break; + } + } else { + color = fColor; + } +} diff --git a/src/main/resources/engine/shaders/default/default.vert b/src/main/resources/engine/shaders/default/default.vert new file mode 100644 index 000000000..4367fbf94 --- /dev/null +++ b/src/main/resources/engine/shaders/default/default.vert @@ -0,0 +1,21 @@ +#version 330 core +layout (location=0) in vec3 aPos; +layout (location=1) in vec4 aColor; +layout (location=2) in vec2 aTexCoords; +layout (location=3) in float aTexId; + +uniform mat4 uProjection; +uniform mat4 uModel; + +out vec4 fColor; +out vec2 fTexCoords; +out float fTexId; + +void main() +{ + fColor = aColor; + fTexCoords = aTexCoords; + fTexId = aTexId; + + gl_Position = uProjection * uModel * vec4(aPos, 1.0); +}