lllocalgltfmaterials.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. /**
  2. * @file lllocalgltfmaterials.cpp
  3. * @brief LLLocalGLTFMaterial and HBFloaterLocalMaterial classes implementation
  4. *
  5. * $LicenseInfo:firstyear=2022&license=viewergpl$
  6. *
  7. * Copyright (c) 2022, Linden Research, Inc. (LLLocalGLTFMaterial)
  8. * Copyright (c) 2023, Henri Beauchamp. (HBFloaterLocalMaterial)
  9. *
  10. * Second Life Viewer Source Code
  11. * The source code in this file ("Source Code") is provided by Linden Lab
  12. * to you under the terms of the GNU General Public License, version 2.0
  13. * ("GPL"), unless you have obtained a separate licensing agreement
  14. * ("Other License"), formally executed by you and Linden Lab. Terms of
  15. * the GPL can be found in doc/GPL-license.txt in this distribution, or
  16. * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  17. *
  18. * There are special exceptions to the terms and conditions of the GPL as
  19. * it is applied to this Source Code. View the full text of the exception
  20. * in the file doc/FLOSS-exception.txt in this software distribution, or
  21. * online at
  22. * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  23. *
  24. * By copying, modifying or distributing this software, you acknowledge
  25. * that you have read and understood your obligations described above,
  26. * and agree to abide by those obligations.
  27. *
  28. * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  29. * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  30. * COMPLETENESS OR PERFORMANCE.
  31. * $/LicenseInfo$
  32. */
  33. #include "llviewerprecompiledheaders.h"
  34. #include <time.h>
  35. #include <ctime>
  36. #include "lllocalgltfmaterials.h"
  37. #include "llbutton.h"
  38. #include "llcheckboxctrl.h"
  39. #include "lldir.h"
  40. #include "llimage.h"
  41. #include "llnotifications.h"
  42. #include "llscrolllistctrl.h"
  43. #include "lltextureentry.h"
  44. #include "lluictrlfactory.h"
  45. #include "llinventoryicon.h"
  46. #include "llmaterialmgr.h"
  47. #include "llpreviewmaterial.h"
  48. #include "lltinygltfhelper.h"
  49. #include "llviewertexture.h"
  50. constexpr F32 LL_LOCAL_TIMER_HEARTBEAT = 3.f;
  51. constexpr U32 LL_LOCAL_UPDATE_RETRIES = 5;
  52. ///////////////////////////////////////////////////////////////////////////////
  53. // LLLocalGLTFMaterial class
  54. ///////////////////////////////////////////////////////////////////////////////
  55. // Static members
  56. LLLocalGLTFMaterial::list_t LLLocalGLTFMaterial::sMaterialList;
  57. S32 LLLocalGLTFMaterial::sMaterialsListVersion = 0;
  58. LLLocalGLTFMaterialTimer LLLocalGLTFMaterial::sTimer;
  59. LLLocalGLTFMaterial::LLLocalGLTFMaterial(const std::string& fname, S32 index)
  60. : mFilename(fname),
  61. mLastModified(0),
  62. mLinkStatus(LS_ON),
  63. mUpdateRetries(LL_LOCAL_UPDATE_RETRIES),
  64. mMaterialIndex(index)
  65. {
  66. mTrackingID.generate();
  67. mShortName = gDirUtil.getBaseFileName(fname, true);
  68. std::string ext = gDirUtil.getExtension(fname);
  69. if (ext == "gltf")
  70. {
  71. mExtension = ET_MATERIAL_GLTF;
  72. }
  73. else if (ext == "glb")
  74. {
  75. mExtension = ET_MATERIAL_GLB;
  76. }
  77. else
  78. {
  79. llwarns << "Not a valid file extension for GTLF material file: "
  80. << fname << " - Aborted." << llendl;
  81. }
  82. }
  83. //static
  84. void LLLocalGLTFMaterial::cleanupClass()
  85. {
  86. sMaterialList.clear();
  87. }
  88. bool LLLocalGLTFMaterial::updateSelf()
  89. {
  90. if (mLinkStatus != LS_ON)
  91. {
  92. return false;
  93. }
  94. if (!LLFile::exists(mFilename))
  95. {
  96. mLinkStatus = LS_BROKEN;
  97. materialBegin();
  98. materialComplete(true);
  99. LLSD args;
  100. args["FNAME"] = mFilename;
  101. gNotifications.add("LocalBitmapsUpdateFileNotFound", args);
  102. return false;
  103. }
  104. // Verifying that the file has indeed been modified
  105. time_t new_last_modified = LLFile::lastModidied(mFilename);
  106. if (mLastModified == new_last_modified)
  107. {
  108. return false;
  109. }
  110. if (loadMaterial())
  111. {
  112. // Decode is successful, we can safely proceed.
  113. if (mWorldID.isNull())
  114. {
  115. mWorldID.generate();
  116. }
  117. mLastModified = new_last_modified;
  118. // addMaterial() will replace material with a new pointer if value
  119. // already exists but we are reusing existing pointer, so it should
  120. // add only.
  121. gGLTFMaterialList.addMaterial(mWorldID, this);
  122. mUpdateRetries = LL_LOCAL_UPDATE_RETRIES;
  123. for (LLTextureEntry* entry : mTextureEntries)
  124. {
  125. // Normally a change in applied material id is supposed to drop
  126. // overrides thus reset material, but local materials currently
  127. // reuse their existing asset id, and purpose is to preview how
  128. // material will work in-world, overrides included, so do an
  129. // override to render update instead.
  130. LLGLTFMaterial* override_mat = entry->getGLTFMaterialOverride();
  131. if (override_mat)
  132. {
  133. // Do not create a new material, reuse existing pointer
  134. LLFetchedGLTFMaterial* render_mat =
  135. (LLFetchedGLTFMaterial*)entry->getGLTFRenderMaterial();
  136. if (render_mat)
  137. {
  138. *render_mat = *this;
  139. render_mat->applyOverride(*override_mat);
  140. }
  141. }
  142. }
  143. materialBegin();
  144. materialComplete(true);
  145. return true;
  146. }
  147. // If decoding failed, we get here and it will attempt to decode it in the
  148. // next cycles/ until mUpdateRetries runs out. this is done because some
  149. // software lock the material while writing to it
  150. if (mUpdateRetries)
  151. {
  152. --mUpdateRetries;
  153. }
  154. else
  155. {
  156. mLinkStatus = LS_BROKEN;
  157. materialBegin();
  158. materialComplete(false);
  159. LLSD args;
  160. args["FNAME"] = mFilename;
  161. args["NRETRIES"] = LL_LOCAL_UPDATE_RETRIES;
  162. gNotifications.add("LocalBitmapsUpdateFailedFinal", args);
  163. }
  164. return false;
  165. }
  166. bool LLLocalGLTFMaterial::loadMaterial()
  167. {
  168. if (mExtension != ET_MATERIAL_GLTF && mExtension != ET_MATERIAL_GLB)
  169. {
  170. mLinkStatus = LS_BROKEN;
  171. materialBegin();
  172. materialComplete(false);
  173. return false;
  174. }
  175. std::string filename_lc = mFilename;
  176. LLStringUtil::toLower(filename_lc);
  177. tinygltf::Model model;
  178. if (!LLTinyGLTFHelper::loadModel(mFilename, model))
  179. {
  180. return false;
  181. }
  182. // Might be a good idea to make these textures into local textures
  183. std::string mat_name;
  184. if (!LLTinyGLTFHelper::getMaterialFromModel(mFilename, model,
  185. mMaterialIndex, this,
  186. mat_name))
  187. {
  188. return false;
  189. }
  190. if (!mat_name.empty())
  191. {
  192. mShortName = gDirUtil.getBaseFileName(filename_lc, true) + " (" +
  193. mat_name + ")";
  194. }
  195. return true;
  196. }
  197. S32 LLLocalGLTFMaterial::addUnit(const std::string& filename)
  198. {
  199. tinygltf::Model model;
  200. LLTinyGLTFHelper::loadModel(filename, model);
  201. S32 materials_in_file = model.materials.size();
  202. if (materials_in_file <= 0)
  203. {
  204. return 0;
  205. }
  206. S32 loaded_materials = 0;
  207. for (S32 i = 0; i < materials_in_file; ++i)
  208. {
  209. // *TODO: this is rather inefficient: files will be spammed with
  210. // separate loads and date checks. Find a way to improve this. Maybe
  211. // doUpdates() should be checking individual files.
  212. LLPointer<LLLocalGLTFMaterial> matp =
  213. new LLLocalGLTFMaterial(filename, i);
  214. // Load material from file
  215. if (matp && matp->updateSelf())
  216. {
  217. sMaterialList.emplace_back(matp);
  218. ++loaded_materials;
  219. }
  220. else
  221. {
  222. matp = NULL;
  223. LLSD args;
  224. args["FNAME"] = filename;
  225. gNotifications.add("LocalGLTFVerifyFail", args);
  226. }
  227. }
  228. return loaded_materials;
  229. }
  230. //static
  231. void LLLocalGLTFMaterial::addUnitsCallback(HBFileSelector::ELoadFilter type,
  232. std::deque<std::string>& files,
  233. void*)
  234. {
  235. bool updated = false;
  236. std::string filename;
  237. while (!files.empty())
  238. {
  239. filename = files.front();
  240. files.pop_front();
  241. if (!filename.empty())
  242. {
  243. sTimer.stopTimer();
  244. if (addUnit(filename))
  245. {
  246. updated = true;
  247. }
  248. sTimer.startTimer();
  249. }
  250. }
  251. if (updated)
  252. {
  253. ++sMaterialsListVersion;
  254. }
  255. }
  256. //static
  257. void LLLocalGLTFMaterial::addUnits()
  258. {
  259. HBFileSelector::loadFiles(HBFileSelector::FFLOAD_GLTF, addUnitsCallback);
  260. }
  261. //static
  262. void LLLocalGLTFMaterial::delUnit(const LLUUID& tracking_id)
  263. {
  264. bool updated = false;
  265. for (list_t::iterator iter = sMaterialList.begin(),
  266. end = sMaterialList.end();
  267. iter != end; )
  268. {
  269. list_t::iterator curiter = iter++;
  270. LLLocalGLTFMaterial* matp = *curiter;
  271. if (matp->getTrackingID() == tracking_id)
  272. {
  273. // std::list:erase() preserves all iterators but curiter
  274. sMaterialList.erase(curiter);
  275. delete matp;
  276. updated = true;
  277. }
  278. }
  279. if (updated)
  280. {
  281. ++sMaterialsListVersion;
  282. }
  283. }
  284. //static
  285. const LLUUID& LLLocalGLTFMaterial::getWorldID(const LLUUID& tracking_id)
  286. {
  287. for (list_t::const_iterator it = sMaterialList.begin(),
  288. end = sMaterialList.end();
  289. it != end; ++it)
  290. {
  291. const LLLocalGLTFMaterial* matp = (*it).get();
  292. if (matp && matp->getTrackingID() == tracking_id)
  293. {
  294. return matp->getWorldID();
  295. }
  296. }
  297. return LLUUID::null;
  298. }
  299. //static
  300. bool LLLocalGLTFMaterial::isLocal(const LLUUID& world_id)
  301. {
  302. for (list_t::const_iterator it = sMaterialList.begin(),
  303. end = sMaterialList.end();
  304. it != end; ++it)
  305. {
  306. const LLLocalGLTFMaterial* matp = (*it).get();
  307. if (matp && matp->getWorldID() == world_id)
  308. {
  309. return true;
  310. }
  311. }
  312. return false;
  313. }
  314. //static
  315. const std::string& LLLocalGLTFMaterial::getFilenameAndIndex(const LLUUID& tid,
  316. S32& index)
  317. {
  318. for (list_t::const_iterator it = sMaterialList.begin(),
  319. end = sMaterialList.end();
  320. it != end; ++it)
  321. {
  322. const LLLocalGLTFMaterial* matp = (*it).get();
  323. if (matp && matp->getTrackingID() == tid)
  324. {
  325. index = matp->getIndexInFile();
  326. return matp->getFilename();
  327. }
  328. }
  329. // Not found.
  330. index = 0;
  331. return LLStringUtil::null;
  332. }
  333. //static
  334. void LLLocalGLTFMaterial::doUpdates()
  335. {
  336. // Preventing theoretical overlap in cases with huge number of loaded
  337. // images.
  338. sTimer.stopTimer();
  339. for (list_t::iterator it = sMaterialList.begin(),
  340. end = sMaterialList.end();
  341. it != end; ++it)
  342. {
  343. (*it)->updateSelf();
  344. }
  345. sTimer.startTimer();
  346. }
  347. ///////////////////////////////////////////////////////////////////////////////
  348. // LLLocalGLTFMaterialTimer class
  349. ///////////////////////////////////////////////////////////////////////////////
  350. LLLocalGLTFMaterialTimer::LLLocalGLTFMaterialTimer()
  351. : LLEventTimer(LL_LOCAL_TIMER_HEARTBEAT)
  352. {
  353. }
  354. void LLLocalGLTFMaterialTimer::startTimer()
  355. {
  356. mEventTimer.start();
  357. }
  358. void LLLocalGLTFMaterialTimer::stopTimer()
  359. {
  360. mEventTimer.stop();
  361. }
  362. bool LLLocalGLTFMaterialTimer::isRunning()
  363. {
  364. return mEventTimer.getStarted();
  365. }
  366. bool LLLocalGLTFMaterialTimer::tick()
  367. {
  368. LLLocalGLTFMaterial::doUpdates();
  369. return false;
  370. }
  371. ///////////////////////////////////////////////////////////////////////////////
  372. // HBFloaterLocalMaterial class
  373. ///////////////////////////////////////////////////////////////////////////////
  374. HBFloaterLocalMaterial::HBFloaterLocalMaterial(LLView* ownerp, callback_t cb,
  375. void* userdata)
  376. : mCallback(cb),
  377. mCallbackUserdata(userdata),
  378. mLastListVersion(-1)
  379. {
  380. LLUICtrlFactory::getInstance()->buildFloater(this,
  381. "floater_local_material.xml");
  382. // Note: at this point postBuild() has been called and returned.
  383. LLView* parentp = ownerp;
  384. // Search for our owner's parent floater and register as dependent of
  385. // it if found.
  386. while (parentp)
  387. {
  388. LLFloater* floaterp = parentp->asFloater();
  389. if (floaterp)
  390. {
  391. floaterp->addDependentFloater(this);
  392. break;
  393. }
  394. parentp = parentp->getParent();
  395. }
  396. if (!parentp)
  397. {
  398. // Place ourselves in a smart way, like preview floaters...
  399. S32 left, top;
  400. gFloaterViewp->getNewFloaterPosition(&left, &top);
  401. translate(left - getRect().mLeft, top - getRect().mTop);
  402. gFloaterViewp->adjustToFitScreen(this);
  403. }
  404. }
  405. //virtual
  406. HBFloaterLocalMaterial::~HBFloaterLocalMaterial()
  407. {
  408. gFocusMgr.releaseFocusIfNeeded(this);
  409. }
  410. //virtual
  411. bool HBFloaterLocalMaterial::postBuild()
  412. {
  413. childSetAction("add_btn", onBtnAdd, this);
  414. mRemoveButton = getChild<LLButton>("remove_btn");
  415. mRemoveButton->setClickedCallback(onBtnRemove, this);
  416. mRemoveButton->setEnabled(false);
  417. mUploadButton = getChild<LLButton>("upload_btn");
  418. mUploadButton->setClickedCallback(onBtnUpload, this);
  419. mUploadButton->setEnabled(false);
  420. mSelectButton = getChild<LLButton>("select_btn");
  421. mSelectButton->setClickedCallback(onBtnSelect, this);
  422. mSelectButton->setEnabled(false);
  423. childSetAction("cancel_btn", onBtnCancel, this);
  424. mMaterialsList = getChild<LLScrollListCtrl>("materials_list");
  425. mMaterialsList->setCommitCallback(onMaterialListCommit);
  426. mMaterialsList->setCallbackUserData(this);
  427. mMaterialsList->setCommitOnSelectionChange(true);
  428. mApplyImmediatelyCheck = getChild<LLCheckBoxCtrl>("apply_immediate_check");
  429. return true;
  430. }
  431. //virtual
  432. void HBFloaterLocalMaterial::draw()
  433. {
  434. if (mLastListVersion != LLLocalGLTFMaterial::getMaterialListVersion())
  435. {
  436. mLastListVersion = LLLocalGLTFMaterial::getMaterialListVersion();
  437. mMaterialsList->clearRows();
  438. mRemoveButton->setEnabled(false);
  439. mUploadButton->setEnabled(false);
  440. mSelectButton->setEnabled(false);
  441. const LLLocalGLTFMaterial::list_t& mats =
  442. LLLocalGLTFMaterial::getMaterialList();
  443. for (LLLocalGLTFMaterial::list_t::const_iterator it = mats.begin(),
  444. end = mats.end();
  445. it != end; ++it)
  446. {
  447. const LLLocalGLTFMaterial* matp = (*it).get();
  448. if (!matp) continue; // Paranoia
  449. LLSD element;
  450. element["id"] = matp->getTrackingID();
  451. element["columns"][0]["column"] = "name";
  452. element["columns"][0]["type"] = "text";
  453. element["columns"][0]["value"] = matp->getShortName();
  454. mMaterialsList->addElement(element);
  455. }
  456. }
  457. LLFloater::draw();
  458. }
  459. //static
  460. void HBFloaterLocalMaterial::onMaterialListCommit(LLUICtrl*, void* userdata)
  461. {
  462. HBFloaterLocalMaterial* self = (HBFloaterLocalMaterial*)userdata;
  463. if (!self) return;
  464. uuid_vec_t ids = self->mMaterialsList->getSelectedIDs();
  465. S32 items = ids.size();
  466. bool has_selection = items > 0;
  467. bool has_one_selection = items == 1;
  468. // Enable/disable buttons based on selection
  469. self->mRemoveButton->setEnabled(has_selection);
  470. self->mUploadButton->setEnabled(has_selection);
  471. self->mSelectButton->setEnabled(has_one_selection);
  472. // When applying immediately, send the selected Id via the callback.
  473. if (has_one_selection && self->mApplyImmediatelyCheck->get())
  474. {
  475. const LLUUID& world_id = LLLocalGLTFMaterial::getWorldID(ids[0]);
  476. if (world_id.notNull())
  477. {
  478. self->mCallback(world_id, self->mCallbackUserdata);
  479. }
  480. }
  481. }
  482. //static
  483. void HBFloaterLocalMaterial::onBtnSelect(void* userdata)
  484. {
  485. HBFloaterLocalMaterial* self = (HBFloaterLocalMaterial*)userdata;
  486. if (!self) return;
  487. const LLUUID& world_id =
  488. LLLocalGLTFMaterial::getWorldID(self->mMaterialsList->getCurrentID());
  489. if (world_id.notNull())
  490. {
  491. self->mCallback(world_id, self->mCallbackUserdata);
  492. }
  493. self->close();
  494. }
  495. //static
  496. void HBFloaterLocalMaterial::onBtnCancel(void* userdata)
  497. {
  498. HBFloaterLocalMaterial* self = (HBFloaterLocalMaterial*)userdata;
  499. if (self)
  500. {
  501. // Send a cancel selection/revert material event.
  502. self->mCallback(LLUUID::null, self->mCallbackUserdata);
  503. self->close();
  504. }
  505. }
  506. //static
  507. void HBFloaterLocalMaterial::onBtnAdd(void*)
  508. {
  509. LLLocalGLTFMaterial::addUnits();
  510. }
  511. //static
  512. void HBFloaterLocalMaterial::onBtnRemove(void* userdata)
  513. {
  514. HBFloaterLocalMaterial* self = (HBFloaterLocalMaterial*)userdata;
  515. if (!self) return;
  516. uuid_vec_t ids = self->mMaterialsList->getSelectedIDs();
  517. if (ids.empty())
  518. {
  519. return;
  520. }
  521. for (U32 i = 0, count = ids.size(); i < count; ++i)
  522. {
  523. LLLocalGLTFMaterial::delUnit(ids[i]);
  524. }
  525. self->mRemoveButton->setEnabled(false);
  526. self->mUploadButton->setEnabled(false);
  527. self->mSelectButton->setEnabled(false);
  528. }
  529. //static
  530. void HBFloaterLocalMaterial::onBtnUpload(void* userdata)
  531. {
  532. HBFloaterLocalMaterial* self = (HBFloaterLocalMaterial*)userdata;
  533. if (!self)
  534. {
  535. return;
  536. }
  537. uuid_vec_t ids = self->mMaterialsList->getSelectedIDs();
  538. if (ids.empty())
  539. {
  540. return;
  541. }
  542. S32 index;
  543. std::string filename;
  544. for (U32 i = 0, count = ids.size(); i < count; ++i)
  545. {
  546. filename = LLLocalGLTFMaterial::getFilenameAndIndex(ids[i], index);
  547. LLPreviewMaterial::loadFromFile(filename, index);
  548. }
  549. }