diff --git a/TSE_Base/src/version.h b/TSE_Base/src/version.h index be93a9f..ec2f451 100644 --- a/TSE_Base/src/version.h +++ b/TSE_Base/src/version.h @@ -5,7 +5,7 @@ namespace TSE { #define TSE_VERSION_MAJOR 0 #define TSE_VERSION_MINOR 1 - #define TSE_VERSION_BUILD 3 + #define TSE_VERSION_BUILD 4 #define TSE_VERSION_STRING std::to_string(TSE_VERSION_MAJOR) + "." + std::to_string(TSE_VERSION_MINOR) + "." + std::to_string(TSE_VERSION_BUILD) diff --git a/TSE_Core/src/BehaviourScripts/TileMap.cpp b/TSE_Core/src/BehaviourScripts/TileMap.cpp index 3c9aacf..57ea090 100644 --- a/TSE_Core/src/BehaviourScripts/TileMap.cpp +++ b/TSE_Core/src/BehaviourScripts/TileMap.cpp @@ -9,9 +9,7 @@ TSE::TileMapChunk::TileMapChunk(int _chunksize, const Vector2 &_pos, SortingOrde void TSE::TileMapChunk::SetTile(const Vector2& p, const Vector2& Spriteindex, TileSet* set) { - Sprite s; - set->GetSpriteAt(Spriteindex, s); - sprites[p] = s; + sprites[p] = set->GetSpriteIdAt(Spriteindex.x, Spriteindex.y); } void TSE::TileMapChunk::RemoveTile(Vector2 p) @@ -24,6 +22,130 @@ void TSE::TileMapChunk::SetOrdering(SortingOrder _order) order = _order; } +void TSE::TileMapChunk::GetOrderedPositions(Vector2 *array) +{ + switch (order) + { + case TopLeft: + for (int y = 0; y < chunksize; y++) + { + Vector2 offset = nextLine * y; + for (int x = 0; x < chunksize; x++) + { + Vector2 p(x,y); + auto v = sprites.find(p); + if(v != sprites.end()) + *array++ = v->first - offset; + } + } + break; + case TopRight: + for (int y = 0; y < chunksize; y++) + { + Vector2 offset = nextLine * y; + for (int x = chunksize - 1; x >= 0; x--) + { + Vector2 p(x,y); + auto v = sprites.find(p); + if(v != sprites.end()) + *array++ = v->first - offset; + } + } + break; + case BottomLeft: + for (int y = chunksize - 1; y >= 0; y--) + { + Vector2 offset = nextLine * y; + for (int x = 0; x < chunksize; x++) + { + Vector2 p(x,y); + auto v = sprites.find(p); + if(v != sprites.end()) + *array++ = v->first - offset; + } + } + break; + case BottomRight: + for (int y = chunksize - 1; y >= 0; y--) + { + Vector2 offset = nextLine * y; + for (int x = chunksize - 1; x >= 0; x--) + { + Vector2 p(x,y); + auto v = sprites.find(p); + if(v != sprites.end()) + *array++ = v->first - offset; + } + } + break; + } +} + +void TSE::TileMapChunk::GetOrderedSpriteIds(int *array) +{ + switch (order) + { + case TopLeft: + for (int y = 0; y < chunksize; y++) + { + for (int x = 0; x < chunksize; x++) + { + Vector2 p(x,y); + auto v = sprites.find(p); + if(v != sprites.end()) + *array++ = v->second; + } + } + break; + case TopRight: + for (int y = 0; y < chunksize; y++) + { + for (int x = chunksize - 1; x >= 0; x--) + { + Vector2 p(x,y); + auto v = sprites.find(p); + if(v != sprites.end()) + *array++ = v->second; + } + } + break; + case BottomLeft: + for (int y = chunksize - 1; y >= 0; y--) + { + for (int x = 0; x < chunksize; x++) + { + Vector2 p(x,y); + auto v = sprites.find(p); + if(v != sprites.end()) + *array++ = v->second; + } + } + break; + case BottomRight: + for (int y = chunksize - 1; y >= 0; y--) + { + for (int x = chunksize - 1; x >= 0; x--) + { + Vector2 p(x,y); + auto v = sprites.find(p); + if(v != sprites.end()) + *array++ = v->second; + } + } + break; + } +} + +int TSE::TileMapChunk::GetChunksize() +{ + return chunksize; +} + +int TSE::TileMapChunk::GetSpriteCount() +{ + return sprites.size(); +} + void TSE::TileMap::RemoveTile(Vector2 p) { Vector2 chunkInnerPos = LocalToChunkPos(p); @@ -39,13 +161,118 @@ void TSE::TileMap::SetTile(Vector2 p, Vector2 Spriteindex) if(!chunks.contains(chunkIndex)) { chunks[chunkIndex] = TileMapChunk(chunkSize, chunkIndex, order); + chunks[chunkIndex].nextLine = nextLine; + CheckBounds(chunkIndex); } chunks[chunkIndex].SetTile(chunkInnerPos, Spriteindex, set); } +TSE::TileMapChunk* TSE::TileMap::GetChunk(const Vector2 &pos) +{ + auto chunk = chunks.find(pos); + if(chunk == chunks.end()) + return nullptr; + return &chunks[pos]; +} + +void TSE::TileMap::GetChunkPositionsInOrder(Vector2 *arr) +{ + + switch (order) + { + case TopLeft: + for (int y = bounds.p1.y; y < bounds.p2.y + 1; y++) + { + for (int x = bounds.p1.x; x < bounds.p2.x + 1; x++) + { + Vector2 p(x,y); + auto v = chunks.find(p); + if(v != chunks.end()) + *arr++ = v->first; + } + } + break; + case TopRight: + for (int y = bounds.p1.y; y < bounds.p2.y + 1; y++) + { + for (int x = bounds.p2.x; x > bounds.p1.x - 1; x--) + { + Vector2 p(x,y); + auto v = chunks.find(p); + if(v != chunks.end()) + *arr++ = v->first; + } + } + break; + case BottomLeft: + for (int y = bounds.p2.y; y > bounds.p1.y - 1; y--) + { + for (int x = bounds.p1.x; x < bounds.p2.x + 1; x++) + { + Vector2 p(x,y); + auto v = chunks.find(p); + if(v != chunks.end()) + *arr++ = v->first; + } + } + break; + case BottomRight: + for (int y = bounds.p2.y; y > bounds.p1.y - 1; y--) + { + for (int x = bounds.p2.x; x > bounds.p1.x - 1; x--) + { + Vector2 p(x,y); + auto v = chunks.find(p); + if(v != chunks.end()) + *arr++ = v->first; + } + } + break; + } +} + +int TSE::TileMap::GetChunkCount() +{ + return chunks.size(); +} + +TSE::TileSet *TSE::TileMap::GetTileSet() +{ + return set; +} + +void TSE::TileMap::SetNextLineOffset(const Vector2 &offset) +{ + nextLine = offset; + for(auto& [_, chunk] : chunks) + { + chunk.nextLine = offset; + } +} + +TSE::Vector2 TSE::TileMap::GetNextLineOffset() +{ + return nextLine; +} + +void TSE::TileMap::CheckBounds(Vector2 pos) +{ + if(pos.x > bounds.p2.x) + bounds.p2.x = pos.x; + if(pos.y > bounds.p2.y) + bounds.p2.y = pos.y; + if(pos.x < bounds.p1.x) + bounds.p1.x = pos.x; + if(pos.y < bounds.p1.y) + bounds.p1.y = pos.y; +} + TSE::Vector2 TSE::TileMap::LocalToChunkPos(const Vector2 &v) { - return Vector2((int)v.x % chunkSize, (int)v.y % chunkSize); + Vector2 p = Vector2((int)v.x % chunkSize, (int)v.y % chunkSize); + if(p.x < 0) p.x += chunkSize; + if(p.y < 0) p.y += chunkSize; + return p; } TSE::Vector2 TSE::TileMap::ChunkToLocalPos(const Vector2 &v, const TileMapChunk &chunk) diff --git a/TSE_Core/src/BehaviourScripts/TileMap.hpp b/TSE_Core/src/BehaviourScripts/TileMap.hpp index c30abe1..3f790f1 100644 --- a/TSE_Core/src/BehaviourScripts/TileMap.hpp +++ b/TSE_Core/src/BehaviourScripts/TileMap.hpp @@ -23,33 +23,50 @@ namespace TSE private: SortingOrder order; int chunksize; - std::unordered_map sprites; + std::unordered_map sprites; public: + Vector2 nextLine; Vector2 pos; - TileMapChunk(int _chunksize, const Vector2& _pos, SortingOrder _order); + TileMapChunk(int _chunksize = 16, const Vector2& _pos = {0,0}, SortingOrder _order = TopRight); void SetTile(const Vector2& p, const Vector2& Spriteindex, TileSet* set); void RemoveTile(Vector2 p); void SetOrdering(SortingOrder _order); + void GetOrderedPositions(Vector2* array); + void GetOrderedSpriteIds(int* array); + int GetChunksize(); + int GetSpriteCount(); }; class TileMap : public BehaviourScript { + private: + Rect bounds = Rect(0,0,0,0); + Vector2 nextLine = Vector2(-0.5f, 1.25f); public: int chunkSize = 16; SortingOrder order = TopRight; + Vector2 SpriteScale = Vector2(1,1); TileSet* set; std::unordered_map chunks; void RemoveTile(Vector2 p); void SetTile(Vector2 p, Vector2 Spriteindex); + TileMapChunk* GetChunk(const Vector2& pos); + void GetChunkPositionsInOrder(Vector2* arr); + int GetChunkCount(); + TileSet* GetTileSet(); + const Rect& GetBounds() const { return bounds; } + void SetNextLineOffset(const Vector2& offset); + Vector2 GetNextLineOffset(); inline const char* GetName() override { return "Tile Map"; } private: + void CheckBounds(Vector2 pos); Vector2 LocalToChunkPos(const Vector2& v); Vector2 ChunkToLocalPos(const Vector2& v, const TileMapChunk& chunk); }; diff --git a/TSE_Core/src/elements/TileSet.cpp b/TSE_Core/src/elements/TileSet.cpp index e37c7b6..9a0720b 100644 --- a/TSE_Core/src/elements/TileSet.cpp +++ b/TSE_Core/src/elements/TileSet.cpp @@ -33,3 +33,24 @@ void TSE::TileSet::GetSpriteAt(int x, int y, Sprite &s) s = Sprite(tex, Rect(startpos, endpos)); } + +int TSE::TileSet::GetSpriteIdAt(int x, int y) +{ + if(x < 0 || x >= resx || y < 0 || y >= resy) + { + TSE_ERROR("The sprite you are trying to access is out of range"); + return -1; + } + + return y * resx + x; +} + +uint TSE::TileSet::GetTextueID() +{ + return tex->GetTextureId(); +} + +TSE::Vector2 TSE::TileSet::GetCount() +{ + return Vector2(resx, resy); +} diff --git a/TSE_Core/src/elements/TileSet.hpp b/TSE_Core/src/elements/TileSet.hpp index c507878..0680db2 100644 --- a/TSE_Core/src/elements/TileSet.hpp +++ b/TSE_Core/src/elements/TileSet.hpp @@ -18,6 +18,9 @@ namespace TSE void SetTexture(Texture* tex); void GetSpriteAt(int x, int y, Sprite& s); + int GetSpriteIdAt(int x, int y); + uint GetTextueID(); + Vector2 GetCount(); inline void SetCount(Vector2& v) { @@ -27,5 +30,6 @@ namespace TSE { GetSpriteAt(v.x, v.y, s); }; + }; } // namespace TSE diff --git a/TSE_Core/src/elements/Transformable.cpp b/TSE_Core/src/elements/Transformable.cpp index 94a4493..5538fa3 100644 --- a/TSE_Core/src/elements/Transformable.cpp +++ b/TSE_Core/src/elements/Transformable.cpp @@ -110,7 +110,7 @@ namespace TSE Matrix4x4 Transformable::GetLocalMatrix() const { - return Matrix4x4::ToTranslationMatrix(position) * Matrix4x4::ToRotationMatrix(rotation) * Matrix4x4::ToScaleMatrix(scale);; + return Matrix4x4::ToTranslationMatrix(position) * Matrix4x4::ToRotationMatrix(rotation) * Matrix4x4::ToScaleMatrix(scale); } Matrix4x4 Transformable::GetGlobalMatrix() const diff --git a/TSE_Editor/src/UI/ElementDrawer.cpp b/TSE_Editor/src/UI/ElementDrawer.cpp index b07c770..2769552 100644 --- a/TSE_Editor/src/UI/ElementDrawer.cpp +++ b/TSE_Editor/src/UI/ElementDrawer.cpp @@ -64,7 +64,7 @@ namespace TSE::EDITOR // Suchfeld mit X Button ImGui::PushItemWidth(-style.FramePadding.x * 4 - 24); // Platz für Clear Button - ImGui::InputTextWithHint("##SearchField", "Suchfeld", searchBuffer, IM_ARRAYSIZE(searchBuffer)); + ImGui::InputTextWithHint("##SearchField", "Search", searchBuffer, IM_ARRAYSIZE(searchBuffer)); ImGui::PopItemWidth(); ImGui::SameLine(); @@ -203,6 +203,10 @@ namespace TSE::EDITOR { Draw((ParticleSystem*)element, debug); } + else if (name == "Tile Map") + { + Draw((TileMap*)element, debug); + } else { element->CustomDraw(debug); @@ -813,6 +817,44 @@ namespace TSE::EDITOR ImGui::Unindent(20.0f); } } + void ElementDrawer::Draw(TileMap *element, const bool &debug) + { + int orderIndex = static_cast(element->order); + const char* orderItems[] = { "TopRight", "TopLeft", "BottomRight", "BottomLeft" }; + if (ImGui::Combo("Order", &orderIndex, orderItems, IM_ARRAYSIZE(orderItems))) + { + element->order = static_cast(orderIndex); + for (auto& [_, chunk] : element->chunks) + { + chunk.SetOrdering(element->order); + } + } + + ImGui::BeginDisabled(); + ImGui::DragInt("Chunk Size", &element->chunkSize, 1.0f); + ImGui::EndDisabled(); + + Vector2 nextLine = element->GetNextLineOffset(); + if (ImGui::DragFloat2("Next Line Offset", &nextLine.x, 0.01f)) + { + element->SetNextLineOffset(nextLine); + } + + ImGui::DragFloat2("Sprite Scale", &element->SpriteScale.x, 0.01f); + + if (debug) + { + Rect bounds = element->GetBounds(); + ImGui::Separator(); + ImGui::TextDisabled("Bounds"); + ImGui::BeginDisabled(); + ImGui::InputFloat2("Min", &bounds.p1.x); + ImGui::InputFloat2("Max", &bounds.p2.x); + ImGui::EndDisabled(); + + ImGui::TextDisabled(("Chunk Count: " + std::to_string(element->GetChunkCount())).c_str()); + } + } void ElementDrawer::DrawAudioClipCompact(AudioClip *element, const bool &debug, const std::string &label) { float item_spacing = ImGui::GetStyle().ItemSpacing.x; diff --git a/TSE_Editor/src/UI/ElementDrawer.hpp b/TSE_Editor/src/UI/ElementDrawer.hpp index 2acb17f..bdd5f89 100644 --- a/TSE_Editor/src/UI/ElementDrawer.hpp +++ b/TSE_Editor/src/UI/ElementDrawer.hpp @@ -13,6 +13,7 @@ #include "BehaviourScripts/ParticleSystem.hpp" #include "BehaviourScripts/AudioListener.hpp" #include "BehaviourScripts/AudioSource.hpp" +#include "BehaviourScripts/TileMap.hpp" namespace TSE::EDITOR { @@ -79,6 +80,7 @@ namespace TSE::EDITOR static void Draw(AudioClip* element, const bool& debug, const std::string& label = "", const bool small = false); static void Draw(Camera* element, const bool& debug); static void Draw(ParticleSystem* element, const bool& debug); + static void Draw(TileMap* element, const bool& debug); static void DrawAudioClipCompact(AudioClip* element, const bool& debug, const std::string& label); static void DrawAudioClipNormal(AudioClip* element, const bool& debug, const std::string& label); diff --git a/TSE_GlfwOpenGlImpl/src/shader/basicTileMapShader.cpp b/TSE_GlfwOpenGlImpl/src/shader/basicTileMapShader.cpp new file mode 100644 index 0000000..6539289 --- /dev/null +++ b/TSE_GlfwOpenGlImpl/src/shader/basicTileMapShader.cpp @@ -0,0 +1,212 @@ +#include "basicTileMapShader.hpp" +#include "BehaviourScripts/Renderable.hpp" +#include "BehaviourScripts/TileMap.hpp" +#include "Color.hpp" +#include "basicTileMapShaderGLSL.hpp" + +#define SHADER_MESH_INDEX 0 +#define SHADER_POS_INDEX 1 +#define SHADER_SPRITE_INDEX 2 + +#define SHADER_PACKAGE_SIZE sizeof(float) * (3 + 1) + +TSE::GLFW::BasicTileMapShader* TSE::GLFW::BasicTileMapShader::instance = nullptr; + +TSE::GLFW::BasicTileMapShader *TSE::GLFW::BasicTileMapShader::Instance() +{ + return instance; +} + +void TSE::GLFW::BasicTileMapShader::Destroy() +{ + if(instance != nullptr) + delete instance; + instance = nullptr; +} + +void TSE::GLFW::BasicTileMapShader::Init(float width, float height) +{ + std::vector> parts; + parts.push_back(ShaderPart::LoadFromString(vertTile, GL_VERTEX_SHADER)); + parts.push_back(ShaderPart::LoadFromString(fragTile, GL_FRAGMENT_SHADER)); + instance = new BasicTileMapShader(std::move(parts)); + + instance->Enable(); + int texIDs[] = { 0 }; + instance->SetUniform("atlas", 0); + instance->Disable(); +} + +TSE::GLFW::BasicTileMapShader::BasicTileMapShader(std::vector> &&parts) : Shader(parts) +{ + PackageSize = SHADER_PACKAGE_SIZE; +} + +TSE::GLFW::BasicTileMapShader::~BasicTileMapShader() +{ + if (meshVBO) glDeleteBuffers(1, &meshVBO); + if (meshIBO) glDeleteBuffers(1, &meshIBO); +} + +void TSE::GLFW::BasicTileMapShader::SetMesh(const void *verts, int vertCount, int stride, int floatCountPerVertex, int posOffsetBytes, GLenum primitive, const void *indices, int indexCount, GLenum indexType) +{ + GLint prevVAO = 0, prevArrayBuffer = 0, prevElementBuffer = 0; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &prevVAO); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prevArrayBuffer); + glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &prevElementBuffer); + + if (!meshVBO) glGenBuffers(1, &meshVBO); + glBindBuffer(GL_ARRAY_BUFFER, meshVBO); + glBufferData(GL_ARRAY_BUFFER, vertCount * stride, verts, GL_STATIC_DRAW); + + if (indices && indexCount > 0) + { + if (!meshIBO) glGenBuffers(1, &meshIBO); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, meshIBO); + GLsizeiptr idxSize = + (indexType == GL_UNSIGNED_INT ? 4 : + indexType == GL_UNSIGNED_SHORT? 2 : 1) * indexCount; + glBufferData(GL_ELEMENT_ARRAY_BUFFER, idxSize, indices, GL_STATIC_DRAW); + meshIndexCount = indexCount; + meshIndexType = indexType; + } + else + { + // Kein Index-Buffer + if (meshIBO) { glDeleteBuffers(1, &meshIBO); meshIBO = 0; } + meshIndexCount = 0; + } + + meshVertexCount = vertCount; + meshStride = stride; + meshPosOffset = posOffsetBytes; + meshPosSize = floatCountPerVertex; + meshPrimitive = primitive; + meshReady = true; + + glBindBuffer(GL_ARRAY_BUFFER, prevArrayBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, prevElementBuffer); + glBindVertexArray(prevVAO); +} + +void TSE::GLFW::BasicTileMapShader::OnEnable() const +{ + if (!meshReady) + { + // Fallback: unit-Quad als TRIANGLE_FAN (4 Vertices, 2D Positionen) + const float quad[8] = { -0.5f,-0.5f, 0.5f,-0.5f, 0.5f,0.5f, -0.5f,0.5f }; + const_cast(this)->SetMesh( + quad, 4, sizeof(float)*2, 2, 0, GL_TRIANGLE_FAN + ); + } + + GLint prevArrayBuffer = 0; + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prevArrayBuffer); + + glBindBuffer(GL_ARRAY_BUFFER, meshVBO); + glEnableVertexAttribArray(SHADER_MESH_INDEX); // LOC_QUAD/pos + glVertexAttribPointer(SHADER_MESH_INDEX, meshPosSize, GL_FLOAT, GL_FALSE, meshStride, (void*)meshPosOffset); + glVertexAttribDivisor(SHADER_MESH_INDEX, 0); // per-vertex (Mesh) + + glBindBuffer(GL_ARRAY_BUFFER, prevArrayBuffer); + + // layout 1: position (vec3) + glEnableVertexAttribArray(SHADER_POS_INDEX); + glVertexAttribPointer(SHADER_POS_INDEX, 3, GL_FLOAT, GL_FALSE, PackageSize, (void*)0); + glVertexAttribDivisor(SHADER_POS_INDEX, 1); + + // layout 2: spriteindex (float) + glEnableVertexAttribArray(SHADER_SPRITE_INDEX); + glVertexAttribPointer(SHADER_SPRITE_INDEX, 1, GL_FLOAT, GL_FALSE, PackageSize, (void*)(sizeof(float)*3)); + glVertexAttribDivisor(SHADER_SPRITE_INDEX, 1); +} + +void TSE::GLFW::BasicTileMapShader::OnDisable() const +{ + glDisableVertexAttribArray(SHADER_MESH_INDEX); + glDisableVertexAttribArray(SHADER_POS_INDEX); + glDisableVertexAttribArray(SHADER_SPRITE_INDEX); +} + +void TSE::GLFW::BasicTileMapShader::OnFlush() +{ + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, TextureID); +} + +void TSE::GLFW::BasicTileMapShader::OnDrawCall(int indexCount) +{ + if (instanceCount <= 0) return; + SetUniform("spriteCount", &SpriteCount); + SetUniform("spriteScale", &SpriteScale); + + GLint prevElementBuffer = 0; + glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &prevElementBuffer); + + if (meshIBO && meshIndexCount > 0) + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, meshIBO); + glDrawElementsInstanced(meshPrimitive, meshIndexCount, meshIndexType, (void*)0, instanceCount); + } + else + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glDrawArraysInstanced(meshPrimitive, 0, meshVertexCount, instanceCount); + } + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (GLuint)prevElementBuffer); + instanceCount = 0; +} + +void TSE::GLFW::BasicTileMapShader::OnSubmit(const Transformable &t, float *&target, TransformationStack &stack, void (*restartDrawcall)(IRenderer &), IRenderer &rnd) +{ + auto* r = dynamic_cast(t.GetBehaviourScript(RENDERABLE)); + if (!r) return; + + auto* tm = dynamic_cast(t.GetBehaviourScript(TILE_MAP)); + if (!tm) return; + + auto tileSet = tm->GetTileSet(); + TextureID = tileSet->GetTextueID(); + SpriteCount = tileSet->GetCount(); + SpriteScale = tm->SpriteScale; + + int chunkcount = tm->GetChunkCount(); + Vector2 orderedChunks[chunkcount]; + tm->GetChunkPositionsInOrder(orderedChunks); + + Matrix4x4 matr = t.GetLocalMatrix(); + + stack.Push(matr); + + for(auto chunkPos : orderedChunks) + { + auto chunk = tm->GetChunk(chunkPos); + int spriteCount = chunk->GetSpriteCount(); + Vector2 spritePositions[spriteCount]; + int spriteIds[spriteCount]; + chunk->GetOrderedPositions(spritePositions); + chunk->GetOrderedSpriteIds(spriteIds); + int chunkSize = chunk->GetChunksize(); + + for (int i = 0; i < spriteCount; i++) + { + Matrix4x4 mat = Matrix4x4::ToTranslationMatrix((chunkPos - chunk->nextLine * chunkPos.y) + spritePositions[i]) * Matrix4x4::ToRotationMatrix(Quaternion()) * Matrix4x4::ToScaleMatrix({1,1,1}); + stack.Push(mat); + Vector3 pos = stack.Top() * Vector3(0,0,0); + + *target++ = pos.x; + *target++ = pos.y; + *target++ = pos.z; + *target++ = spriteIds[i]; + + ++instanceCount; + stack.Pop(); + + if(instanceCount >= 20000) + restartDrawcall(rnd); + } + } + + restartDrawcall(rnd); +} diff --git a/TSE_GlfwOpenGlImpl/src/shader/basicTileMapShader.hpp b/TSE_GlfwOpenGlImpl/src/shader/basicTileMapShader.hpp new file mode 100644 index 0000000..b58a337 --- /dev/null +++ b/TSE_GlfwOpenGlImpl/src/shader/basicTileMapShader.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "GL/gl3w.h" +#include "GL/gl.h" +#include "Shader.hpp" +#include "Types.hpp" + +namespace TSE::GLFW +{ + class BasicTileMapShader : public Shader + { + private: + static BasicTileMapShader* instance; + mutable bool meshReady = false; + GLuint meshVBO = 0; + GLuint meshIBO = 0; + GLsizei meshVertexCount = 0; // für DrawArraysInstanced + GLsizei meshIndexCount = 0; // für DrawElementsInstanced + GLenum meshPrimitive = GL_TRIANGLES; + GLenum meshIndexType = GL_UNSIGNED_SHORT; + int instanceCount = 0; // eigener Instanzzähler + GLint meshPosSize = 2; // 2D (Billboard-Formen), für 3D Meshes: 3 + GLsizei meshStride = sizeof(float) * 2; + size_t meshPosOffset = 0; + GLuint TextureID; + Vector2 SpriteCount; + Vector2 SpriteScale; + + public: + static BasicTileMapShader* Instance(); + static void Destroy(); + static void Init(float width, float height); + BasicTileMapShader(std::vector>&& parts); + ~BasicTileMapShader(); + void SetMesh(const void* verts, int vertCount, int stride, int floatCountPerVertex, int posOffsetBytes, GLenum primitive, const void* indices = nullptr, int indexCount = 0, GLenum indexType = GL_UNSIGNED_SHORT); + + protected: + void OnEnable() const override; + void OnDisable() const override; + void OnFlush() override; + void OnDrawCall(int indexCount) override; + void OnSubmit(const Transformable& t, float*& target, TransformationStack& stack, void (*restartDrawcall)(IRenderer&), IRenderer& rnd) override; + }; +} // namespace TSE::GLFW diff --git a/TSE_GlfwOpenGlImpl/src/shader/basicTileMapShaderGLSL.hpp b/TSE_GlfwOpenGlImpl/src/shader/basicTileMapShaderGLSL.hpp new file mode 100644 index 0000000..736ca77 --- /dev/null +++ b/TSE_GlfwOpenGlImpl/src/shader/basicTileMapShaderGLSL.hpp @@ -0,0 +1,60 @@ +#pragma once + +inline const char* vertTile = R"( + #version 330 core + + layout(location = 0) in vec2 aPos; + + layout(location = 1) in vec3 iTilePos; + layout(location = 2) in float iSpriteId; + + uniform mat4 prMatrix; + uniform mat4 camMatrix; + uniform vec2 spriteCount; + uniform vec2 spriteScale; + + out vec2 vUV; + flat out int vSpriteId; + + void main() + { + vec3 local = vec3(aPos.x, aPos.y, 0); + vec2 baseUV = aPos + vec2(0.5); + vec3 tileSize = vec3(spriteScale.x, spriteScale.y, 1); + + vec3 worldPos = (iTilePos * tileSize) + (local * tileSize); + + gl_Position = prMatrix * camMatrix * vec4(worldPos.x, worldPos.y, worldPos.z, 1.0); + + vUV = baseUV; + vSpriteId = int(iSpriteId + 0.5); + } +)"; + +inline const char* fragTile = R"( + #version 330 core + + in vec2 vUV; + flat in int vSpriteId; + + uniform sampler2D atlas; + uniform vec2 spriteCount; + + out vec4 FragColor; + + void main() + { + vec2 tileUVSize = 1.0 / spriteCount; + + int cols = int(spriteCount.x); + int sx = vSpriteId % cols; + int sy = vSpriteId / cols; + + vec2 atlasOffset = vec2(float(sx), float(sy)) * tileUVSize; + vec2 atlasUV = atlasOffset + (vUV * tileUVSize); + vec4 c = texture(atlas, atlasUV); + if (c.a < 0.01) discard; + + FragColor = c; + } +)"; diff --git a/TSE_GlfwOpenGlImpl/src/shader/defaultShaderHandler.cpp b/TSE_GlfwOpenGlImpl/src/shader/defaultShaderHandler.cpp index 0654c46..c8a51d8 100644 --- a/TSE_GlfwOpenGlImpl/src/shader/defaultShaderHandler.cpp +++ b/TSE_GlfwOpenGlImpl/src/shader/defaultShaderHandler.cpp @@ -3,6 +3,7 @@ #include "basicTextureShader.hpp" #include "ditheringShader.hpp" #include "basicParticleShader.hpp" +#include "basicTileMapShader.hpp" #include "elements/ShaderRegistry.hpp" void TSE::GLFW::LoadBasicShaders(float width, float height) @@ -11,10 +12,12 @@ void TSE::GLFW::LoadBasicShaders(float width, float height) BasicTextureShader::Init(width, height); DitheringShader::Init(width, height); BasicParticleShader::Init(width, height); + BasicTileMapShader::Init(width, height); ShaderRegistry::SetShader("Basic Unlit Shader", BasicShader::Instance()); ShaderRegistry::SetShader("Basic Unlit Texture Shader", BasicTextureShader::Instance()); ShaderRegistry::SetShader("Basic Unlit Dithering Shader", DitheringShader::Instance()); ShaderRegistry::SetShader("Basic Unlit Particle Shader", BasicParticleShader::Instance()); + ShaderRegistry::SetShader("Basic Unlit TileMap Shader", BasicTileMapShader::Instance()); } void TSE::GLFW::UnLoadBasicShaders() @@ -23,8 +26,10 @@ void TSE::GLFW::UnLoadBasicShaders() ShaderRegistry::RemoveShader("Basic Unlit Texture Shader"); ShaderRegistry::RemoveShader("Basic Unlit Dithering Shader"); ShaderRegistry::RemoveShader("Basic Unlit Particle Shader"); + ShaderRegistry::RemoveShader("Basic Unlit TileMap Shader"); BasicShader::Destroy(); BasicTextureShader::Destroy(); DitheringShader::Destroy(); BasicParticleShader::Destroy(); + BasicTileMapShader::Destroy(); }