Antkeeper  0.0.1
material.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 
25 #include <engine/utility/json.hpp>
27 #include <utility>
28 #include <type_traits>
29 #include <string>
30 
31 namespace render {
32 
34 {
35  *this = other;
36 }
37 
39 {
40  two_sided = other.two_sided;
41  blend_mode = other.blend_mode;
42  shadow_mode = other.shadow_mode;
43  flags = other.flags;
44  shader_template = other.shader_template;
45 
46  variable_map.clear();
47  for (const auto& [key, value]: other.variable_map)
48  {
49  if (value)
50  {
51  variable_map.emplace(key, value->clone());
52  }
53  }
54 
55  m_hash = other.m_hash;
56 
57  return *this;
58 }
59 
60 void material::set_two_sided(bool two_sided) noexcept
61 {
62  this->two_sided = two_sided;
63 
64  rehash();
65 }
66 
68 {
69  blend_mode = mode;
70 
71  rehash();
72 }
73 
75 {
76  shadow_mode = mode;
77 
78  rehash();
79 }
80 
81 void material::set_flags(std::uint32_t flags) noexcept
82 {
83  this->flags = flags;
84 
85  rehash();
86 }
87 
88 void material::set_shader_template(std::shared_ptr<gl::shader_template> shader_template)
89 {
90  this->shader_template = shader_template;
91 
92  rehash();
93 }
94 
95 void material::set_variable(hash::fnv1a32_t key, std::shared_ptr<material_variable_base> value)
96 {
97  variable_map[key] = std::move(value);
98 }
99 
100 std::shared_ptr<material_variable_base> material::get_variable(hash::fnv1a32_t key) const
101 {
102  if (auto i = variable_map.find(key); i != variable_map.end())
103  {
104  return i->second;
105  }
106 
107  return nullptr;
108 }
109 
110 void material::rehash() noexcept
111 {
112  m_hash = 0;
113  if (shader_template)
114  {
115  m_hash = shader_template->hash();
116  }
117 
118  m_hash = hash_combine(m_hash, std::hash<bool>{}(two_sided));
119  m_hash = hash_combine(m_hash, std::hash<material_blend_mode>{}(blend_mode));
120  m_hash = hash_combine(m_hash, std::hash<material_shadow_mode>{}(shadow_mode));
121  m_hash = hash_combine(m_hash, std::hash<std::uint32_t>{}(flags));
122 }
123 
124 } // namespace render
125 
126 template <typename T>
127 static bool read_value(T* value, const nlohmann::json& json, const std::string& name)
128 {
129  if (auto element = json.find(name); element != json.end())
130  {
131  *value = element.value().get<T>();
132  return true;
133  }
134 
135  return false;
136 }
137 
138 static bool load_texture_1d_property(resource_manager& resource_manager, render::material& material, hash::fnv1a32_t key, const nlohmann::json& json)
139 {
140  // If JSON element is an array
141  if (json.is_array())
142  {
143  // Create variable
144  auto variable = std::make_shared<render::matvar_texture_1d>(json.size());
145 
146  // Load textures
147  std::size_t i = 0;
148  for (const auto& element: json)
149  {
150  variable->set(i, resource_manager.load<gl::texture_1d>(element.get<std::string>()));
151  ++i;
152  }
153 
154  material.set_variable(key, variable);
155  }
156  else
157  {
158  // Create variable
159  auto variable = std::make_shared<render::matvar_texture_1d>(json.size());
160 
161  // Load texture
162  variable->set(resource_manager.load<gl::texture_1d>(json.get<std::string>()));
163 
164  material.set_variable(key, variable);
165  }
166 
167  return true;
168 }
169 
170 static bool load_texture_2d_property(resource_manager& resource_manager, render::material& material, hash::fnv1a32_t key, const nlohmann::json& json)
171 {
172  // If JSON element is an array
173  if (json.is_array())
174  {
175  // Create variable
176  auto variable = std::make_shared<render::matvar_texture_2d>(json.size());
177 
178  // Load textures
179  std::size_t i = 0;
180  for (const auto& element: json)
181  {
182  variable->set(i, resource_manager.load<gl::texture_2d>(element.get<std::string>()));
183  ++i;
184  }
185 
186  material.set_variable(key, variable);
187  }
188  else
189  {
190  // Create variable
191  auto variable = std::make_shared<render::matvar_texture_2d>(json.size());
192 
193  // Load texture
194  variable->set(resource_manager.load<gl::texture_2d>(json.get<std::string>()));
195 
196  material.set_variable(key, variable);
197  }
198 
199  return true;
200 }
201 
202 static bool load_texture_3d_property(resource_manager& resource_manager, render::material& material, hash::fnv1a32_t key, const nlohmann::json& json)
203 {
204  // If JSON element is an array
205  if (json.is_array())
206  {
207  // Create variable
208  auto variable = std::make_shared<render::matvar_texture_3d>(json.size());
209 
210  // Load textures
211  std::size_t i = 0;
212  for (const auto& element: json)
213  {
214  variable->set(i, resource_manager.load<gl::texture_3d>(element.get<std::string>()));
215  ++i;
216  }
217 
218  material.set_variable(key, variable);
219  }
220  else
221  {
222  // Create variable
223  auto variable = std::make_shared<render::matvar_texture_3d>(json.size());
224 
225  // Load texture
226  variable->set(resource_manager.load<gl::texture_3d>(json.get<std::string>()));
227 
228  material.set_variable(key, variable);
229  }
230 
231  return true;
232 }
233 
234 static bool load_texture_cube_property(resource_manager& resource_manager, render::material& material, hash::fnv1a32_t key, const nlohmann::json& json)
235 {
236  // If JSON element is an array
237  if (json.is_array())
238  {
239  // Create variable
240  auto variable = std::make_shared<render::matvar_texture_cube>(json.size());
241 
242  // Load textures
243  std::size_t i = 0;
244  for (const auto& element: json)
245  {
246  variable->set(i, resource_manager.load<gl::texture_cube>(element.get<std::string>()));
247  ++i;
248  }
249 
250  material.set_variable(key, variable);
251  }
252  else
253  {
254  // Create variable
255  auto variable = std::make_shared<render::matvar_texture_cube>(json.size());
256 
257  // Load texture
258  variable->set(resource_manager.load<gl::texture_cube>(json.get<std::string>()));
259 
260  material.set_variable(key, variable);
261  }
262 
263  return true;
264 }
265 
266 template <typename T>
267 static bool load_scalar_property(render::material& material, hash::fnv1a32_t key, const nlohmann::json& json)
268 {
269  // If JSON element is an array
270  if (json.is_array())
271  {
272  // Create variable
273  auto variable = std::make_shared<render::material_variable<T>>(json.size());
274 
275  // Set variable values
276  std::size_t i = 0;
277  for (const auto& element: json)
278  {
279  variable->set(i, element.get<T>());
280  }
281 
282  material.set_variable(key, variable);
283  }
284  else
285  {
286  material.set_variable(key, std::make_shared<render::material_variable<T>>(1, json.get<T>()));
287  }
288 
289  return true;
290 }
291 
292 template <typename T>
293 static bool load_vector_property(render::material& material, hash::fnv1a32_t key, std::size_t vector_size, const nlohmann::json& json)
294 {
295  // If JSON element is an array of arrays
296  if (json.is_array() && json.begin().value().is_array())
297  {
298  // Create variable
299  auto variable = std::make_shared<render::material_variable<T>>(json.size());
300 
301  // For each vector in the array
302  std::size_t i = 0;
303  for (const auto& vector_element: json)
304  {
305  // Read vector elements
306  T value;
307  std::size_t j = 0;
308  for (const auto& value_element: vector_element)
309  value[j++] = value_element.get<typename T::element_type>();
310 
311  variable->set(i, value);
312 
313  ++i;
314  }
315 
316  material.set_variable(key, variable);
317  }
318  else
319  {
320  // Read vector elements
321  T value;
322  std::size_t i = 0;
323  for (const auto& value_element: json)
324  value[i++] = value_element.get<typename T::element_type>();
325 
326  material.set_variable(key, std::make_shared<render::material_variable<T>>(1, value));
327  }
328 
329  return true;
330 }
331 
332 template <typename T>
333 static bool load_matrix_property(render::material& material, hash::fnv1a32_t key, std::size_t column_count, std::size_t row_count, const nlohmann::json& json)
334 {
335  // If JSON element is an array of arrays of arrays
336  if (json.is_array() && json.begin().value().is_array())
337  {
338  if (json.begin().value().begin().value().is_array())
339  {
340  // Create variable
341  auto variable = std::make_shared<render::material_variable<T>>(json.size());
342 
343  // For each matrix in the array
344  std::size_t i = 0;
345  for (const auto& matrix_element: json)
346  {
347  // Read vector elements
348  T value;
349  std::size_t j = 0;
350  for (const auto& column_element: matrix_element)
351  {
352  std::size_t k = 0;
353  for (const auto& row_element: column_element)
354  {
355  value[j][k] = row_element.get<typename T::element_type>();
356  ++k;
357  }
358 
359  ++j;
360  }
361 
362  // Set matrix value
363  variable->set(i, value);
364 
365  ++i;
366  }
367 
368  material.set_variable(key, variable);
369 
370  return true;
371  }
372  else
373  {
374  // Read matrix elements
375  T value;
376  std::size_t i = 0;
377  for (const auto& column_element: json)
378  {
379  std::size_t j = 0;
380  for (const auto& row_element: column_element)
381  {
382  value[i][j] = row_element.get<typename T::element_type>();
383  ++j;
384  }
385 
386  ++i;
387  }
388 
389  material.set_variable(key, std::make_shared<render::material_variable<T>>(1, value));
390 
391  return true;
392  }
393  }
394 
395  return false;
396 }
397 
398 template <>
400 {
401  auto material = std::make_unique<render::material>();
402 
403  // Load JSON data
405 
406  // Read two sided
407  bool two_sided = false;
408  read_value(&two_sided, *json, "two_sided");
409  material->set_two_sided(two_sided);
410 
411  // Read blend mode
412  std::string blend_mode;
413  read_value(&blend_mode, *json, "blend_mode");
414  if (blend_mode == "opaque")
415  {
417  }
418  else if (blend_mode == "masked")
419  {
421  }
422  else if (blend_mode == "translucent")
423  {
425  }
426 
427  // Read shadow mode
428  std::string shadow_mode;
429  read_value(&shadow_mode, *json, "shadow_mode");
430  if (shadow_mode == "opaque")
431  {
433  }
434  else if (shadow_mode == "none")
435  {
437  }
438 
439  // Init material flags
440  std::uint32_t flags = 0;
441 
442  // Read depth mode
443  std::string depth_mode;
444  read_value(&depth_mode, *json, "depth_mode");
445  if (depth_mode == "in_front")
446  flags |= MATERIAL_FLAG_X_RAY;
447 
448  // Read decal mode
449  std::string decal_mode;
450  read_value(&decal_mode, *json, "decal_mode");
451  if (decal_mode == "decal")
452  flags |= MATERIAL_FLAG_DECAL;
453  else if (decal_mode == "surface")
455 
456  // Set material flags
457  material->set_flags(flags);
458 
459  // Read shader template filename
460  std::string shader_template_filename;
461  if (read_value(&shader_template_filename, *json, "shader_template"))
462  {
463  // Loader shader template
464  material->set_shader_template(resource_manager.load<gl::shader_template>(shader_template_filename));
465  }
466 
467  // Read material variables
468  if (auto variables_element = json->find("variables"); variables_element != json->end())
469  {
470  for (const auto& variable_element: variables_element.value())
471  {
472  // Read variable name
473  std::string name;
474  if (!read_value(&name, variable_element, "name"))
475  {
476  // Ignore nameless properties
477  continue;
478  }
479 
480  // Read variable type
481  std::string type;
482  if (!read_value(&type, variable_element, "type"))
483  {
484  // Ignore typeless properties
485  continue;
486  }
487 
488  // Find value element
489  auto value_element = variable_element.find("value");
490  if (value_element == variable_element.end())
491  {
492  // Ignore valueless properties
493  continue;
494  }
495 
496  // Hash variable name
497  const hash::fnv1a32_t key = hash::fnv1a32<char>(name);
498 
499  if (type == "texture_1d")
500  {
501  load_texture_1d_property(resource_manager, *material, key, value_element.value());
502  }
503  else if (type == "texture_2d")
504  {
505  load_texture_2d_property(resource_manager, *material, key, value_element.value());
506  }
507  else if (type == "texture_3d")
508  {
509  load_texture_3d_property(resource_manager, *material, key, value_element.value());
510  }
511  else if (type == "texture_cube")
512  {
513  load_texture_cube_property(resource_manager, *material, key, value_element.value());
514  }
515  // If variable type is a matrix
516  else if (type[type.size() - 2] == 'x' &&
517  std::isdigit(type[type.size() - 3]) &&
518  std::isdigit(type.back()))
519  {
520  std::size_t columns = std::stoul(type.substr(type.size() - 3, 1));
521  std::size_t rows = std::stoul(type.substr(type.size() - 1, 1));
522 
523  if (type.find("float") != std::string::npos)
524  {
525  if (columns == 2 && rows == 2)
526  load_matrix_property<math::fmat2>(*material, key, columns, rows, value_element.value());
527  else if (columns == 3 && rows == 3)
528  load_matrix_property<math::fmat3>(*material, key, columns, rows, value_element.value());
529  else if (columns == 4 && rows == 4)
530  load_matrix_property<math::fmat4>(*material, key, columns, rows, value_element.value());
531  }
532  }
533  // If variable type is a vector
534  else if (std::isdigit(type.back()))
535  {
536  std::size_t size = std::stoul(type.substr(type.size() - 1, 1));
537 
538  if (type.find("float") != std::string::npos)
539  {
540  if (size == 2)
541  load_vector_property<math::fvec2>(*material, key, size, value_element.value());
542  else if (size == 3)
543  load_vector_property<math::fvec3>(*material, key, size, value_element.value());
544  else if (size == 4)
545  load_vector_property<math::fvec4>(*material, key, size, value_element.value());
546  }
547  else if (type.find("uint") != std::string::npos)
548  {
549  if (size == 2)
550  load_vector_property<math::uvec2>(*material, key, size, value_element.value());
551  else if (size == 3)
552  load_vector_property<math::uvec3>(*material, key, size, value_element.value());
553  else if (size == 4)
554  load_vector_property<math::uvec4>(*material, key, size, value_element.value());
555  }
556  else if (type.find("int") != std::string::npos)
557  {
558  if (size == 2)
559  load_vector_property<math::ivec2>(*material, key, size, value_element.value());
560  else if (size == 3)
561  load_vector_property<math::ivec3>(*material, key, size, value_element.value());
562  else if (size == 4)
563  load_vector_property<math::ivec4>(*material, key, size, value_element.value());
564  }
565  else if (type.find("bool") != std::string::npos)
566  {
567  if (size == 2)
568  load_vector_property<math::bvec2>(*material, key, size, value_element.value());
569  else if (size == 3)
570  load_vector_property<math::bvec3>(*material, key, size, value_element.value());
571  else if (size == 4)
572  load_vector_property<math::bvec4>(*material, key, size, value_element.value());
573  }
574  }
575  // If variable type is a scalar
576  else
577  {
578  if (type.find("float") != std::string::npos)
579  load_scalar_property<float>(*material, key, value_element.value());
580  else if (type.find("uint") != std::string::npos)
581  load_scalar_property<unsigned int>(*material, key, value_element.value());
582  else if (type.find("int") != std::string::npos)
583  load_scalar_property<int>(*material, key, value_element.value());
584  else if (type.find("bool") != std::string::npos)
585  load_scalar_property<bool>(*material, key, value_element.value());
586  }
587  }
588  }
589 
590  return material;
591 }
Template used to for generating one or more shader variants from a single source.
1D texture.
Definition: texture.hpp:73
2D texture.
Definition: texture.hpp:141
3D texture.
Definition: texture.hpp:209
Cube texture.
Definition: texture.hpp:243
A material is associated with exactly one shader program and contains a set of material properties wh...
Definition: material.hpp:37
void set_variable(hash::fnv1a32_t key, std::shared_ptr< material_variable_base > value)
Sets the value of a material variable with the given name.
Definition: material.cpp:95
material()=default
Constructs a material.
void set_two_sided(bool two_sided) noexcept
Enables or disables back-face culling of the material surface.
Definition: material.cpp:60
std::shared_ptr< material_variable_base > get_variable(hash::fnv1a32_t key) const
Returns a shared pointer to the material variable with the given name, or nullptr if not found.
Definition: material.cpp:100
material & operator=(const material &other)
Makes this material a copy of aother material.
Definition: material.cpp:38
void set_flags(std::uint32_t flags) noexcept
Sets the material flags.
Definition: material.cpp:81
void set_blend_mode(material_blend_mode mode) noexcept
Sets the material blend mode.
Definition: material.cpp:67
void set_shadow_mode(material_shadow_mode mode) noexcept
Sets the material shadow mode.
Definition: material.cpp:74
void set_shader_template(std::shared_ptr< gl::shader_template > shader_template)
Sets the material's shader template.
Definition: material.cpp:88
static std::unique_ptr< T > load(::resource_manager &resource_manager, deserialize_context &ctx)
Loads a resource.
Manages the loading, caching, and saving of resources.
std::shared_ptr< T > load(const std::filesystem::path &path)
Loads and caches a resource.
constexpr std::uint32_t hash_combine(std::uint32_t x, std::uint32_t y) noexcept
Combines two hash values.
nlohmann::json json
JSON data.
Definition: json.hpp:26
#define MATERIAL_FLAG_DECAL
#define MATERIAL_FLAG_DECAL_SURFACE
#define MATERIAL_FLAG_X_RAY
High-level rendering.
material_blend_mode
Material blend modes.
@ opaque
Material is fully opaque.
@ masked
Material has binary masked opacity.
@ translucent
Material is translucent.
material_shadow_mode
Material shadow casting modes.
@ none
Material does not cast shadows.
@ opaque
Material casts fully opaque shadows.
Text and typography.
Definition: bitmap-font.cpp:24
Provides access to a deserialization state.
32-bit FNV-1a hash value.
Definition: fnv1a.hpp:117