TerraForge3D  2.3.1
3D Terrain And Landscape Generator
TextureStore.cpp
1#include "TextureStore/TextureStore.h"
2#include "Platform.h"
3#include "Data/ApplicationState.h"
4#include "Utils/Utils.h"
5
6#include <cstdlib>
7#include <cstring>
8#include <iostream>
9#include <filesystem>
10
11#define TEXTURE_STORE_ITEM_DND(text, var) \
12 if(item.var.size() > 3) \
13 { \
14 ImGui::Selectable(text, &tmp); \
15 if(ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) \
16 { \
17 ImGui::SetDragDropPayload("TerraForge3D_Texture", item.var.data(), sizeof(char) * item.var.size()); \
18 ImGui::Image((ImTextureID)item.texThumbnail->GetRendererID(), ImVec2(128, 128)); \
19 ImGui::Text("%s", item.name.c_str()); \
20 ImGui::EndDragDropSource(); \
21 } \
22 }
23
24#ifdef TERR3D_WIN32
25inline char *strcasestr(const char *str, const char *pattern)
26{
27 size_t i;
28
29 if (!*pattern)
30 {
31 return (char *)str;
32 }
33
34 for (; *str; str++)
35 {
36 if (toupper((unsigned char)*str) == toupper((unsigned char)*pattern))
37 {
38 for (i = 1;; i++)
39 {
40 if (!pattern[i])
41 {
42 return (char *)str;
43 }
44
45 if (toupper((unsigned char)str[i]) != toupper((unsigned char)pattern[i]))
46 {
47 break;
48 }
49 }
50 }
51 }
52
53 return NULL;
54}
55#endif
56
57TextureStore* TextureStore::sInstance = nullptr;
58
59nlohmann::json TextureStore::LoadTextureDatabaseJ()
60{
61 bool loadFromFile = true;
62
63 if(IsNetWorkConnected() && rand() % 2 == 0)
64 {
65 loadFromFile = false;
66 }
67
68 if(!FileExists(GetExecutableDir() + PATH_SEPARATOR "Data" PATH_SEPARATOR "cache" PATH_SEPARATOR "texture_database.terr3d"))
69 {
70 loadFromFile = false;
71 }
72
73 if(loadFromFile)
74 {
75 Log("Loading texture database from file");
76 bool tmp = false;
77 std::string tmpStr = ReadShaderSourceFile(GetExecutableDir() + PATH_SEPARATOR "Data" PATH_SEPARATOR "cache" PATH_SEPARATOR "texture_database.terr3d", &tmp);
78
79 if(tmp == false)
80 {
81 Log("Failed to load texture database from file");
82 return nlohmann::json();
83 }
84
85 try
86 {
87 return nlohmann::json::parse(tmpStr);
88 }
89
91 {
92 Log("Failed to parse texture database from file");
93 return nlohmann::json();
94 }
95 }
96
97 else
98 {
99 Log("Fetching texture database from web database.");
100 std::string tmp = FetchURL("https://api.polyhaven.com", "/assets?t=textures");
101
102 if (tmp.size() < 3)
103 {
104 Log("Failed to fetch texture database from web database.");
105 return nlohmann::json();
106 }
107
108 SaveToFile(GetExecutableDir() + PATH_SEPARATOR "Data" PATH_SEPARATOR "cache" PATH_SEPARATOR "texture_database.terr3d", tmp);
109 return nlohmann::json::parse(tmp);
110 }
111}
112
113void TextureStore::VerifyTextureThumbs()
114{
115 MkDir(GetExecutableDir() + PATH_SEPARATOR "Data" PATH_SEPARATOR "cache" PATH_SEPARATOR "texture_thumbnails" PATH_SEPARATOR);
116
117 for(auto it = textureDatabaseJ.begin() ; it != textureDatabaseJ.end() ; it++)
118 {
119 if(!FileExists(GetExecutableDir() + PATH_SEPARATOR "Data" PATH_SEPARATOR "cache" PATH_SEPARATOR "texture_thumbnails" PATH_SEPARATOR + it.key() + ".png"))
120 {
121 Log("Downloading thumbnail for texture: " + it.key());
122 DownloadFile("https://cdn.polyhaven.com", "/asset_img/thumbs/" +
123 it.key() + ".png?width=100&height=100", GetExecutableDir()
124 + PATH_SEPARATOR "Data" PATH_SEPARATOR "cache"
125 PATH_SEPARATOR "texture_thumbnails" PATH_SEPARATOR +
126 it.key() + ".png");
127 }
128 }
129}
130
131nlohmann::json TextureStore::LoadDownloadedTextureDatabaseJ()
132{
133 if(!FileExists(GetExecutableDir() + PATH_SEPARATOR "Data" PATH_SEPARATOR "configs" PATH_SEPARATOR "texture_database_downloaded.terr3d"))
134 {
135 Log("No Textures downloaded yet");
136 return nlohmann::json();
137 }
138
139 bool tmp = false;
140 std::string tmpStr = ReadShaderSourceFile(GetExecutableDir() +
141 PATH_SEPARATOR "Data" PATH_SEPARATOR "configs" PATH_SEPARATOR
142 "texture_database_downloaded.terr3d", &tmp);
143
144 if(tmp == false)
145 {
146 Log("Failed to load downloaded texture database from file");
147 return nlohmann::json();
148 }
149
150 nlohmann::json tmpJ;
151
152 try
153 {
154 tmpJ = nlohmann::json::parse(tmpStr);
155 }
156
158 {
159 Log("Failed to parse downloaded texture database from file");
160 return nlohmann::json();
161 }
162
163 return tmpJ;
164}
165
166
167void TextureStore::LoadTextureDatabase()
168{
169 textureDatabaseJ = LoadTextureDatabaseJ();
170 downloadedTextureDatabaseJ = LoadDownloadedTextureDatabaseJ();
171 textureStoreItems.clear();
172 downloadedTextureStoreItems.clear();
173
174 for(auto it = textureDatabaseJ.begin() ; it != textureDatabaseJ.end() ; it++)
175 {
176 TextureStoreItem item;
177 item.name = it.key();
178 item.thumbnailPath = GetExecutableDir() + PATH_SEPARATOR "Data"
179 PATH_SEPARATOR "cache" PATH_SEPARATOR "texture_thumbnails"
180 PATH_SEPARATOR + item.name + ".png";
181 item.download_count = it.value()["download_count"];
182
183 for(auto it2 = it.value()["authors"].begin() ; it2 != it.value()["authors"].end() ; it2++)
184 {
185 item.authours.push_back(it2.key());
186 }
187
188 if(downloadedTextureDatabaseJ.find(item.name) != downloadedTextureDatabaseJ.end())
189 {
190 item.downloaded = true;
191 item.abledo = downloadedTextureDatabaseJ[item.name]["abledo"];
192 item.normal = downloadedTextureDatabaseJ[item.name]["normal"];
193 item.roughness = downloadedTextureDatabaseJ[item.name]["roughness"];
194 item.metallic = downloadedTextureDatabaseJ[item.name]["metallic"];
195 item.ao = downloadedTextureDatabaseJ[item.name]["ao"];
196 item.arm = downloadedTextureDatabaseJ[item.name]["arm"];
197 item.baseDir = downloadedTextureDatabaseJ[item.name]["baseDir"];
198 downloadedTextureStoreItems.push_back(textureStoreItems.size());
199 }
200
201 textureStoreItems.push_back(item);
202 }
203}
204
205void TextureStore::LoadTextureThumbs()
206{
207 Log("Loading texture thumbnails");
208 int i=0;
209
210 for(auto &it : textureStoreItems)
211 {
212 i++;
213
214 if(!FileExists(GetExecutableDir() + PATH_SEPARATOR "Data" PATH_SEPARATOR "cache" PATH_SEPARATOR "texture_thumbnails" PATH_SEPARATOR + it.name + ".png"))
215 {
216 Log("Thumbnail for texture: " + it.name + " not found.");
217 it.texThumbnail = new Texture2D(GetExecutableDir() + PATH_SEPARATOR
218 "Data" PATH_SEPARATOR "textures" PATH_SEPARATOR
219 "white.png", false);
220 }
221
222 else
223 {
224 it.texThumbnail = new Texture2D(it.thumbnailPath, false);
225 }
226
227 if(i % 20 == 0)
228 {
229 std::cout << ("Loaded " + std::to_string(i) + " of " + std::to_string(textureStoreItems.size()) + " texture thumbnails\r");
230 }
231 }
232
233 Log("Texture thumbnails loaded");
234}
235
236void TextureStore::SaveDownloadsDatabase()
237{
238 nlohmann::json tmp2;
239
240 for(int id : downloadedTextureStoreItems)
241 {
242 nlohmann::json tmp;
243 tmp["abledo"] = textureStoreItems[id].abledo;
244 tmp["normal"] = textureStoreItems[id].normal;
245 tmp["roughness"]= textureStoreItems[id].roughness;
246 tmp["metallic"] = textureStoreItems[id].metallic;
247 tmp["ao"] = textureStoreItems[id].ao;
248 tmp["arm"] = textureStoreItems[id].arm;
249 tmp["baseDir"] = textureStoreItems[id].baseDir;
250 tmp2[textureStoreItems[id].name] = tmp;
251 }
252
253 SaveToFile(GetExecutableDir() + PATH_SEPARATOR "Data" PATH_SEPARATOR
254 "configs" PATH_SEPARATOR "texture_database_downloaded.terr3d",
255 tmp2.dump(4));
256}
257
258void TextureStore::DeleteTexture(int id)
259{
260 TextureStoreItem item = textureStoreItems[id];
261 DeleteFileT(textureStoreItems[id].abledo);
262 DeleteFileT(textureStoreItems[id].ao);
263 DeleteFileT(textureStoreItems[id].normal);
264 DeleteFileT(textureStoreItems[id].metallic);
265 DeleteFileT(textureStoreItems[id].roughness);
266 DeleteFileT(textureStoreItems[id].arm);
267 DeleteFileT(textureStoreItems[id].baseDir + PATH_SEPARATOR "displacement.png"); // TEMPORARY
268 textureStoreItems[id].downloaded = false;
269 downloadedTextureStoreItems.erase(std::find(downloadedTextureStoreItems.begin(), downloadedTextureStoreItems.end(), id));
270 SaveDownloadsDatabase();
271}
272
273void TextureStore::DownloadTexture(int id, int res)
274{
275 std::string tmpStr = FetchURL("https://api.polyhaven.com", "/files/" + textureStoreItems[id].name);
276 nlohmann::json tmpJ;
277
278 try
279 {
280 tmpJ = nlohmann::json::parse(tmpStr);
281 }
282
283 catch(...)
284 {
285 Log("Failed to download texture : " + textureStoreItems[id].name);
286 return;
287 }
288
289 std::string baseDir = GetExecutableDir() + PATH_SEPARATOR "Data"
290 PATH_SEPARATOR "textures" PATH_SEPARATOR + textureStoreItems[id].name;
291 MkDir(baseDir);
292 textureStoreItems[id].baseDir = baseDir;
293 tmpStr = tmpJ["Diffuse"][std::to_string(res) + "k"]["png"]["url"];
294 DownloadFile("https://dl.polyhaven.org", tmpStr.substr(24), baseDir + PATH_SEPARATOR "albedo.png");
295 textureStoreItems[id].abledo = baseDir + PATH_SEPARATOR "albedo.png";
296 tmpStr = tmpJ["nor_gl"][std::to_string(res) + "k"]["png"]["url"];
297 DownloadFile("https://dl.polyhaven.org", tmpStr.substr(24), baseDir + PATH_SEPARATOR "normal.png");
298 textureStoreItems[id].normal = baseDir + PATH_SEPARATOR "normal.png";
299 tmpStr = tmpJ["Rough"][std::to_string(res) + "k"]["png"]["url"];
300 DownloadFile("https://dl.polyhaven.org", tmpStr.substr(24), baseDir + PATH_SEPARATOR "roughness.png");
301 textureStoreItems[id].roughness = baseDir + PATH_SEPARATOR "roughness.png";
302 tmpStr = tmpJ["AO"][std::to_string(res) + "k"]["png"]["url"];
303 DownloadFile("https://dl.polyhaven.org", tmpStr.substr(24), baseDir + PATH_SEPARATOR "ao.png");
304 textureStoreItems[id].ao = baseDir + PATH_SEPARATOR "ao.png";
305 tmpStr = tmpJ["arm"][std::to_string(res) + "k"]["png"]["url"];
306 DownloadFile("https://dl.polyhaven.org", tmpStr.substr(24), baseDir + PATH_SEPARATOR "arm.png");
307 textureStoreItems[id].arm = baseDir + PATH_SEPARATOR "arm.png";
308 tmpStr = tmpJ["Displacement"][std::to_string(res) + "k"]["png"]["url"];
309 DownloadFile("https://dl.polyhaven.org", tmpStr.substr(24), baseDir + PATH_SEPARATOR "displacement.png");
310 textureStoreItems[id].downloaded = true;
311 downloadedTextureStoreItems.push_back(id);
312 SaveDownloadsDatabase();
313}
314
315void TextureStore::ShowAllTexturesSettings()
316{
317 int searchLength = strlen(searchStr);
318 ImGui::Columns(4, NULL);
319 float width = ImGui::GetContentRegionAvail().x;
320
321 if(width <= 5)
322 {
323 width = 50;
324 }
325
326 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.3f, 0.3f, 0.3f, 0.7f));
327
328 for(int i=0; i<textureStoreItems.size(); i++)
329 {
330 bool tmp = false;
331 TextureStoreItem &item = textureStoreItems[i];
332
333 if(searchLength == 0 || strcasestr(item.name.c_str(), searchStr) != NULL)
334 {
335 tmp = item.downloaded;
336
337
338 if(item.downloaded)
339 {
340 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.5f, 0.5f, 0.5f, 0.7f));
341 }
342
343 ImGui::PushID(item.name.data());
344 ImGui::BeginChild("##texture_thumb", ImVec2(width, 300), true);
345 ImGui::Image((ImTextureID)item.texThumbnail->GetRendererID(), ImVec2(width, 150));
346 ImGui::Text(item.name.data());
347
348 if(!item.downloaded)
349 {
350 if(ImGui::Button("Download 1K"))
351 {
352 DownloadTexture(i, 1);
353 tmp = false;
354 }
355
356 if(ImGui::Button("Download 2K"))
357 {
358 DownloadTexture(i, 2);
359 tmp = false;
360 }
361
362 if(ImGui::Button("Download 4K"))
363 {
364 DownloadTexture(i, 4);
365 tmp = false;
366 }
367 }
368
369 else
370 {
371 static bool tmp = false;
372
373
374 TEXTURE_STORE_ITEM_DND("Albedo", abledo)
375 TEXTURE_STORE_ITEM_DND("Normal", normal)
376 TEXTURE_STORE_ITEM_DND("Metallic", metallic)
377 TEXTURE_STORE_ITEM_DND("Roughness", roughness)
378 TEXTURE_STORE_ITEM_DND("AO", ao)
379 TEXTURE_STORE_ITEM_DND("ARM", arm)
380
381
382 if(ImGui::Button("Delete"))
383 {
384 DeleteTexture(i);
385 }
386 }
387
388 //ImGui::Text("Downloads : %d", item.download_count);
389 //ImGui::Text("Authors :");
390 //for(auto author : item.authours)
391 //{
392 // ImGui::BulletText(author.c_str());
393 //}
394 ImGui::EndChild();
395 ImGui::PopID();
396
397 if(tmp)
398 {
399 ImGui::PopStyleColor();
400 }
401
402 ImGui::NextColumn();
403 }
404 }
405
406 ImGui::PopStyleColor();
407 ImGui::Columns(1);
408}
409
410void TextureStore::ShowDownloadedTexturesSettings()
411{
412 ImGui::Columns(4, NULL);
413 float width = ImGui::GetContentRegionAvail().x;
414
415 if(width <= 5)
416 {
417 width = 50;
418 }
419
420 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.5f, 0.5f, 0.5f, 0.7f));
421
422 for(int i=0; i<downloadedTextureStoreItems.size(); i++)
423 {
424 TextureStoreItem &item = textureStoreItems[downloadedTextureStoreItems[i]];
425 ImGui::PushID(i);
426 ImGui::BeginChild("##texture_thumb", ImVec2(width, 300), true);
427 ImGui::Image((ImTextureID)item.texThumbnail->GetRendererID(), ImVec2(width, 120));
428 ImGui::Text(item.name.c_str());
429
430 if(ImGui::Button("Delete##DTS"))
431 {
432 DeleteTexture(downloadedTextureStoreItems[i]);
433 }
434
435 //ImGui::Text("Downloads : %d", item.download_count);
436 //ImGui::Text("Authors :");
437 //for(auto author : item.authours)
438 //{
439 // ImGui::BulletText(author.c_str());
440 //}
441 ImGui::EndChild();
442 ImGui::PopID();
443 ImGui::NextColumn();
444 }
445
446 ImGui::PopStyleColor();
447 ImGui::Columns(1);
448}
449
450void TextureStore::ShowSettings(bool *pOpen)
451{
452 ImGui::Begin("Textute Store Settings", pOpen);
453 ImGui::InputTextWithHint("##TextureStorePolyHavenSearch", "Search ...", searchStr, 4096);
454 ImGui::Separator();
455
456 if (ImGui::BeginTabBar("##textureStoreTabBar"))
457 {
458 if (ImGui::BeginTabItem("All Textures"))
459 {
460 ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 12);
461 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0f, 6.0f));
462 ShowAllTexturesSettings();
463 ImGui::PopStyleVar();
464 ImGui::PopStyleVar();
465 ImGui::EndTabItem();
466 }
467
468 if (ImGui::BeginTabItem("Downloaded Textures"))
469 {
470 ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 12);
471 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0f, 6.0f));
472 ShowDownloadedTexturesSettings();
473 ImGui::PopStyleVar();
474 ImGui::PopStyleVar();
475 ImGui::EndTabItem();
476 }
477
478 if (ImGui::BeginTabItem("About"))
479 {
480 ImGui::Text("This texture store provides collection of free PBR textures.\nThis is powered by Polyhaven.");
481 ImGui::EndTabItem();
482 }
483
484 ImGui::EndTabBar();
485 }
486
487 ImGui::End();
488}
489
490TextureStore::TextureStore(ApplicationState *as)
491{
492 sInstance = this;
493 uid = GenerateId(32);
494 memset(searchStr, 0, sizeof(searchStr) / sizeof(searchStr[0]));
495 appState = as;
496 LoadTextureDatabase();
497 std::thread t([&]()
498 {
499 VerifyTextureThumbs();
500 });
501 t.detach();
502 LoadTextureThumbs();
503}
504
505TextureStore::~TextureStore()
506{
507 for(auto &it : textureStoreItems)
508 {
509 delete it.texThumbnail;
510 }
511}
static JSON_HEDLEY_WARN_UNUSED_RESULT basic_json parse(InputType &&i, const parser_callback_t cb=nullptr, const bool allow_exceptions=true, const bool ignore_comments=false)
deserialize from a compatible input
Definition: json.hpp:24605
iterator begin() noexcept
returns an iterator to the first element
Definition: json.hpp:22377
string_t dump(const int indent=-1, const char indent_char=' ', const bool ensure_ascii=false, const error_handler_t error_handler=error_handler_t::strict) const
serialization
Definition: json.hpp:20117
iterator end() noexcept
returns an iterator to one past the last element
Definition: json.hpp:22448
iterator find(KeyT &&key)
find an element in a JSON object
Definition: json.hpp:22223
a class to store JSON values
Definition: json.hpp:17860
exception indicating a parse error
Definition: json.hpp:2788
basic_json<> json
default JSON class
Definition: json.hpp:3411