Antkeeper  0.0.1
terrain-system.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2023 Christopher J. Howard
3  *
4  * This file is part of Antkeeper source code.
5  *
6  * Antkeeper source code is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Antkeeper source code is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
24 #include <engine/debug/log.hpp>
33 #include <algorithm>
34 #include <execution>
35 #include <stdexcept>
36 
39 {}
40 
42 {}
43 
44 void terrain_system::update(float t, float dt)
45 {
46 }
47 
48 entity::id terrain_system::generate(std::shared_ptr<gl::image_2d> heightmap, const math::uvec2& subdivisions, const math::transform<float>& transform, std::shared_ptr<render::material> material)
49 {
50  if (!heightmap)
51  {
52  debug::log_error("Failed to generate terrain from null heightmap");
53  throw std::invalid_argument("Failed to generate terrain from null heightmap");
54  }
55 
56  const auto& heightmap_dimensions = heightmap->get_dimensions();
57  if (heightmap_dimensions[0] < 2 || heightmap_dimensions[1] < 2)
58  {
59  debug::log_error("Heightmap size less than 2x2");
60  throw std::runtime_error("Heightmap size less than 2x2");
61  }
62 
63  if (((heightmap_dimensions[0] - 1) % (subdivisions.x() + 1)) != 0 ||
64  ((heightmap_dimensions[1] - 1) % (subdivisions.y() + 1)) != 0)
65  {
66  debug::log_error("{}x{} heightmap cannot be subdivided {}x{} times",
67  heightmap_dimensions[0],
68  heightmap_dimensions[1],
69  subdivisions.x(),
70  subdivisions.y());
71  throw std::runtime_error("Heightmap subdivision failed");
72  }
73 
74  // Generate terrain grid
76  grid.dimensions = subdivisions + 1u;
77  grid.cells.resize(grid.dimensions.x() * grid.dimensions.y());
78  auto grid_eid = registry.create();
79  for (auto y = 0u; y < grid.dimensions.y(); ++y)
80  {
81  for (auto x = 0u; x < grid.dimensions.x(); ++x)
82  {
83  auto cell_eid = registry.create();
84  registry.emplace<terrain_cell_component>(cell_eid, grid_eid, math::uvec2{x, y});
85  grid.cells[y * grid.dimensions.x() + x] = cell_eid;
86  }
87  }
88 
89  // Calculate cell dimensions
90  const auto cell_quad_dimensions = math::uvec2{static_cast<unsigned int>(heightmap_dimensions[0] - 1) / grid.dimensions.x(), static_cast<unsigned int>(heightmap_dimensions[1] - 1) / grid.dimensions.y()};
91  const auto cell_vert_dimensions = cell_quad_dimensions + 1u;
92 
93  const auto max_scale = math::max(transform.scale);
94  const auto scale_ratio = transform.scale / max_scale;
95  const auto vertex_scale = scale_ratio * math::fvec3{2.0f / static_cast<float>(cell_quad_dimensions.x()), 2.0f, 2.0f / static_cast<float>(cell_quad_dimensions.y())};
96  const auto vertex_translation = -scale_ratio;
97 
98  // Allocate heightmap data buffer and select heightmap sampling function according to image format
99  std::vector<float> heightmap_data(heightmap_dimensions[0] * heightmap_dimensions[1]);
100  std::function<float(const math::uvec2&)> sample = [&](const math::uvec2& p)
101  {
102  return heightmap_data[p.y() * heightmap_dimensions[0] + p.x()];
103  };
104 
105  // Read heightmap pixel data into buffer
106  heightmap->read
107  (
108  0,
109  0,
110  0,
111  0,
112  heightmap_dimensions[0],
113  heightmap_dimensions[1],
114  1,
116  std::as_writable_bytes(std::span{heightmap_data})
117  );
118 
119  // Generate terrain cell meshes
120  std::for_each
121  (
122  std::execution::seq,
123  std::begin(grid.cells),
124  std::end(grid.cells),
125  [&](auto cell_eid)
126  {
127  const auto& cell = registry.get<terrain_cell_component>(cell_eid);
128 
129  // Allocate cell mesh and attributes
130  auto mesh = std::make_shared<geom::brep_mesh>();
131  auto& vertex_positions = static_cast<geom::brep_attribute<math::fvec3>&>(*mesh->vertices().attributes().emplace<math::fvec3>("position"));
132 
133  auto cell_pixel_bounds_min = cell.coordinates * cell_quad_dimensions;
134  auto cell_pixel_bounds_max = cell_pixel_bounds_min + cell_quad_dimensions;
135 
136  // Build cell vertices
137  math::uvec2 pixel_position;
138  for (pixel_position.y() = cell_pixel_bounds_min.y(); pixel_position.y() <= cell_pixel_bounds_max.y(); ++pixel_position.y())
139  {
140  for (pixel_position.x() = cell_pixel_bounds_min.x(); pixel_position.x() <= cell_pixel_bounds_max.x(); ++pixel_position.x())
141  {
142  // Allocate vertex
143  auto vertex = mesh->vertices().emplace_back();
144 
145  // Get vertex height from heightmap
146  float height = sample(pixel_position);
147 
148  // Set vertex position
149  auto& position = vertex_positions[vertex->index()];
150  position.x() = static_cast<float>(pixel_position.x()) * vertex_scale.x() + vertex_translation.x();
151  position.y() = height * vertex_scale.y() + vertex_translation.y();
152  position.z() = static_cast<float>(pixel_position.y()) * vertex_scale.z() + vertex_translation.z();
153  }
154  }
155 
156  // Build cell faces
157  for (auto y = 0u; y < cell_quad_dimensions.y(); ++y)
158  {
159  for (auto x = 0u; x < cell_quad_dimensions.x(); ++x)
160  {
161  auto a = mesh->vertices()[y * cell_vert_dimensions.x() + x];
162  auto b = mesh->vertices()[a->index() + cell_vert_dimensions.x()];
163  auto c = mesh->vertices()[a->index() + 1];
164  auto d = mesh->vertices()[b->index() + 1];
165 
166  geom::brep_vertex* abc[3] = {a, b, c};
167  geom::brep_vertex* cbd[3] = {c, b, d};
168 
169  mesh->faces().emplace_back(abc);
170  mesh->faces().emplace_back(cbd);
171  }
172  }
173 
174  // Generate vertex normals
175  auto& vertex_normals = static_cast<geom::brep_attribute<math::fvec3>&>(*mesh->vertices().attributes().try_emplace<math::fvec3>("normal").first);
176  for (pixel_position.y() = cell_pixel_bounds_min.y(); pixel_position.y() <= cell_pixel_bounds_max.y(); ++pixel_position.y())
177  {
178  for (pixel_position.x() = cell_pixel_bounds_min.x(); pixel_position.x() <= cell_pixel_bounds_max.x(); ++pixel_position.x())
179  {
180  const auto pixel_w = pixel_position.x() >= 1u ? pixel_position - math::uvec2{1, 0} : pixel_position;
181  const auto pixel_e = pixel_position.x() < cell_pixel_bounds_max.x() ? pixel_position + math::uvec2{1, 0} : pixel_position;
182  const auto pixel_s = pixel_position.y() >= 1u ? pixel_position - math::uvec2{0, 1} : pixel_position;
183  const auto pixel_n = pixel_position.y() < cell_pixel_bounds_max.y() ? pixel_position + math::uvec2{0, 1} : pixel_position;
184 
185  const auto index_c = pixel_position.y() * (cell_pixel_bounds_max.x() + 1) + pixel_position.x();
186  const auto index_w = pixel_w.y() * (cell_pixel_bounds_max.x() + 1) + pixel_w.x();
187  const auto index_e = pixel_e.y() * (cell_pixel_bounds_max.x() + 1) + pixel_e.x();
188  const auto index_s = pixel_s.y() * (cell_pixel_bounds_max.x() + 1) + pixel_s.x();
189  const auto index_n = pixel_n.y() * (cell_pixel_bounds_max.x() + 1) + pixel_n.x();
190 
191  const auto height_w = vertex_positions[index_w].y();
192  const auto height_e = vertex_positions[index_e].y();
193  const auto height_s = vertex_positions[index_s].y();
194  const auto height_n = vertex_positions[index_n].y();
195 
196  auto& normal_c = vertex_normals[index_c];
197  normal_c = math::normalize(math::fvec3{(height_w - height_e) / vertex_scale.x(), 2.0f, (height_s - height_n) / vertex_scale.z()});
198  }
199  }
200 
201  // Construct terrain cell rigid body
202  auto rigid_body = std::make_unique<physics::rigid_body>();
203  rigid_body->set_mass(0.0f);
204  rigid_body->set_collider(std::make_shared<physics::mesh_collider>(mesh));
205  rigid_body->set_transform({transform.translation, transform.rotation, math::fvec3{max_scale, max_scale, max_scale} * 0.5f});
206  registry.emplace<rigid_body_component>(cell_eid, std::move(rigid_body));
207 
208  auto model = generate_terrain_model(*mesh, material, cell_quad_dimensions);
210  scene.object = std::make_shared<scene::static_mesh>(std::move(model));
211  scene.layer_mask = 1;
212  registry.emplace<scene_component>(cell_eid, std::move(scene));
213  }
214  );
215 
216  registry.emplace<terrain_grid_component>(grid_eid, std::move(grid));
217  return grid_eid;
218 }
219 
220 std::unique_ptr<render::model> terrain_system::generate_terrain_model(const geom::brep_mesh& mesh, std::shared_ptr<render::material> material, const math::uvec2& quad_dimensions) const
221 {
222  const auto& vertex_positions = mesh.vertices().attributes().at<math::fvec3>("position");
223  const auto& vertex_normals = mesh.vertices().attributes().at<math::fvec3>("normal");
224 
225  // Allocate model
226  auto model = std::make_unique<render::model>();
227 
228  // Init model bounds
229  auto& bounds = model->get_bounds();
231 
232  // Construct VAO
233  constexpr gl::vertex_input_attribute vertex_attributes[] =
234  {
235  {
237  0,
239  0
240  },
241  {
243  0,
245  3 * sizeof(std::int16_t)
246  }
247  };
248  auto& vao = model->get_vertex_array();
249  vao = std::make_unique<gl::vertex_array>(vertex_attributes);
250 
251  // Interleave vertex data
252  const auto vert_dimensions = quad_dimensions + 1u;
253  const std::size_t vertex_count = 2 * (vert_dimensions.x() * quad_dimensions.y() + quad_dimensions.y() - 1);
254  constexpr std::size_t vertex_stride = 3 * sizeof(std::int16_t) + 3 * sizeof(float);
255  std::vector<std::byte> vertex_data(vertex_count * vertex_stride);
256  std::byte* v = vertex_data.data();
257 
258  auto normalized_int16 = [](const math::fvec3& f) -> math::vec3<std::int16_t>
259  {
261  for (int j = 0; j < 3; ++j)
262  {
263  i[j] = static_cast<std::int16_t>(f[j] < 0.0f ? f[j] * 32768.0f : f[j] * 32767.0f);
264  }
265  return i;
266  };
267 
268  for (auto y = 0u; y < quad_dimensions.y(); ++y)
269  {
270  std::size_t indices[2];
271 
272  for (auto x = 0u; x < vert_dimensions.x(); ++x)
273  {
274  indices[0] = y * vert_dimensions.x() + x;
275  indices[1] = indices[0] + vert_dimensions.x();
276 
277  for (auto i: indices)
278  {
279  auto position = normalized_int16(vertex_positions[i]);
280 
281  std::memcpy(v, &position[0], sizeof(std::int16_t) * 3);
282  v += sizeof(std::int16_t) * 3;
283  std::memcpy(v, &vertex_normals[i], sizeof(float) * 3);
284  v += sizeof(float) * 3;
285 
286  // Extend model bounds
287  bounds.extend(vertex_positions[i]);
288  }
289  }
290 
291  if (y < quad_dimensions.y() - 1)
292  {
293  // Restart triangle strip on next row using degenerate triangles
294 
295 
296  auto position = normalized_int16(vertex_positions[indices[1]]);
297  std::memcpy(v, &position[0], sizeof(std::int16_t) * 3);
298  v += sizeof(int16_t) * 3;
299  std::memcpy(v, &vertex_normals[indices[1]], sizeof(float) * 3);
300  v += sizeof(float) * 3;
301 
302  indices[0] = (y + 1) * vert_dimensions.x();
303 
304  position = normalized_int16(vertex_positions[indices[0]]);
305  std::memcpy(v, &position[0], sizeof(std::int16_t) * 3);
306  v += sizeof(int16_t) * 3;
307  std::memcpy(v, &vertex_normals[indices[0]], sizeof(float) * 3);
308  v += sizeof(float) * 3;
309  }
310  }
311 
312  // Construct VBO
313  auto& vbo = model->get_vertex_buffer();
314  vbo = std::make_unique<gl::vertex_buffer>(gl::buffer_usage::static_draw, vertex_data);
315  model->set_vertex_offset(0);
316  model->set_vertex_stride(vertex_stride);
317 
318  // Create material group
319  model->get_groups().resize(1);
320  render::model_group& model_group = model->get_groups().front();
321 
322  model_group.id = {};
323  model_group.material = material;
325  model_group.first_vertex = 0;
326  model_group.vertex_count = static_cast<std::uint32_t>(vertex_count);
327 
328  return model;
329 }
Per-element B-rep data.
Boundary representation (B-rep) of a mesh.
Definition: brep-mesh.hpp:34
A point in space.
Abstract base class for lights, cameras, model instances, and other scene objects.
Definition: object.hpp:172
terrain_system(entity::registry &registry)
entity::id generate(std::shared_ptr< gl::image_2d > heightmap, const math::uvec2 &subdivisions, const math::transform< float > &transform, std::shared_ptr< render::material > material)
Generates terrain entities from a heightmap.
virtual void update(float t, float dt)
Perform's a system's update() function.
Abstract base class for updatable systems.
entity::registry & registry
Registry on which the system operate.
log_message< log_message_severity::error, Args... > log_error
Formats and logs an error message.
Definition: log.hpp:144
entt::registry registry
Component registry type.
Definition: registry.hpp:28
entt::entity id
Entity ID type.
Definition: id.hpp:28
@ static_draw
Data will be modified once, by the application, and used many times, for drawing commands.
@ triangle_strip
Connected triangle primitives with consecutive triangles sharing an edge.
constexpr vector< T, N > max(const vector< T, N > &x, const vector< T, N > &y)
Returns a vector containing the maximum elements of two vectors.
Definition: vector.hpp:1328
quaternion< T > normalize(const quaternion< T > &q)
Normalizes a quaternion.
Definition: quaternion.hpp:679
@ mesh
Mesh collider.
3D scene.
Definition: context.hpp:28
vector_type scale
Scale vector.
Definition: transform.hpp:56
vector_type translation
Translation vector.
Definition: transform.hpp:50
quaternion_type rotation
Rotation quaternion.
Definition: transform.hpp:53
n-dimensional vector.
Definition: vector.hpp:44
constexpr element_type & x() noexcept
Returns a reference to the first element.
Definition: vector.hpp:164
constexpr element_type & y() noexcept
Returns a reference to the second element.
Definition: vector.hpp:180
static constexpr vector infinity() noexcept
Returns a vector of infinities, where every element is equal to infinity.
Definition: vector.hpp:342
constexpr element_type & front() noexcept
Returns a reference to the first element.
Definition: vector.hpp:124
Part of a model which is associated with exactly one material.
Definition: model.hpp:41
std::shared_ptr< render::material > material
Definition: model.hpp:46
gl::primitive_topology primitive_topology
Definition: model.hpp:43
std::uint32_t vertex_count
Definition: model.hpp:45
std::uint32_t first_vertex
Definition: model.hpp:44
hash::fnv1a32_t id
Definition: model.hpp:42
Single cell in a terrain grid.
Grid of terrain cells.
std::vector< entity::id > cells