diff --git a/application/lucre/gameState.cpp b/application/lucre/gameState.cpp index edc1b2e4..b4e3083c 100644 --- a/application/lucre/gameState.cpp +++ b/application/lucre/gameState.cpp @@ -55,7 +55,7 @@ namespace LucreApp #ifdef MACOSX SetNextState(State::CUTSCENE); #else - SetNextState(State::DESSERT); + SetNextState(State::TERRAIN); #endif } diff --git a/application/lucre/sceneDescriptions/terrain.json b/application/lucre/sceneDescriptions/terrain.json index c2de3d1d..f93a048f 100644 --- a/application/lucre/sceneDescriptions/terrain.json +++ b/application/lucre/sceneDescriptions/terrain.json @@ -2,7 +2,49 @@ "file format identifier": 1.2, "description": "terrain scene", "author": "Copyright (c) 2024 Engine Development Team", - "terrainPngPath": "application/lucre/models/assets/terrain/heightmap2.png", + "terrain": + [ + { + "filename": "application/lucre/terrainDescriptions/heightmap2.json", + "instances": + [ + { + "transform": + { + "scale": + [ + 0.141, 0.141, 0.141 + ], + "rotation": + [ + 3.14159, 1.07215, 3.14159 + ], + "translation": + [ + -0.779968, 2.25822, -4 + ] + } + }, + { + "transform": + { + "scale": + [ + 0.140998, 0.140998, 0.140998 + ], + "rotation": + [ + 3.01614, 1.00771, 2.99355 + ], + "translation": + [ + -0.494322, 2.28222, -3.408 + ] + } + } + ] + } + ], "fastgltf files": [ { diff --git a/application/lucre/scenes/terrainScene.cpp b/application/lucre/scenes/terrainScene.cpp index e34ffa6e..0e5f1f1d 100644 --- a/application/lucre/scenes/terrainScene.cpp +++ b/application/lucre/scenes/terrainScene.cpp @@ -156,15 +156,7 @@ namespace LucreApp void TerrainScene::LoadTerrain() { - Builder builder; - m_Terrain = builder.LoadTerrainHeightMapPNG(m_SceneLoaderJSON.GetTerrainPath(), *this); - if (m_Terrain != entt::null) - { - auto view = m_Registry.view(); - auto& terrainTransform = view.get(m_Terrain); - terrainTransform.SetScale(0.054f); - terrainTransform.SetTranslation({-0.576f, 2.33f, -0.117f}); - } + m_Terrain = m_Dictionary.Retrieve("application/lucre/terrainDescriptions/heightmap2.json::0"); } void TerrainScene::LoadModels() { diff --git a/application/lucre/terrainDescriptions/heightmap2.json b/application/lucre/terrainDescriptions/heightmap2.json new file mode 100644 index 00000000..1e472600 --- /dev/null +++ b/application/lucre/terrainDescriptions/heightmap2.json @@ -0,0 +1,6 @@ +{ + "file format identifier": 1.2, + "description": "height map 2", + "author": "Copyright (c) 2024 Engine Development Team", + "terrainPngPath": "application/lucre/models/assets/terrain/heightmap2.png" +} diff --git a/engine/core.h b/engine/core.h index e752d422..4c90b536 100644 --- a/engine/core.h +++ b/engine/core.h @@ -85,6 +85,7 @@ namespace GfxRenderEngine float GetDesktopHeight() const { return static_cast(m_Window->GetDesktopHeight()); } std::shared_ptr LoadModel(const Builder& builder) { return m_GraphicsContext->LoadModel(builder); } + std::shared_ptr LoadModel(const TerrainBuilder& builder) { return m_GraphicsContext->LoadModel(builder); } std::shared_ptr LoadModel(const GltfBuilder& builder) { return m_GraphicsContext->LoadModel(builder); } std::shared_ptr LoadModel(const FastgltfBuilder& builder) { return m_GraphicsContext->LoadModel(builder); } std::shared_ptr LoadModel(const FbxBuilder& builder) { return m_GraphicsContext->LoadModel(builder); } diff --git a/engine/platform/Vulkan/VKgraphicsContext.cpp b/engine/platform/Vulkan/VKgraphicsContext.cpp index 12a5d2c2..27631b1a 100644 --- a/engine/platform/Vulkan/VKgraphicsContext.cpp +++ b/engine/platform/Vulkan/VKgraphicsContext.cpp @@ -85,6 +85,13 @@ namespace GfxRenderEngine return model; } + std::shared_ptr VK_Context::LoadModel(const TerrainBuilder& builder) + { + ASSERT(VK_Core::m_Device != nullptr); + auto model = std::make_shared(VK_Core::m_Device, builder); + return model; + } + std::shared_ptr VK_Context::LoadModel(const GltfBuilder& builder) { ASSERT(VK_Core::m_Device != nullptr); diff --git a/engine/platform/Vulkan/VKgraphicsContext.h b/engine/platform/Vulkan/VKgraphicsContext.h index a150194c..7f18a0d8 100644 --- a/engine/platform/Vulkan/VKgraphicsContext.h +++ b/engine/platform/Vulkan/VKgraphicsContext.h @@ -47,6 +47,7 @@ namespace GfxRenderEngine virtual std::shared_ptr GetRenderer() const override { return m_Renderer; } virtual std::shared_ptr LoadModel(const Builder& builder) override; + virtual std::shared_ptr LoadModel(const TerrainBuilder& builder) override; virtual std::shared_ptr LoadModel(const GltfBuilder& builder) override; virtual std::shared_ptr LoadModel(const FastgltfBuilder& builder) override; virtual std::shared_ptr LoadModel(const FbxBuilder& builder) override; diff --git a/engine/platform/Vulkan/VKmodel.cpp b/engine/platform/Vulkan/VKmodel.cpp index 11b1b8e5..81ea671c 100644 --- a/engine/platform/Vulkan/VKmodel.cpp +++ b/engine/platform/Vulkan/VKmodel.cpp @@ -100,6 +100,15 @@ namespace GfxRenderEngine CreateIndexBuffer(std::move(builder.m_Indices)); } + VK_Model::VK_Model(std::shared_ptr device, const TerrainBuilder& builder) + : m_Device(device), m_HasIndexBuffer{false} + { + CopySubmeshes(builder.m_Submeshes); + + CreateVertexBuffer(std::move(builder.m_Vertices)); + CreateIndexBuffer(std::move(builder.m_Indices)); + } + VK_Model::~VK_Model() {} VK_Submesh::VK_Submesh(Submesh const& submesh) diff --git a/engine/platform/Vulkan/VKmodel.h b/engine/platform/Vulkan/VKmodel.h index 42e55e1f..ef7cd485 100644 --- a/engine/platform/Vulkan/VKmodel.h +++ b/engine/platform/Vulkan/VKmodel.h @@ -30,6 +30,7 @@ #include "renderer/buffer.h" #include "renderer/builder/builder.h" #include "renderer/builder/gltfBuilder.h" +#include "renderer/builder/terrainBuilder.h" #include "renderer/builder/fastgltfBuilder.h" #include "renderer/builder/ufbxBuilder.h" #include "renderer/builder/fbxBuilder.h" @@ -90,6 +91,7 @@ namespace GfxRenderEngine VK_Model(std::shared_ptr device, const FastgltfBuilder& builder); VK_Model(std::shared_ptr device, const FbxBuilder& builder); VK_Model(std::shared_ptr device, const UFbxBuilder& builder); + VK_Model(std::shared_ptr device, const TerrainBuilder& builder); virtual ~VK_Model() override; VK_Model(const VK_Model&) = delete; diff --git a/engine/renderer/builder/builder.cpp b/engine/renderer/builder/builder.cpp index ffd8f241..70fe1eac 100644 --- a/engine/renderer/builder/builder.cpp +++ b/engine/renderer/builder/builder.cpp @@ -46,186 +46,6 @@ namespace std namespace GfxRenderEngine { - void Builder::PopulateTerrainData(std::vector> const& heightMap) - { - float scale = 0.1f; // Scale for the grid spacing - float heightScale = 1.f; // Scale for the height values - size_t rows = heightMap.size(); - size_t cols = heightMap.empty() ? 0 : heightMap[0].size(); - m_Vertices.resize(rows * cols); - size_t vertexCounter = 0; - - for (size_t z = 0; z < rows; ++z) - { - for (size_t x = 0; x < cols; ++x) - { - Vertex& vertex = m_Vertices[vertexCounter]; - ++vertexCounter; - - float originY = heightMap[z][x] * heightScale; - - vertex.m_Position = glm::vec3(x * scale, originY, z * scale); - vertex.m_Color = glm::vec4(0.f, 0.f, heightMap[z][x] / 3, 1.0f); - vertex.m_UV = glm::vec2(0.f, 0.f); - vertex.m_Tangent = glm::vec3(1.0f); - vertex.m_JointIds = glm::ivec4(0); - vertex.m_Weights = glm::vec4(0.0f); - - // compute normals via neighbors - // up - // left O right - // down - glm::vec3 sumNormals(0.0f); - float leftY = x > 0 ? heightMap[z][x - 1] * heightScale : 0.0f; - float rightY = x < cols - 1 ? heightMap[z][x + 1] * heightScale : 0.0f; - float upY = z < rows - 1 ? heightMap[z + 1][x] * heightScale : 0.0f; - float downY = z > 0 ? heightMap[z - 1][x] * heightScale : 0.0f; - - float dx = scale; - float dz = scale; - - glm::vec3 left = glm::vec3(-dx, leftY - originY, 0.0f); - glm::vec3 right = glm::vec3(dx, rightY - originY, 0.0f); - glm::vec3 up = glm::vec3(0.0f, upY - originY, dz); - glm::vec3 down = glm::vec3(0.0f, downY - originY, -dz); - - auto normalComponent = [&](glm::vec3 a, glm::vec3 b) - { - glm::vec3 normal; - if (x > 0 && z > 0 && x < cols - 1 && z < rows - 1) - { - // Cross products to compute normals - normal = glm::cross(a, b); - } - else - { - normal = glm::vec3(0.0f, 1.0f, 0.0f); - } - return normal; - }; - - // smoothshading - sumNormals = normalComponent(left, -down); - sumNormals += normalComponent(-down, right); - sumNormals += normalComponent(right, -up); - sumNormals += normalComponent(-up, left); - - vertex.m_Normal = glm::normalize(sumNormals); - } - } - for (size_t z = 0; z < rows - 1; ++z) - { - for (size_t x = 0; x < cols - 1; ++x) - { - - uint32_t topLeft = z * cols + x; - - uint32_t topRight = topLeft + 1; - uint32_t bottomLeft = (z + 1) * cols + x; - uint32_t bottomRight = bottomLeft + 1; - - m_Indices.push_back(topLeft); - m_Indices.push_back(bottomLeft); - m_Indices.push_back(topRight); - m_Indices.push_back(topRight); - m_Indices.push_back(bottomLeft); - m_Indices.push_back(bottomRight); - } - } - } - entt::entity Builder::LoadTerrainHeightMapPNG(std::string const& filepath, Scene& scene) - { - m_Vertices.clear(); - m_Indices.clear(); - int width, height, bytesPerPixel; - uchar* localBuffer = stbi_load(filepath.c_str(), &width, &height, &bytesPerPixel, 0); - if (localBuffer) - { - std::vector> terrainData(height, std::vector(width)); - for (int i = 0; i < height; ++i) - { - for (int j = 0; j < width; ++j) - { - terrainData[i][j] = static_cast(localBuffer[i * width + j]) / 127.0f; - } - } - stbi_image_free(localBuffer); - - PopulateTerrainData(terrainData); - - // create game object - entt::entity entity; - std::shared_ptr instanceBuffer; - { - auto& registry = scene.GetRegistry(); - auto& sceneGraph = scene.GetSceneGraph(); - auto& dictionary = scene.GetDictionary(); - - // create game object - entity = registry.create(); - TransformComponent transform{}; - PbrMaterialTag pbrMaterialTag{}; - InstanceTag instanceTag; - - // create instance buffer - instanceTag.m_Instances.push_back(entity); - const uint numberOfInstances = 1; - const uint indexOfFirstInstance = 0; - instanceBuffer = InstanceBuffer::Create(numberOfInstances); - instanceTag.m_InstanceBuffer = instanceBuffer; - instanceTag.m_InstanceBuffer->SetInstanceData(indexOfFirstInstance, transform.GetMat4Global(), - transform.GetNormalMatrix()); - transform.SetInstance(instanceTag.m_InstanceBuffer, indexOfFirstInstance); - - // push into ECS - registry.emplace(entity, transform); - registry.emplace(entity, pbrMaterialTag); - registry.emplace(entity, instanceTag); - - // add to scene graph - uint newNode = sceneGraph.CreateNode(entity, "terrain", "terrain", dictionary); - sceneGraph.GetRoot().AddChild(newNode); - - Submesh submesh{}; - submesh.m_FirstIndex = 0; - submesh.m_FirstVertex = 0; - submesh.m_IndexCount = m_Indices.size(); - submesh.m_VertexCount = m_Vertices.size(); - submesh.m_InstanceCount = 1; - - submesh.m_Material.m_PbrMaterial.m_Roughness = 0.1f; - submesh.m_Material.m_PbrMaterial.m_Metallic = 0.9f; - submesh.m_Material.m_PbrMaterial.m_NormalMapIntensity = 1.0f; - - { // create material descriptor - Material::MaterialTextures materialTextures; - - auto materialDescriptor = MaterialDescriptor::Create(MaterialDescriptor::MtPbr, materialTextures); - submesh.m_Material.m_MaterialDescriptor = materialDescriptor; - } - { // create resource descriptor - Resources::ResourceBuffers resourceBuffers; - - resourceBuffers[Resources::INSTANCE_BUFFER_INDEX] = instanceBuffer->GetBuffer(); - auto resourceDescriptor = ResourceDescriptor::Create(resourceBuffers); - submesh.m_Resources.m_ResourceDescriptor = resourceDescriptor; - } - m_Submeshes.push_back(submesh); - auto model = Engine::m_Engine->LoadModel(*this); - MeshComponent mesh{"terrain", model}; - registry.emplace(entity, mesh); - } - - return entity; - } - else - { - LOG_CORE_CRITICAL("Builder::LoadTerrainHeightMapPNG: Couldn't load file {0}", filepath); - } - - return entt::null; - } - void Builder::LoadSprite(Sprite const& sprite, float const amplification, int const unlit, glm::vec4 const& color) { m_Vertices.clear(); diff --git a/engine/renderer/builder/builder.h b/engine/renderer/builder/builder.h index 4fd2300f..89c14f62 100644 --- a/engine/renderer/builder/builder.h +++ b/engine/renderer/builder/builder.h @@ -1,4 +1,4 @@ -/* Engine Copyright (c) 2023 Engine Development Team +/* Engine Copyright (c) 2024 Engine Development Team https://github.com/beaumanvienna/vulkan Permission is hereby granted, free of charge, to any person @@ -33,7 +33,6 @@ namespace GfxRenderEngine public: Builder() = default; - entt::entity LoadTerrainHeightMapPNG(const std::string& filepath, Scene& scene); void LoadParticle(glm::vec4 const& color); void LoadSprite(Sprite const& sprite, float const amplification = 0.0f, int const unlit = 0, glm::vec4 const& color = glm::vec4(1.0f)); @@ -41,7 +40,6 @@ namespace GfxRenderEngine private: void CalculateTangents(); - void PopulateTerrainData(std::vector> const& heightMap); void CalculateTangentsFromIndexBuffer(std::vector const& indices); public: diff --git a/engine/renderer/builder/terrainBuilder.cpp b/engine/renderer/builder/terrainBuilder.cpp new file mode 100644 index 00000000..91e4c09f --- /dev/null +++ b/engine/renderer/builder/terrainBuilder.cpp @@ -0,0 +1,314 @@ + +/* Engine Copyright (c) 2024 Engine Development Team + https://github.com/beaumanvienna/vulkan + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +#include "core.h" +#include "renderer/builder/terrainBuilder.h" +#include "scene/scene.h" +#include "stb_image.h" + +namespace GfxRenderEngine +{ + + void TerrainBuilder::PopulateTerrainData(std::vector> const& heightMap) + { + float scale = 0.1f; // Scale for the grid spacing + float heightScale = 1.f; // Scale for the height values + size_t rows = heightMap.size(); + size_t cols = heightMap.empty() ? 0 : heightMap[0].size(); + m_Vertices.resize(rows * cols); + size_t vertexCounter = 0; + + for (size_t z = 0; z < rows; ++z) + { + for (size_t x = 0; x < cols; ++x) + { + Vertex& vertex = m_Vertices[vertexCounter]; + ++vertexCounter; + + float originY = heightMap[z][x] * heightScale; + + vertex.m_Position = glm::vec3(x * scale, originY, z * scale); + vertex.m_Color = glm::vec4(0.f, 0.f, heightMap[z][x] / 3, 1.0f); + vertex.m_UV = glm::vec2(0.f, 0.f); + vertex.m_Tangent = glm::vec3(1.0f); + vertex.m_JointIds = glm::ivec4(0); + vertex.m_Weights = glm::vec4(0.0f); + + // compute normals via neighbors + // up + // left O right + // down + glm::vec3 sumNormals(0.0f); + float leftY = x > 0 ? heightMap[z][x - 1] * heightScale : 0.0f; + float rightY = x < cols - 1 ? heightMap[z][x + 1] * heightScale : 0.0f; + float upY = z < rows - 1 ? heightMap[z + 1][x] * heightScale : 0.0f; + float downY = z > 0 ? heightMap[z - 1][x] * heightScale : 0.0f; + + float dx = scale; + float dz = scale; + + glm::vec3 left = glm::vec3(-dx, leftY - originY, 0.0f); + glm::vec3 right = glm::vec3(dx, rightY - originY, 0.0f); + glm::vec3 up = glm::vec3(0.0f, upY - originY, dz); + glm::vec3 down = glm::vec3(0.0f, downY - originY, -dz); + + auto normalComponent = [&](glm::vec3 a, glm::vec3 b) + { + glm::vec3 normal; + if (x > 0 && z > 0 && x < cols - 1 && z < rows - 1) + { + // Cross products to compute normals + normal = glm::cross(a, b); + } + else + { + normal = glm::vec3(0.0f, 1.0f, 0.0f); + } + return normal; + }; + + // smoothshading + sumNormals = normalComponent(left, -down); + sumNormals += normalComponent(-down, right); + sumNormals += normalComponent(right, -up); + sumNormals += normalComponent(-up, left); + + vertex.m_Normal = glm::normalize(sumNormals); + } + } + for (size_t z = 0; z < rows - 1; ++z) + { + for (size_t x = 0; x < cols - 1; ++x) + { + + uint32_t topLeft = z * cols + x; + + uint32_t topRight = topLeft + 1; + uint32_t bottomLeft = (z + 1) * cols + x; + uint32_t bottomRight = bottomLeft + 1; + + m_Indices.push_back(topLeft); + m_Indices.push_back(bottomLeft); + m_Indices.push_back(topRight); + m_Indices.push_back(topRight); + m_Indices.push_back(bottomLeft); + m_Indices.push_back(bottomRight); + } + } + } + + bool TerrainBuilder::LoadTerrainHeightMap(std::string const& filepath, Scene& scene, int instanceCount) + { + m_Vertices.clear(); + m_Indices.clear(); + int width, height, bytesPerPixel; + uchar* localBuffer = stbi_load(filepath.c_str(), &width, &height, &bytesPerPixel, 0); + if (localBuffer) + { + std::vector> terrainData(height, std::vector(width)); + for (int i = 0; i < height; ++i) + { + for (int j = 0; j < width; ++j) + { + terrainData[i][j] = static_cast(localBuffer[i * width + j]) / 127.0f; + } + } + stbi_image_free(localBuffer); + + PopulateTerrainData(terrainData); + + // create game object + entt::entity entity; + std::shared_ptr instanceBuffer; + { + auto& registry = scene.GetRegistry(); + auto& sceneGraph = scene.GetSceneGraph(); + auto& dictionary = scene.GetDictionary(); + + // create game object + entity = registry.create(); + TransformComponent transform{}; + PbrMaterialTag pbrMaterialTag{}; + InstanceTag instanceTag; + + // create instance buffer + instanceTag.m_Instances.push_back(entity); + const uint numberOfInstances = instanceCount; + const uint indexOfFirstInstance = 0; + instanceBuffer = InstanceBuffer::Create(numberOfInstances); + instanceTag.m_InstanceBuffer = instanceBuffer; + instanceTag.m_InstanceBuffer->SetInstanceData(indexOfFirstInstance, transform.GetMat4Global(), + transform.GetNormalMatrix()); + transform.SetInstance(instanceTag.m_InstanceBuffer, indexOfFirstInstance); + + // push into ECS + registry.emplace(entity, transform); + registry.emplace(entity, pbrMaterialTag); + registry.emplace(entity, instanceTag); + + // add to scene graph + uint newNode = sceneGraph.CreateNode(entity, "terrain", "terrain", dictionary); + sceneGraph.GetRoot().AddChild(newNode); + + Submesh submesh{}; + submesh.m_FirstIndex = 0; + submesh.m_FirstVertex = 0; + submesh.m_IndexCount = m_Indices.size(); + submesh.m_VertexCount = m_Vertices.size(); + submesh.m_InstanceCount = 1; + + submesh.m_Material.m_PbrMaterial.m_Roughness = 0.1f; + submesh.m_Material.m_PbrMaterial.m_Metallic = 0.9f; + submesh.m_Material.m_PbrMaterial.m_NormalMapIntensity = 1.0f; + + { // create material descriptor + Material::MaterialTextures materialTextures; + + auto materialDescriptor = MaterialDescriptor::Create(MaterialDescriptor::MtPbr, materialTextures); + submesh.m_Material.m_MaterialDescriptor = materialDescriptor; + } + { // create resource descriptor + Resources::ResourceBuffers resourceBuffers; + + resourceBuffers[Resources::INSTANCE_BUFFER_INDEX] = instanceBuffer->GetBuffer(); + auto resourceDescriptor = ResourceDescriptor::Create(resourceBuffers); + submesh.m_Resources.m_ResourceDescriptor = resourceDescriptor; + } + m_Submeshes.push_back(submesh); + auto model = Engine::m_Engine->LoadModel(*this); + MeshComponent mesh{"terrain", model}; + registry.emplace(entity, mesh); + } + + return true; + } + else + { + LOG_CORE_CRITICAL("TerrainBuilder::LoadTerrainHeightMapPNG: Couldn't load file {0}", filepath); + } + + return false; + } + + void TerrainBuilder::CalculateTangents() + { + if (m_Indices.size()) + { + CalculateTangentsFromIndexBuffer(m_Indices); + } + else + { + uint vertexCount = m_Vertices.size(); + if (vertexCount) + { + std::vector indices; + indices.resize(vertexCount); + for (uint i = 0; i < vertexCount; i++) + { + indices[i] = i; + } + CalculateTangentsFromIndexBuffer(indices); + } + } + } + + void TerrainBuilder::CalculateTangentsFromIndexBuffer(std::vector const& indices) + { + uint cnt = 0; + uint vertexIndex1 = 0; + uint vertexIndex2 = 0; + uint vertexIndex3 = 0; + glm::vec3 position1 = glm::vec3{0.0f}; + glm::vec3 position2 = glm::vec3{0.0f}; + glm::vec3 position3 = glm::vec3{0.0f}; + glm::vec2 uv1 = glm::vec2{0.0f}; + glm::vec2 uv2 = glm::vec2{0.0f}; + glm::vec2 uv3 = glm::vec2{0.0f}; + + for (uint index : indices) + { + auto& vertex = m_Vertices[index]; + + switch (cnt) + { + case 0: + position1 = vertex.m_Position; + uv1 = vertex.m_UV; + vertexIndex1 = index; + break; + case 1: + position2 = vertex.m_Position; + uv2 = vertex.m_UV; + vertexIndex2 = index; + break; + case 2: + position3 = vertex.m_Position; + uv3 = vertex.m_UV; + vertexIndex3 = index; + + glm::vec3 edge1 = position2 - position1; + glm::vec3 edge2 = position3 - position1; + glm::vec2 deltaUV1 = uv2 - uv1; + glm::vec2 deltaUV2 = uv3 - uv1; + + float dU1 = deltaUV1.x; + float dU2 = deltaUV2.x; + float dV1 = deltaUV1.y; + float dV2 = deltaUV2.y; + float E1x = edge1.x; + float E2x = edge2.x; + float E1y = edge1.y; + float E2y = edge2.y; + float E1z = edge1.z; + float E2z = edge2.z; + + float factor; + if ((dU1 * dV2 - dU2 * dV1) > std::numeric_limits::epsilon()) + { + factor = 1.0f / (dU1 * dV2 - dU2 * dV1); + } + else + { + factor = 100000.0f; + } + + glm::vec3 tangent; + + tangent.x = factor * (dV2 * E1x - dV1 * E2x); + tangent.y = factor * (dV2 * E1y - dV1 * E2y); + tangent.z = factor * (dV2 * E1z - dV1 * E2z); + if (tangent.x == 0.0f && tangent.y == 0.0f && tangent.z == 0.0f) + { + tangent = glm::vec3(1.0f, 0.0f, 0.0f); + } + + m_Vertices[vertexIndex1].m_Tangent = tangent; + m_Vertices[vertexIndex2].m_Tangent = tangent; + m_Vertices[vertexIndex3].m_Tangent = tangent; + + break; + } + cnt = (cnt + 1) % 3; + } + } +} // namespace GfxRenderEngine diff --git a/engine/renderer/builder/terrainBuilder.h b/engine/renderer/builder/terrainBuilder.h new file mode 100644 index 00000000..9d3b8a1b --- /dev/null +++ b/engine/renderer/builder/terrainBuilder.h @@ -0,0 +1,49 @@ +/* Engine Copyright (c) 2024 Engine Development Team + https://github.com/beaumanvienna/vulkan + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +#pragma once + +#include "renderer/model.h" +#include "scene/scene.h" + +namespace GfxRenderEngine +{ + class TerrainBuilder + { + + public: + TerrainBuilder() = default; + + bool LoadTerrainHeightMap(const std::string& filepath, Scene& scene, int instanceCount); + + private: + void PopulateTerrainData(std::vector> const& heightMap); + + void CalculateTangents(); + void CalculateTangentsFromIndexBuffer(std::vector const& indices); + + public: + std::vector m_Indices{}; + std::vector m_Vertices{}; + std::vector m_Submeshes{}; + }; +} // namespace GfxRenderEngine diff --git a/engine/renderer/gltf.h b/engine/renderer/gltf.h index 8900e3ce..7ad93fb3 100644 --- a/engine/renderer/gltf.h +++ b/engine/renderer/gltf.h @@ -1,4 +1,4 @@ -/* Engine Copyright (c) 2023 Engine Development Team +/* Engine Copyright (c) 2024 Engine Development Team https://github.com/beaumanvienna/vulkan Permission is hereby granted, free of charge, to any person diff --git a/engine/renderer/graphicsContext.h b/engine/renderer/graphicsContext.h index 2050f75f..cadd2e43 100644 --- a/engine/renderer/graphicsContext.h +++ b/engine/renderer/graphicsContext.h @@ -31,6 +31,7 @@ #include "renderer/renderer.h" #include "renderer/builder/builder.h" #include "renderer/builder/gltfBuilder.h" +#include "renderer/builder/terrainBuilder.h" #include "renderer/builder/fastgltfBuilder.h" #include "renderer/builder/ufbxBuilder.h" #include "renderer/builder/fbxBuilder.h" @@ -50,6 +51,7 @@ namespace GfxRenderEngine virtual std::shared_ptr GetRenderer() const = 0; virtual std::shared_ptr LoadModel(const Builder& builder) = 0; + virtual std::shared_ptr LoadModel(const TerrainBuilder& builder) = 0; virtual std::shared_ptr LoadModel(const GltfBuilder& builder) = 0; virtual std::shared_ptr LoadModel(const FastgltfBuilder& builder) = 0; virtual std::shared_ptr LoadModel(const FbxBuilder& builder) = 0; diff --git a/engine/scene/sceneLoaderDeserializeJSON.cpp b/engine/scene/sceneLoaderDeserializeJSON.cpp index b54bc0dc..dc851b6c 100644 --- a/engine/scene/sceneLoaderDeserializeJSON.cpp +++ b/engine/scene/sceneLoaderDeserializeJSON.cpp @@ -23,6 +23,7 @@ #include "auxiliary/file.h" #include "core.h" #include "scene/sceneLoaderJSON.h" +#include "scene/terrainLoaderJSON.h" namespace GfxRenderEngine { @@ -65,18 +66,31 @@ namespace GfxRenderEngine CORE_ASSERT((sceneObject.value().type() == ondemand::json_type::number), "type must be number"); // only check major version of "file format identifier" - // todo: Ask to JC: is the format identifier 1.3 or 1.2.1? m_SceneDescriptionFile.m_FileFormatIdentifier = sceneObject.value().get_double(); CORE_ASSERT( (std::trunc(m_SceneDescriptionFile.m_FileFormatIdentifier) == std::trunc(SUPPORTED_FILE_FORMAT_VERSION)), "The scene description major version does not match"); } - else if (sceneObjectKey == "terrainPngPath") + else if (sceneObjectKey == "terrain") { - CORE_ASSERT((sceneObject.value().type() == ondemand::json_type::string), "Terrain path must be string"); - std::string_view terrainPath = sceneObject.value().get_string(); - m_SceneDescriptionFile.m_TerrainPngPath = std::string(terrainPath); - LOG_CORE_INFO("Terrain PNG Path: {0}", m_SceneDescriptionFile.m_TerrainPngPath); + CORE_ASSERT((sceneObject.value().type() == ondemand::json_type::array), "type must be array"); + ondemand::array terrainDescriptions = sceneObject.value().get_array(); + { + int count = terrainDescriptions.count_elements(); + if (count == 1) + { + LOG_CORE_INFO("loading 1 terrain"); + } + else + { + LOG_CORE_INFO("loading {0} terrain descriptions", count); + } + } + + for (auto terrainDescription : terrainDescriptions) + { + ParseTerrainDescription(terrainDescription, m_SceneDescriptionFile.m_TerrainDescriptions); + } } else if (sceneObjectKey == "description") @@ -532,4 +546,96 @@ namespace GfxRenderEngine } return returnVec3; } + + void SceneLoaderJSON::ParseTerrainDescription(ondemand::object terrainDescription, + std::vector& terrainDescriptions) + { + std::string filename; + + for (auto terrainDescriptionObject : terrainDescription) + { + std::string_view terrainDescriptionObjectKey = terrainDescriptionObject.unescaped_key(); + + if (terrainDescriptionObjectKey == "filename") + { + std::string_view filenameStringView = terrainDescriptionObject.value().get_string(); + filename = std::string(filenameStringView); + if (EngineCore::FileExists(filename)) + { + LOG_CORE_INFO("Scene loader found {0}", filename); + } + else + { + LOG_CORE_CRITICAL("terrain description not found: {0}", filename); + return; + } + } + else if (terrainDescriptionObjectKey == "instances") + { + // get array of terrain instances + ondemand::array instances = terrainDescriptionObject.value(); + int instanceCount = instances.count_elements(); + + if (!instanceCount) + { + LOG_CORE_ERROR("no instances found (json file broken): {0}", filename); + return; + } + + TerrainLoaderJSON terrainLoaderJSON(m_Scene); + bool loadSuccessful = terrainLoaderJSON.Deserialize(filename, instanceCount); + + if (!loadSuccessful) + { + Terrain::TerrainDescription terrainDescriptionScene(filename); + terrainDescriptions.push_back(terrainDescriptionScene); + } + else + { + LOG_CORE_CRITICAL("terrain description did not load properly: {0}", filename); + return; + } + + std::vector& terrainInstances = terrainDescriptions.back().m_Instances; + terrainInstances.resize(instanceCount); + + { + uint instanceIndex = 0; + for (auto instance : instances) + { + std::string fullEntityName = filename + std::string("::" + std::to_string(instanceIndex)); + entt::entity entity = m_Scene.m_Dictionary.Retrieve(fullEntityName); + Terrain::Instance& terrainInstance = terrainInstances[instanceIndex]; + terrainInstance.m_Entity = entity; + ondemand::object instanceObjects = instance.value(); + for (auto instanceObject : instanceObjects) + { + std::string_view instanceObjectKey = instanceObject.unescaped_key(); + + if (instanceObjectKey == "transform") + { + CORE_ASSERT((instanceObject.value().type() == ondemand::json_type::object), + "type must be object"); + ParseTransform(instanceObject.value(), entity); + } + else + { + LOG_CORE_CRITICAL("unrecognized terrain instance object"); + } + } + ++instanceIndex; + } + } + } + else + { + LOG_CORE_CRITICAL("unrecognized terrain description object"); + } + } + } + + std::vector& SceneLoaderJSON::GetTerrainDescriptions() + { + return m_SceneDescriptionFile.m_TerrainDescriptions; + } } // namespace GfxRenderEngine diff --git a/engine/scene/sceneLoaderJSON.h b/engine/scene/sceneLoaderJSON.h index 40785c95..cdd6650d 100644 --- a/engine/scene/sceneLoaderJSON.h +++ b/engine/scene/sceneLoaderJSON.h @@ -33,6 +33,7 @@ using namespace simdjson; #include "renderer/gltf.h" #include "renderer/obj.h" #include "scene/scene.h" +#include "scene/terrain.h" namespace GfxRenderEngine { @@ -46,7 +47,7 @@ namespace GfxRenderEngine void Deserialize(std::string& filepath, std::string& alternativeFilepath); void Serialize(); Gltf::GltfFiles& GetGltfFiles() { return m_SceneDescriptionFile.m_GltfFiles; } - std::string& GetTerrainPath() { return m_SceneDescriptionFile.m_TerrainPngPath; }; + std::vector& GetTerrainDescriptions(); Gltf::GltfFiles& GetFastgltfFiles() { return m_SceneDescriptionFile.m_FastgltfFiles; } private: @@ -56,8 +57,9 @@ namespace GfxRenderEngine double m_FileFormatIdentifier; std::string m_Description; std::string m_Author; + // assets Gltf::GltfFiles m_GltfFiles; - std::string m_TerrainPngPath; + std::vector m_TerrainDescriptions; Gltf::GltfFiles m_FastgltfFiles; Fbx::FbxFiles m_FbxFiles; Fbx::FbxFiles m_UFbxFiles; @@ -69,10 +71,12 @@ namespace GfxRenderEngine void ParseGltfFile(ondemand::object gltfFileJSON, bool fast, std::vector& gltfFilesFromScene); void ParseFbxFile(ondemand::object fbxFileJSON, bool ufbx); - void ParseObjFile(ondemand::object objFileJSON); + void ParseTransform(ondemand::object transformJSON, entt::entity entity); void ParseNodesGltf(ondemand::array nodesJSON, std::string const& gltfFilename, Gltf::Instance& gltfFileInstance, uint instanceIndex); + void ParseTerrainDescription(ondemand::object terrainDescription, + std::vector& terrainDescriptions); glm::vec3 ConvertToVec3(ondemand::array arrayJSON); diff --git a/engine/scene/terrain.h b/engine/scene/terrain.h new file mode 100644 index 00000000..e3f99308 --- /dev/null +++ b/engine/scene/terrain.h @@ -0,0 +1,51 @@ +/* Engine Copyright (c) 2024 Engine Development Team + https://github.com/beaumanvienna/vulkan + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +#pragma once + +#include "entt.hpp" + +#include "engine.h" + +namespace GfxRenderEngine +{ + namespace Terrain + { + + struct Instance + { + entt::entity m_Entity; + + Instance() = default; + Instance(entt::entity entity) : m_Entity{entity} {} + }; + + struct TerrainDescription + { + std::string m_Filename; + std::vector m_Instances; + + TerrainDescription(std::string const& filename) : m_Filename{filename} {} + }; + + } // namespace Terrain +} // namespace GfxRenderEngine \ No newline at end of file diff --git a/engine/scene/terrainLoaderDeserializeJSON.cpp b/engine/scene/terrainLoaderDeserializeJSON.cpp new file mode 100644 index 00000000..383974cc --- /dev/null +++ b/engine/scene/terrainLoaderDeserializeJSON.cpp @@ -0,0 +1,94 @@ +/* Engine Copyright (c) 2024 Engine Development Team + https://github.com/beaumanvienna/vulkan + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +#include "auxiliary/file.h" +#include "core.h" +#include "scene/terrainLoaderJSON.h" + +namespace GfxRenderEngine +{ + + TerrainLoaderJSON::TerrainLoaderJSON(Scene& scene) : m_Scene(scene) {} + + bool TerrainLoaderJSON::Deserialize(std::string& filepath, int instanceCount) + { + if (!EngineCore::FileExists(filepath)) + { + LOG_CORE_CRITICAL("TerrainLoaderJSON: could not find file {0}", filepath); + return false; + } + + LOG_CORE_INFO("TerrainLoaderJSON: loading {0}", filepath); + + ondemand::parser parser; + padded_string json = padded_string::load(filepath); + + ondemand::document terrainDocument = parser.iterate(json); + ondemand::object terrainAttributess = terrainDocument.get_object(); + + for (auto terrainAttributes : terrainAttributess) + { + std::string_view terrainAttributesKey = terrainAttributes.unescaped_key(); + + if (terrainAttributesKey == "file format identifier") + { + CORE_ASSERT((terrainAttributes.value().type() == ondemand::json_type::number), "type must be number"); + + // only check major version of "file format identifier" + m_TerrainDescriptionFile.m_FileFormatIdentifier = terrainAttributes.value().get_double(); + CORE_ASSERT((std::trunc(m_TerrainDescriptionFile.m_FileFormatIdentifier) == + std::trunc(SUPPORTED_FILE_FORMAT_VERSION)), + "The terrain description major version does not match"); + } + else if (terrainAttributesKey == "terrainPngPath") + { + CORE_ASSERT((terrainAttributes.value().type() == ondemand::json_type::string), + "Terrain path must be string"); + std::string_view terrainPath = terrainAttributes.value().get_string(); + m_TerrainDescriptionFile.m_TerrainPngPath = std::string(terrainPath); + LOG_CORE_INFO("Terrain PNG Path: {0}", m_TerrainDescriptionFile.m_TerrainPngPath); + } + else if (terrainAttributesKey == "description") + { + CORE_ASSERT((terrainAttributes.value().type() == ondemand::json_type::string), "type must be string"); + std::string_view terrainDescription = terrainAttributes.value().get_string(); + m_TerrainDescriptionFile.m_Description = std::string(terrainDescription); + LOG_CORE_INFO("description: {0}", m_TerrainDescriptionFile.m_Description); + } + else if (terrainAttributesKey == "author") + { + CORE_ASSERT((terrainAttributes.value().type() == ondemand::json_type::string), "type must be string"); + std::string_view terrainAuthor = terrainAttributes.value().get_string(); + m_TerrainDescriptionFile.m_Author = std::string(terrainAuthor); + LOG_CORE_INFO("author: {0}", m_TerrainDescriptionFile.m_Author); + } + else + { + LOG_CORE_CRITICAL("unrecognized terrain object '" + std::string(terrainAttributesKey) + "'"); + } + } + + TerrainBuilder builder{}; + return builder.LoadTerrainHeightMap(m_TerrainDescriptionFile.m_TerrainPngPath, m_Scene, instanceCount); + } + +} // namespace GfxRenderEngine diff --git a/engine/scene/terrainLoaderJSON.h b/engine/scene/terrainLoaderJSON.h new file mode 100644 index 00000000..85480884 --- /dev/null +++ b/engine/scene/terrainLoaderJSON.h @@ -0,0 +1,65 @@ +/* Engine Copyright (c) 2024 Engine Development Team + https://github.com/beaumanvienna/vulkan + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +#pragma once + +#include "simdjson.h" +#include +#include + +using namespace simdjson; + +#include "engine.h" +#include "renderer/fbx.h" +#include "renderer/gltf.h" +#include "renderer/obj.h" +#include "scene/scene.h" + +namespace GfxRenderEngine +{ + class TerrainLoaderJSON + { + + public: + TerrainLoaderJSON(Scene& scene); + ~TerrainLoaderJSON() {} + + bool Deserialize(std::string& filepath, int instanceCount); + + private: + struct TerrainDescriptionFile + { + // JSON file attributes here + double m_FileFormatIdentifier; + std::string m_Description; + std::string m_Author; + std::string m_TerrainPngPath; + }; + + private: + static constexpr double SUPPORTED_FILE_FORMAT_VERSION = 1.2; + + Scene& m_Scene; + + TerrainDescriptionFile m_TerrainDescriptionFile; + }; +} // namespace GfxRenderEngine