diff --git a/src/entity/components/component-type.hpp b/src/entity/components/component-type.hpp
index 910659d..55bec30 100644
--- a/src/entity/components/component-type.hpp
+++ b/src/entity/components/component-type.hpp
@@ -31,6 +31,7 @@ enum class ComponentType
MODEL,
STEERING,
SOUND_SOURCE,
+ TERRAIN_PATCH,
TOOL,
TRANSFORM
};
diff --git a/src/entity/components/terrain-patch-component.cpp b/src/entity/components/terrain-patch-component.cpp
new file mode 100644
index 0000000..645c660
--- /dev/null
+++ b/src/entity/components/terrain-patch-component.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017-2019 Christopher J. Howard
+ *
+ * This file is part of Antkeeper Source Code.
+ *
+ * Antkeeper Source Code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Antkeeper Source Code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Antkeeper Source Code. If not, see .
+ */
+
+#include "terrain-patch-component.hpp"
+
+ComponentBase* TerrainPatchComponent::clone() const
+{
+ TerrainPatchComponent* component = new TerrainPatchComponent();
+ component->position = position;
+
+ return component;
+}
+
diff --git a/src/entity/components/terrain-patch-component.hpp b/src/entity/components/terrain-patch-component.hpp
new file mode 100644
index 0000000..f4023be
--- /dev/null
+++ b/src/entity/components/terrain-patch-component.hpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017-2019 Christopher J. Howard
+ *
+ * This file is part of Antkeeper Source Code.
+ *
+ * Antkeeper Source Code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Antkeeper Source Code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Antkeeper Source Code. If not, see .
+ */
+
+#ifndef TERRAIN_PATCH_COMPONENT_HPP
+#define TERRAIN_PATCH_COMPONENT_HPP
+
+#include "../component.hpp"
+#include "component-type.hpp"
+#include
+
+class TerrainPatchComponent: public Component
+{
+public:
+ virtual ComponentBase* clone() const;
+
+ // Position in integer terrain coordinates
+ std::tuple position;
+};
+
+#endif // TERRAIN_PATCH_COMPONENT_HPP
+
diff --git a/src/entity/systems/terrain-system.cpp b/src/entity/systems/terrain-system.cpp
new file mode 100644
index 0000000..6540779
--- /dev/null
+++ b/src/entity/systems/terrain-system.cpp
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2017-2019 Christopher J. Howard
+ *
+ * This file is part of Antkeeper Source Code.
+ *
+ * Antkeeper Source Code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Antkeeper Source Code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Antkeeper Source Code. If not, see .
+ */
+
+#include "terrain-system.hpp"
+#include "graphics/vertex-format.hpp"
+
+TerrainSystem::TerrainSystem(ComponentManager* componentManager):
+ System(componentManager),
+ terrainCreationGroup(componentManager),
+ terrainGroup(componentManager)
+{
+ terrainCreationGroup.addGroupObserver(this);
+ terrainGroup.addGroupObserver(this);
+
+ patchSize = 100.0f;
+}
+
+TerrainSystem::~TerrainSystem()
+{}
+
+void TerrainSystem::update(float t, float dt)
+{
+
+ auto members = terrainGroup.getMembers();
+ for (const TerrainGroup::Member* member: *members)
+ {
+ ModelComponent* model = std::get<0>(member->components);
+ TerrainPatchComponent* patch = std::get<1>(member->components);
+ TransformComponent* transform = std::get<2>(member->components);
+ }
+}
+
+void TerrainSystem::memberRegistered(const TerrainCreationGroup::Member* member)
+{
+ TerrainPatchComponent* patch = std::get<0>(member->components);
+ TransformComponent* transform = std::get<1>(member->components);
+
+ // Generate a subdivided plane mesh
+ TriangleMesh* patchMesh = generatePlane(5);
+
+ // Generate a model from the subdivided plane
+ Model* patchModel = generateModel(patchMesh);
+
+ // Add model component to the entity
+ ModelComponent* model = new ModelComponent();
+ model->model.setModel(patchModel);
+ componentManager->addComponent(member->entity, model);
+
+ // Set scale of the transform component
+ transform->transform.scale = Vector3(patchSize);
+ transform->transform.translation = Vector3(std::get<0>(patch->position), 0.0f, std::get<1>(patch->position)) * patchSize;
+}
+
+void TerrainSystem::memberUnregistered(const TerrainCreationGroup::Member* member)
+{}
+
+void TerrainSystem::memberRegistered(const TerrainGroup::Member* member)
+{
+ // Add terrain patch to the patch map
+ TerrainPatchComponent* patch = std::get<1>(member->components);
+ patchMap[patch->position] = member;
+}
+
+void TerrainSystem::memberUnregistered(const TerrainGroup::Member* member)
+{
+ // Remove terrain patch from the patch map
+ TerrainPatchComponent* patch = std::get<1>(member->components);
+ auto it = patchMap.find(patch->position);
+ if (it != patchMap.end())
+ {
+ patchMap.erase(it);
+ }
+
+ // TODO: free created terrain patch model
+}
+
+TriangleMesh* TerrainSystem::generatePlane(int subdivisions)
+{
+ //std::size_t quadCount = std::pow(4, subdivisions);
+ //std::size_t triangleCount = quadCount * 2;
+ std::size_t columns = std::pow(2, subdivisions);
+ std::size_t rows = columns;
+ std::size_t vertexCount = (columns + 1) * (rows + 1);
+ float vertexIncrement = 1.0f / static_cast(columns);
+
+ // Generate vertices
+ std::vector vertices;
+ Vector3 position(0.0f);
+ position.z = -0.5f;
+ for (std::size_t i = 0; i <= rows; ++i)
+ {
+ position.x = -0.5f;
+ for (std::size_t j = 0; j <= columns; ++j)
+ {
+ vertices.push_back(position);
+ position.x += vertexIncrement;
+
+ }
+
+ position.z += vertexIncrement;
+ }
+
+ // Generate indices
+ std::vector indices;
+ for (std::size_t i = 0; i < rows; ++i)
+ {
+ for (std::size_t j = 0; j < columns; ++j)
+ {
+ unsigned int a = i * (columns + 1) + j;
+ unsigned int b = (i + 1) * (columns + 1) + j;
+ unsigned int c = i * (columns + 1) + j + 1;
+ unsigned int d = (i + 1) * (columns + 1) + j + 1;
+
+ indices.push_back(a);
+ indices.push_back(b);
+ indices.push_back(c);
+ indices.push_back(c);
+ indices.push_back(b);
+ indices.push_back(d);
+ }
+ }
+
+ return new TriangleMesh(vertices, indices);
+
+ /*
+ // Generate navmesh
+ surfaceNavmesh.create(surfaceVertices, surfaceIndices);
+
+ // Calculate vertex normals
+ calculateSurfaceNormals();
+
+ // Create and load VAO, VBO, and IBO
+ glGenVertexArrays(1, &surfaceVAO);
+ glBindVertexArray(surfaceVAO);
+ glGenBuffers(1, &surfaceVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, surfaceVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float) * surfaceVertexSize * surfaceVertexCount, surfaceVertexData, GL_STATIC_DRAW);
+ glEnableVertexAttribArray(EMERGENT_VERTEX_POSITION);
+ glVertexAttribPointer(EMERGENT_VERTEX_POSITION, 3, GL_FLOAT, GL_FALSE, surfaceVertexSize * sizeof(float), (char*)0 + 0 * sizeof(float));
+ glEnableVertexAttribArray(EMERGENT_VERTEX_NORMAL);
+ glVertexAttribPointer(EMERGENT_VERTEX_NORMAL, 3, GL_FLOAT, GL_FALSE, surfaceVertexSize * sizeof(float), (char*)0 + 3 * sizeof(float));
+ glEnableVertexAttribArray(EMERGENT_VERTEX_TEXCOORD);
+ glVertexAttribPointer(EMERGENT_VERTEX_TEXCOORD, 2, GL_FLOAT, GL_FALSE, surfaceVertexSize * sizeof(float), (char*)0 + 6 * sizeof(float));
+ glGenBuffers(1, &surfaceIBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, surfaceIBO);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(std::uint32_t) * surfaceIndexCount, surfaceIndexData, GL_STATIC_DRAW);
+
+ // Setup material
+ //surfaceMaterial.flags = static_cast(PhysicalMaterial::Flags::OBJECT);
+
+ // Setup buffers
+ surfaceModel.setVAO(surfaceVAO);
+ surfaceModel.setVBO(surfaceVBO);
+ surfaceModel.setIBO(surfaceIBO);
+
+ // Create model group
+ Model::Group* group = new Model::Group();
+ group->name = "default";
+ group->material = nullptr;//&surfaceMaterial;
+ group->indexOffset = 0;
+ group->triangleCount = surfaceTriangleCount;
+
+ // Add group to the model
+ surfaceModel.addGroup(group);
+
+ // Set model bounds
+ surfaceModel.setBounds(surfaceNavmesh.getBounds());
+ */
+}
+
+Model* TerrainSystem::generateModel(TriangleMesh* mesh)
+{
+ std::size_t triangleCount = mesh->getTriangles()->size();
+ // Vertex position + vertex normal
+ std::size_t vertexSize = 3 + 3;
+ std::size_t vertexCount = triangleCount * 3;
+
+ #if defined(DEBUG)
+ const Vector3 barycentricCoordinates[3] =
+ {
+ Vector3(1, 0, 0),
+ Vector3(0, 1, 0),
+ Vector3(0, 0, 1)
+ };
+
+ vertexSize += 3;
+ #endif // DEBUG
+
+ // Generate vertex data
+ float* vertexData = new float[vertexSize * vertexCount];
+ float* v = vertexData;
+ for (std::size_t i = 0; i < triangleCount; ++i)
+ {
+ const TriangleMesh::Triangle* triangle = (*mesh->getTriangles())[i];
+
+ const TriangleMesh::Vertex* a = triangle->edge->vertex;
+ const TriangleMesh::Vertex* b = triangle->edge->next->vertex;
+ const TriangleMesh::Vertex* c = triangle->edge->previous->vertex;
+ const TriangleMesh::Vertex* abc[] = {a, b, c};
+ const Vector3& normal = triangle->normal;
+
+
+ for (std::size_t j = 0; j < 3; ++j)
+ {
+ *(v++) = abc[j]->position[0];
+ *(v++) = abc[j]->position[1];
+ *(v++) = abc[j]->position[2];
+ *(v++) = normal.x;
+ *(v++) = normal.y;
+ *(v++) = normal.z;
+
+ #if defined(DEBUG)
+ {
+ *(v++) = barycentricCoordinates[j].x;
+ *(v++) = barycentricCoordinates[j].y;
+ *(v++) = barycentricCoordinates[j].z;
+ }
+ #endif // DEBUG
+ }
+
+ // Calculate smoothed vertex normal
+ /*
+ Vector3 normal(0.0f);
+ TriangleMesh::Edge* start = vertex->edge;
+ TriangleMesh::Edge* e = start;
+ do
+ {
+ normal += e->triangle->normal;
+ e = e->previous->symmetric;
+ }
+ while (e != start && e != nullptr);
+ normal = glm::normalize(normal);
+ */
+ }
+
+ // Generate index data
+ std::size_t indexCount = triangleCount * 3;
+ std::uint32_t* indexData = new std::uint32_t[indexCount];
+ std::uint32_t* index = indexData;
+ for (std::size_t i = 0; i < triangleCount; ++i)
+ {
+ *(index++) = i * 3;
+ *(index++) = i * 3 + 1;
+ *(index++) = i * 3 + 2;
+ }
+
+ // Calculate AABB bounds
+ AABB bounds;
+ bounds.setMin(mesh->getVertices()->front()->position);
+ bounds.setMax(mesh->getVertices()->front()->position);
+ for (TriangleMesh::Vertex* vertex: *mesh->getVertices())
+ {
+ bounds.add(vertex->position);
+ }
+
+ GLuint vao;
+ GLuint vbo;
+ GLuint ibo;
+
+ // Generate and bind VAO
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ // Generate and bind VBO, then upload vertex data
+ glGenBuffers(1, &vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float) * vertexSize * vertexCount, vertexData, GL_STATIC_DRAW);
+
+ // Setup vertex attribute arrays
+ std::size_t attribOffset = 0;
+ std::size_t attribSize = 0;
+
+ // Vertex position attribute
+ attribSize = 3;
+ glEnableVertexAttribArray(VERTEX_POSITION);
+ glVertexAttribPointer(VERTEX_POSITION, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float));
+ attribOffset += attribSize;
+
+ // Vertex normal attribute
+ attribSize = 3;
+ glEnableVertexAttribArray(VERTEX_NORMAL);
+ glVertexAttribPointer(VERTEX_NORMAL, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float));
+ attribOffset += attribSize;
+
+ #if defined(DEBUG)
+ {
+ // Vertex barycentric coordinates attribute
+ attribSize = 3;
+ glEnableVertexAttribArray(VERTEX_BARYCENTRIC);
+ glVertexAttribPointer(VERTEX_BARYCENTRIC, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float));
+ attribOffset += attribSize;
+ }
+ #endif // DEBUG
+
+ // Generate and bind IBO, then upload index data
+ glGenBuffers(1, &ibo);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(std::uint32_t) * indexCount, indexData, GL_STATIC_DRAW);
+
+ // Delete vertex and index data
+ delete[] vertexData;
+ delete[] indexData;
+
+ // Create model
+ Model* model = new Model();
+
+ // Set model buffers
+ model->setVAO(vao);
+ model->setVBO(vbo);
+ model->setIBO(ibo);
+
+ // Set model bounds
+ model->setBounds(bounds);
+
+ // Create model material
+ Material* material = new Material();
+ material->setShader(nullptr);
+ ShaderVariable* albedo = material->addVariable("albedo");
+ ShaderVariable* roughness = material->addVariable("roughness");
+ ShaderVariable* metalness = material->addVariable("metalness");
+ ShaderVariable* opacity = material->addVariable("opacity");
+ albedo->setValue(Vector3(0.8f));
+ roughness->setValue(0.5f);
+ metalness->setValue(0.0f);
+ opacity->setValue(1.0f);
+
+ // Create model group
+ Model::Group* group = new Model::Group();
+ group->name = std::string();
+ group->material = material;
+ group->indexOffset = 0;
+ group->triangleCount = triangleCount;
+
+ // Add model group to the model
+ model->addGroup(group);
+
+ return model;
+}
+
+void TerrainSystem::projectMesh(TriangleMesh* mesh)
+{
+
+}
+
diff --git a/src/entity/systems/terrain-system.hpp b/src/entity/systems/terrain-system.hpp
new file mode 100644
index 0000000..211da3e
--- /dev/null
+++ b/src/entity/systems/terrain-system.hpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017-2019 Christopher J. Howard
+ *
+ * This file is part of Antkeeper Source Code.
+ *
+ * Antkeeper Source Code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Antkeeper Source Code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Antkeeper Source Code. If not, see .
+ */
+
+#ifndef TERRAIN_SYSTEM_HPP
+#define TERRAIN_SYSTEM_HPP
+
+#include "../entity-group.hpp"
+#include "../components/model-component.hpp"
+#include "../components/terrain-patch-component.hpp"
+#include "../components/transform-component.hpp"
+#include "../system.hpp"
+#include