💿🐜 Antkeeper source code https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

591 lines
16 KiB

  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. #include <engine/render/material.hpp>
  20. #include <engine/render/material-variable.hpp>
  21. #include <engine/resources/resource-loader.hpp>
  22. #include <engine/resources/resource-manager.hpp>
  23. #include <engine/render/material-flags.hpp>
  24. #include <engine/utility/json.hpp>
  25. #include <engine/utility/hash/hash-combine.hpp>
  26. #include <utility>
  27. #include <type_traits>
  28. #include <string>
  29. namespace render {
  30. material::material(const material& other)
  31. {
  32. *this = other;
  33. }
  34. material& material::operator=(const material& other)
  35. {
  36. two_sided = other.two_sided;
  37. blend_mode = other.blend_mode;
  38. shadow_mode = other.shadow_mode;
  39. flags = other.flags;
  40. shader_template = other.shader_template;
  41. variable_map.clear();
  42. for (const auto& [key, value]: other.variable_map)
  43. {
  44. if (value)
  45. {
  46. variable_map.emplace(key, value->clone());
  47. }
  48. }
  49. m_hash = other.m_hash;
  50. return *this;
  51. }
  52. void material::set_two_sided(bool two_sided) noexcept
  53. {
  54. this->two_sided = two_sided;
  55. rehash();
  56. }
  57. void material::set_blend_mode(material_blend_mode mode) noexcept
  58. {
  59. blend_mode = mode;
  60. rehash();
  61. }
  62. void material::set_shadow_mode(material_shadow_mode mode) noexcept
  63. {
  64. shadow_mode = mode;
  65. rehash();
  66. }
  67. void material::set_flags(std::uint32_t flags) noexcept
  68. {
  69. this->flags = flags;
  70. rehash();
  71. }
  72. void material::set_shader_template(std::shared_ptr<gl::shader_template> shader_template)
  73. {
  74. this->shader_template = shader_template;
  75. rehash();
  76. }
  77. void material::set_variable(hash::fnv1a32_t key, std::shared_ptr<material_variable_base> value)
  78. {
  79. variable_map[key] = std::move(value);
  80. }
  81. std::shared_ptr<material_variable_base> material::get_variable(hash::fnv1a32_t key) const
  82. {
  83. if (auto i = variable_map.find(key); i != variable_map.end())
  84. {
  85. return i->second;
  86. }
  87. return nullptr;
  88. }
  89. void material::rehash() noexcept
  90. {
  91. m_hash = 0;
  92. if (shader_template)
  93. {
  94. m_hash = shader_template->hash();
  95. }
  96. m_hash = hash_combine(m_hash, std::hash<bool>{}(two_sided));
  97. m_hash = hash_combine(m_hash, std::hash<material_blend_mode>{}(blend_mode));
  98. m_hash = hash_combine(m_hash, std::hash<material_shadow_mode>{}(shadow_mode));
  99. m_hash = hash_combine(m_hash, std::hash<std::uint32_t>{}(flags));
  100. }
  101. } // namespace render
  102. template <typename T>
  103. static bool read_value(T* value, const nlohmann::json& json, const std::string& name)
  104. {
  105. if (auto element = json.find(name); element != json.end())
  106. {
  107. *value = element.value().get<T>();
  108. return true;
  109. }
  110. return false;
  111. }
  112. static bool load_texture_1d_property(resource_manager& resource_manager, render::material& material, hash::fnv1a32_t key, const nlohmann::json& json)
  113. {
  114. // If JSON element is an array
  115. if (json.is_array())
  116. {
  117. // Create variable
  118. auto variable = std::make_shared<render::matvar_texture_1d>(json.size());
  119. // Load textures
  120. std::size_t i = 0;
  121. for (const auto& element: json)
  122. {
  123. variable->set(i, resource_manager.load<gl::texture_1d>(element.get<std::string>()));
  124. ++i;
  125. }
  126. material.set_variable(key, variable);
  127. }
  128. else
  129. {
  130. // Create variable
  131. auto variable = std::make_shared<render::matvar_texture_1d>(json.size());
  132. // Load texture
  133. variable->set(resource_manager.load<gl::texture_1d>(json.get<std::string>()));
  134. material.set_variable(key, variable);
  135. }
  136. return true;
  137. }
  138. static bool load_texture_2d_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_2d>(json.size());
  145. // Load textures
  146. std::size_t i = 0;
  147. for (const auto& element: json)
  148. {
  149. variable->set(i, resource_manager.load<gl::texture_2d>(element.get<std::string>()));
  150. ++i;
  151. }
  152. material.set_variable(key, variable);
  153. }
  154. else
  155. {
  156. // Create variable
  157. auto variable = std::make_shared<render::matvar_texture_2d>(json.size());
  158. // Load texture
  159. variable->set(resource_manager.load<gl::texture_2d>(json.get<std::string>()));
  160. material.set_variable(key, variable);
  161. }
  162. return true;
  163. }
  164. static bool load_texture_3d_property(resource_manager& resource_manager, render::material& material, hash::fnv1a32_t key, const nlohmann::json& json)
  165. {
  166. // If JSON element is an array
  167. if (json.is_array())
  168. {
  169. // Create variable
  170. auto variable = std::make_shared<render::matvar_texture_3d>(json.size());
  171. // Load textures
  172. std::size_t i = 0;
  173. for (const auto& element: json)
  174. {
  175. variable->set(i, resource_manager.load<gl::texture_3d>(element.get<std::string>()));
  176. ++i;
  177. }
  178. material.set_variable(key, variable);
  179. }
  180. else
  181. {
  182. // Create variable
  183. auto variable = std::make_shared<render::matvar_texture_3d>(json.size());
  184. // Load texture
  185. variable->set(resource_manager.load<gl::texture_3d>(json.get<std::string>()));
  186. material.set_variable(key, variable);
  187. }
  188. return true;
  189. }
  190. static bool load_texture_cube_property(resource_manager& resource_manager, render::material& material, hash::fnv1a32_t key, const nlohmann::json& json)
  191. {
  192. // If JSON element is an array
  193. if (json.is_array())
  194. {
  195. // Create variable
  196. auto variable = std::make_shared<render::matvar_texture_cube>(json.size());
  197. // Load textures
  198. std::size_t i = 0;
  199. for (const auto& element: json)
  200. {
  201. variable->set(i, resource_manager.load<gl::texture_cube>(element.get<std::string>()));
  202. ++i;
  203. }
  204. material.set_variable(key, variable);
  205. }
  206. else
  207. {
  208. // Create variable
  209. auto variable = std::make_shared<render::matvar_texture_cube>(json.size());
  210. // Load texture
  211. variable->set(resource_manager.load<gl::texture_cube>(json.get<std::string>()));
  212. material.set_variable(key, variable);
  213. }
  214. return true;
  215. }
  216. template <typename T>
  217. static bool load_scalar_property(render::material& material, hash::fnv1a32_t key, const nlohmann::json& json)
  218. {
  219. // If JSON element is an array
  220. if (json.is_array())
  221. {
  222. // Create variable
  223. auto variable = std::make_shared<render::material_variable<T>>(json.size());
  224. // Set variable values
  225. std::size_t i = 0;
  226. for (const auto& element: json)
  227. {
  228. variable->set(i, element.get<T>());
  229. }
  230. material.set_variable(key, variable);
  231. }
  232. else
  233. {
  234. material.set_variable(key, std::make_shared<render::material_variable<T>>(1, json.get<T>()));
  235. }
  236. return true;
  237. }
  238. template <typename T>
  239. static bool load_vector_property(render::material& material, hash::fnv1a32_t key, std::size_t vector_size, const nlohmann::json& json)
  240. {
  241. // If JSON element is an array of arrays
  242. if (json.is_array() && json.begin().value().is_array())
  243. {
  244. // Create variable
  245. auto variable = std::make_shared<render::material_variable<T>>(json.size());
  246. // For each vector in the array
  247. std::size_t i = 0;
  248. for (const auto& vector_element: json)
  249. {
  250. // Read vector elements
  251. T value;
  252. std::size_t j = 0;
  253. for (const auto& value_element: vector_element)
  254. value[j++] = value_element.get<typename T::element_type>();
  255. variable->set(i, value);
  256. ++i;
  257. }
  258. material.set_variable(key, variable);
  259. }
  260. else
  261. {
  262. // Read vector elements
  263. T value;
  264. std::size_t i = 0;
  265. for (const auto& value_element: json)
  266. value[i++] = value_element.get<typename T::element_type>();
  267. material.set_variable(key, std::make_shared<render::material_variable<T>>(1, value));
  268. }
  269. return true;
  270. }
  271. template <typename T>
  272. 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)
  273. {
  274. // If JSON element is an array of arrays of arrays
  275. if (json.is_array() && json.begin().value().is_array())
  276. {
  277. if (json.begin().value().begin().value().is_array())
  278. {
  279. // Create variable
  280. auto variable = std::make_shared<render::material_variable<T>>(json.size());
  281. // For each matrix in the array
  282. std::size_t i = 0;
  283. for (const auto& matrix_element: json)
  284. {
  285. // Read vector elements
  286. T value;
  287. std::size_t j = 0;
  288. for (const auto& column_element: matrix_element)
  289. {
  290. std::size_t k = 0;
  291. for (const auto& row_element: column_element)
  292. {
  293. value[j][k] = row_element.get<typename T::element_type>();
  294. ++k;
  295. }
  296. ++j;
  297. }
  298. // Set matrix value
  299. variable->set(i, value);
  300. ++i;
  301. }
  302. material.set_variable(key, variable);
  303. return true;
  304. }
  305. else
  306. {
  307. // Read matrix elements
  308. T value;
  309. std::size_t i = 0;
  310. for (const auto& column_element: json)
  311. {
  312. std::size_t j = 0;
  313. for (const auto& row_element: column_element)
  314. {
  315. value[i][j] = row_element.get<typename T::element_type>();
  316. ++j;
  317. }
  318. ++i;
  319. }
  320. material.set_variable(key, std::make_shared<render::material_variable<T>>(1, value));
  321. return true;
  322. }
  323. }
  324. return false;
  325. }
  326. template <>
  327. std::unique_ptr<render::material> resource_loader<render::material>::load(::resource_manager& resource_manager, deserialize_context& ctx)
  328. {
  329. auto material = std::make_unique<render::material>();
  330. // Load JSON data
  331. auto json = resource_loader<nlohmann::json>::load(resource_manager, ctx);
  332. // Read two sided
  333. bool two_sided = false;
  334. read_value(&two_sided, *json, "two_sided");
  335. material->set_two_sided(two_sided);
  336. // Read blend mode
  337. std::string blend_mode;
  338. read_value(&blend_mode, *json, "blend_mode");
  339. if (blend_mode == "opaque")
  340. {
  341. material->set_blend_mode(render::material_blend_mode::opaque);
  342. }
  343. else if (blend_mode == "masked")
  344. {
  345. material->set_blend_mode(render::material_blend_mode::masked);
  346. }
  347. else if (blend_mode == "translucent")
  348. {
  349. material->set_blend_mode(render::material_blend_mode::translucent);
  350. }
  351. // Read shadow mode
  352. std::string shadow_mode;
  353. read_value(&shadow_mode, *json, "shadow_mode");
  354. if (shadow_mode == "opaque")
  355. {
  356. material->set_shadow_mode(render::material_shadow_mode::opaque);
  357. }
  358. else if (shadow_mode == "none")
  359. {
  360. material->set_shadow_mode(render::material_shadow_mode::none);
  361. }
  362. // Init material flags
  363. std::uint32_t flags = 0;
  364. // Read depth mode
  365. std::string depth_mode;
  366. read_value(&depth_mode, *json, "depth_mode");
  367. if (depth_mode == "in_front")
  368. flags |= MATERIAL_FLAG_X_RAY;
  369. // Read decal mode
  370. std::string decal_mode;
  371. read_value(&decal_mode, *json, "decal_mode");
  372. if (decal_mode == "decal")
  373. flags |= MATERIAL_FLAG_DECAL;
  374. else if (decal_mode == "surface")
  375. flags |= MATERIAL_FLAG_DECAL_SURFACE;
  376. // Set material flags
  377. material->set_flags(flags);
  378. // Read shader template filename
  379. std::string shader_template_filename;
  380. if (read_value(&shader_template_filename, *json, "shader_template"))
  381. {
  382. // Loader shader template
  383. material->set_shader_template(resource_manager.load<gl::shader_template>(shader_template_filename));
  384. }
  385. // Read material variables
  386. if (auto variables_element = json->find("variables"); variables_element != json->end())
  387. {
  388. for (const auto& variable_element: variables_element.value())
  389. {
  390. // Read variable name
  391. std::string name;
  392. if (!read_value(&name, variable_element, "name"))
  393. {
  394. // Ignore nameless properties
  395. continue;
  396. }
  397. // Read variable type
  398. std::string type;
  399. if (!read_value(&type, variable_element, "type"))
  400. {
  401. // Ignore typeless properties
  402. continue;
  403. }
  404. // Find value element
  405. auto value_element = variable_element.find("value");
  406. if (value_element == variable_element.end())
  407. {
  408. // Ignore valueless properties
  409. continue;
  410. }
  411. // Hash variable name
  412. const hash::fnv1a32_t key = hash::fnv1a32<char>(name);
  413. if (type == "texture_1d")
  414. {
  415. load_texture_1d_property(resource_manager, *material, key, value_element.value());
  416. }
  417. else if (type == "texture_2d")
  418. {
  419. load_texture_2d_property(resource_manager, *material, key, value_element.value());
  420. }
  421. else if (type == "texture_3d")
  422. {
  423. load_texture_3d_property(resource_manager, *material, key, value_element.value());
  424. }
  425. else if (type == "texture_cube")
  426. {
  427. load_texture_cube_property(resource_manager, *material, key, value_element.value());
  428. }
  429. // If variable type is a matrix
  430. else if (type[type.size() - 2] == 'x' &&
  431. std::isdigit(type[type.size() - 3]) &&
  432. std::isdigit(type.back()))
  433. {
  434. std::size_t columns = std::stoul(type.substr(type.size() - 3, 1));
  435. std::size_t rows = std::stoul(type.substr(type.size() - 1, 1));
  436. if (type.find("float") != std::string::npos)
  437. {
  438. if (columns == 2 && rows == 2)
  439. load_matrix_property<math::fmat2>(*material, key, columns, rows, value_element.value());
  440. else if (columns == 3 && rows == 3)
  441. load_matrix_property<math::fmat3>(*material, key, columns, rows, value_element.value());
  442. else if (columns == 4 && rows == 4)
  443. load_matrix_property<math::fmat4>(*material, key, columns, rows, value_element.value());
  444. }
  445. }
  446. // If variable type is a vector
  447. else if (std::isdigit(type.back()))
  448. {
  449. std::size_t size = std::stoul(type.substr(type.size() - 1, 1));
  450. if (type.find("float") != std::string::npos)
  451. {
  452. if (size == 2)
  453. load_vector_property<math::fvec2>(*material, key, size, value_element.value());
  454. else if (size == 3)
  455. load_vector_property<math::fvec3>(*material, key, size, value_element.value());
  456. else if (size == 4)
  457. load_vector_property<math::fvec4>(*material, key, size, value_element.value());
  458. }
  459. else if (type.find("uint") != std::string::npos)
  460. {
  461. if (size == 2)
  462. load_vector_property<math::uvec2>(*material, key, size, value_element.value());
  463. else if (size == 3)
  464. load_vector_property<math::uvec3>(*material, key, size, value_element.value());
  465. else if (size == 4)
  466. load_vector_property<math::uvec4>(*material, key, size, value_element.value());
  467. }
  468. else if (type.find("int") != std::string::npos)
  469. {
  470. if (size == 2)
  471. load_vector_property<math::ivec2>(*material, key, size, value_element.value());
  472. else if (size == 3)
  473. load_vector_property<math::ivec3>(*material, key, size, value_element.value());
  474. else if (size == 4)
  475. load_vector_property<math::ivec4>(*material, key, size, value_element.value());
  476. }
  477. else if (type.find("bool") != std::string::npos)
  478. {
  479. if (size == 2)
  480. load_vector_property<math::bvec2>(*material, key, size, value_element.value());
  481. else if (size == 3)
  482. load_vector_property<math::bvec3>(*material, key, size, value_element.value());
  483. else if (size == 4)
  484. load_vector_property<math::bvec4>(*material, key, size, value_element.value());
  485. }
  486. }
  487. // If variable type is a scalar
  488. else
  489. {
  490. if (type.find("float") != std::string::npos)
  491. load_scalar_property<float>(*material, key, value_element.value());
  492. else if (type.find("uint") != std::string::npos)
  493. load_scalar_property<unsigned int>(*material, key, value_element.value());
  494. else if (type.find("int") != std::string::npos)
  495. load_scalar_property<int>(*material, key, value_element.value());
  496. else if (type.find("bool") != std::string::npos)
  497. load_scalar_property<bool>(*material, key, value_element.value());
  498. }
  499. }
  500. }
  501. return material;
  502. }