ImageAsset.h 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. #pragma once
  2. //-----------------------------------------------------------------------------
  3. // Copyright (c) 2013 GarageGames, LLC
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy
  6. // of this software and associated documentation files (the "Software"), to
  7. // deal in the Software without restriction, including without limitation the
  8. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9. // sell copies of the Software, and to permit persons to whom the Software is
  10. // furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  20. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  21. // IN THE SOFTWARE.
  22. //-----------------------------------------------------------------------------
  23. #pragma once
  24. #ifndef _ASSET_BASE_H_
  25. #include "assets/assetBase.h"
  26. #endif
  27. #ifndef _ASSET_DEFINITION_H_
  28. #include "assets/assetDefinition.h"
  29. #endif
  30. #ifndef _STRINGUNIT_H_
  31. #include "string/stringUnit.h"
  32. #endif
  33. #ifndef _ASSET_FIELD_TYPES_H_
  34. #include "assets/assetFieldTypes.h"
  35. #endif
  36. #ifndef _ASSET_PTR_H_
  37. #include "assets/assetPtr.h"
  38. #endif
  39. #ifndef _GBITMAP_H_
  40. #include "gfx/bitmap/gBitmap.h"
  41. #endif
  42. #ifndef _GFXTEXTUREHANDLE_H_
  43. #include "gfx/gfxTextureHandle.h"
  44. #endif
  45. #ifndef _NETCONNECTION_H_
  46. #include "sim/netConnection.h"
  47. #endif
  48. #ifndef _GFXDEVICE_H_
  49. #include "gfx/gfxDevice.h"
  50. #endif
  51. #ifndef _MATTEXTURETARGET_H_
  52. #include "materials/matTextureTarget.h"
  53. #endif
  54. #include "assetMacroHelpers.h"
  55. //-----------------------------------------------------------------------------
  56. class ImageAsset : public AssetBase
  57. {
  58. typedef AssetBase Parent;
  59. typedef AssetPtr<ImageAsset> ConcreteAssetPtr;
  60. public:
  61. typedef HashMap<GFXTextureProfile*, GFXTexHandle> ImageTextureMap;
  62. /// The different types of image use cases
  63. enum ImageTypes
  64. {
  65. Albedo = 0,
  66. Normal = 1,
  67. ORMConfig = 2,
  68. GUI = 3,
  69. Roughness = 4,
  70. AO = 5,
  71. Metalness = 6,
  72. Glow = 7,
  73. Particle = 8,
  74. Decal = 9,
  75. Cubemap = 10,
  76. ImageTypeCount = 11
  77. };
  78. class Frame
  79. {
  80. public:
  81. Frame(const S32 pixelOffsetX, const S32 pixelOffsetY,
  82. const U32 pixelWidth, const U32 pixelHeight,
  83. const F32 texelWidthScale, const F32 texelHeightScale,
  84. StringTableEntry inRegionName = StringTable->EmptyString())
  85. : regionName(inRegionName)
  86. {
  87. pixelOffset.set(pixelOffsetY, pixelOffsetY);
  88. pixelSize.set(pixelWidth, pixelHeight);
  89. texelLower.set(pixelOffsetX * texelWidthScale, pixelOffsetY * texelHeightScale);
  90. texelSize.set(pixelWidth * texelWidthScale, pixelHeight * texelHeightScale);
  91. texelUpper.set(texelLower.x + texelSize.x, texelLower.y + texelSize.y);
  92. }
  93. void setFlip(bool flipX, bool flipY)
  94. {
  95. if (flipX) mSwap(texelLower.x, texelUpper.x);
  96. if (flipY) mSwap(texelLower.y, texelUpper.y);
  97. }
  98. Point2I pixelOffset;
  99. Point2I pixelSize;
  100. Point2F texelLower;
  101. Point2F texelUpper;
  102. Point2F texelSize;
  103. StringTableEntry regionName;
  104. };
  105. static StringTableEntry smNoImageAssetFallback;
  106. static StringTableEntry smNamedTargetAssetFallback;
  107. enum ImageAssetErrCode
  108. {
  109. TooManyMips = AssetErrCode::Extended,
  110. Extended
  111. };
  112. static const String mErrCodeStrings[U32(ImageAssetErrCode::Extended) - U32(Parent::Extended) + 1];
  113. static U32 getAssetErrCode(ConcreteAssetPtr checkAsset) { if (checkAsset.notNull()) return checkAsset->mLoadedState; else return 0; }
  114. static String getAssetErrstrn(U32 errCode)
  115. {
  116. if (errCode < Parent::Extended) return Parent::getAssetErrstrn(errCode);
  117. if (errCode > ImageAssetErrCode::Extended) return "undefined error";
  118. return mErrCodeStrings[errCode - Parent::Extended];
  119. };
  120. private:
  121. StringTableEntry mImageFile;
  122. bool mUseMips;
  123. bool mIsHDRImage;
  124. ImageTypes mImageType;
  125. ImageTextureMap mResourceMap;
  126. bool mIsNamedTarget;
  127. S32 mImageWidth;
  128. S32 mImageHeight;
  129. S32 mImageDepth;
  130. S32 mImageChannels;
  131. public:
  132. ImageAsset();
  133. virtual ~ImageAsset();
  134. /// Set up some global script interface stuff.
  135. static void consoleInit();
  136. /// Engine.
  137. static void initPersistFields();
  138. /// Sim
  139. bool onAdd() override;
  140. void onRemove() override;
  141. void copyTo(SimObject* object) override;
  142. /// Declare Console Object.
  143. DECLARE_CONOBJECT(ImageAsset);
  144. void _onResourceChanged(const Torque::Path& path);
  145. // asset Base load
  146. U32 load() override;
  147. void setImageFile(StringTableEntry pImageFile);
  148. inline StringTableEntry getImageFile(void) const { return mImageFile; };
  149. inline StringTableEntry getRelativeImageFile(void) const { return collapseAssetFilePath(mImageFile); };
  150. void setGenMips(const bool pGenMips);
  151. inline bool getGenMips(void) const { return mUseMips; };
  152. void setTextureHDR(const bool pIsHDR);
  153. inline bool getTextureHDR(void) const { return mIsHDRImage; };
  154. GFXTexHandle getTexture(GFXTextureProfile* requestedProfile);
  155. static StringTableEntry getImageTypeNameFromType(ImageTypes type);
  156. static ImageTypes getImageTypeFromName(StringTableEntry name);
  157. void setImageType(ImageTypes type) { mImageType = type; }
  158. ImageTypes getImageType() { return mImageType; }
  159. inline U32 getTextureBitmapWidth(void) const { return mImageWidth; }
  160. inline U32 getTextureBitmapHeight(void) const { return mImageHeight; }
  161. inline U32 getTextureBitmapDepth(void) const { return mImageDepth; }
  162. bool isNamedTarget(void) const { return mIsNamedTarget; }
  163. NamedTexTargetRef getNamedTarget(void) const { return NamedTexTarget::find(mImageFile + 1); }
  164. static U32 getAssetByFilename(StringTableEntry fileName, AssetPtr<ImageAsset>* imageAsset);
  165. static StringTableEntry getAssetIdByFilename(StringTableEntry fileName);
  166. static U32 getAssetById(StringTableEntry assetId, AssetPtr<ImageAsset>* imageAsset);
  167. static U32 getAssetById(String assetId, AssetPtr<ImageAsset>* imageAsset) { return getAssetById(assetId.c_str(), imageAsset); };
  168. void populateImage(void);
  169. const char* getImageInfo();
  170. protected:
  171. // Asset Base callback
  172. void initializeAsset(void) override;
  173. void onAssetRefresh(void) override;
  174. /// Taml callbacks.
  175. void onTamlPreWrite(void) override;
  176. void onTamlPostWrite(void) override;
  177. void onTamlCustomWrite(TamlCustomNodes& customNodes) override;
  178. void onTamlCustomRead(const TamlCustomNodes& customNodes) override;
  179. protected:
  180. // Texture file
  181. static bool setImageFile(void* obj, StringTableEntry index, StringTableEntry data) { static_cast<ImageAsset*>(obj)->setImageFile(data); return false; }
  182. static const char* getImageFile(void* obj, StringTableEntry data) { return static_cast<ImageAsset*>(obj)->getImageFile(); }
  183. static bool writeImageFile(void* obj, StringTableEntry pFieldName) { return static_cast<ImageAsset*>(obj)->getImageFile() != StringTable->EmptyString(); }
  184. // Gen mips?
  185. static bool setGenMips(void* obj, StringTableEntry index, StringTableEntry data) { static_cast<ImageAsset*>(obj)->setGenMips(dAtob(data)); return false; }
  186. static bool writeGenMips(void* obj, StringTableEntry pFieldName) { return static_cast<ImageAsset*>(obj)->getGenMips() == true; }
  187. // Texture Is Hdr?
  188. static bool setTextureHDR(void* obj, StringTableEntry index, StringTableEntry data) { static_cast<ImageAsset*>(obj)->setTextureHDR(dAtob(data)); return false; }
  189. static bool writeTextureHDR(void* obj, StringTableEntry pFieldName) { return static_cast<ImageAsset*>(obj)->getTextureHDR() == true; }
  190. };
  191. DECLARE_STRUCT(AssetPtr<ImageAsset>)
  192. DefineConsoleType(TypeImageAssetPtr, AssetPtr<ImageAsset> )
  193. typedef ImageAsset::ImageTypes ImageAssetType;
  194. DefineEnumType(ImageAssetType);
  195. #pragma region Refactor Asset Macros
  196. #define DECLARE_IMAGEASSET(className, name, profile) \
  197. private: \
  198. AssetPtr<ImageAsset> m##name##Asset; \
  199. StringTableEntry m##name##File = StringTable->EmptyString(); \
  200. public: \
  201. void _set##name(StringTableEntry _in){ \
  202. if(m##name##Asset.getAssetId() == _in) \
  203. return; \
  204. if(get##name##File() == _in) \
  205. return; \
  206. if(_in == NULL || !String::compare(_in,StringTable->EmptyString())) \
  207. { \
  208. m##name##Asset = NULL; \
  209. m##name##File = ""; \
  210. return; \
  211. } \
  212. if(!AssetDatabase.isDeclaredAsset(_in)) \
  213. { \
  214. StringTableEntry imageAssetId = StringTable->EmptyString(); \
  215. AssetQuery query; \
  216. S32 foundAssetcount = AssetDatabase.findAssetLooseFile(&query, _in); \
  217. if (foundAssetcount != 0) \
  218. { \
  219. imageAssetId = query.mAssetList[0]; \
  220. } \
  221. else if(Torque::FS::IsFile(_in) || (_in[0] == '$' || _in[0] == '#')) \
  222. { \
  223. imageAssetId = ImageAsset::getAssetIdByFilename(_in); \
  224. if (imageAssetId == ImageAsset::smNoImageAssetFallback) \
  225. { \
  226. ImageAsset* privateImage = new ImageAsset(); \
  227. privateImage->setImageFile(_in); \
  228. imageAssetId = AssetDatabase.addPrivateAsset(privateImage); \
  229. } \
  230. } \
  231. else \
  232. { \
  233. Con::warnf("%s::%s: Could not find asset for: %s using fallback", #className, #name, _in); \
  234. imageAssetId = ImageAsset::smNoImageAssetFallback; \
  235. } \
  236. m##name##Asset = imageAssetId; \
  237. m##name##File = _in; \
  238. } \
  239. else \
  240. { \
  241. m##name##Asset = _in; \
  242. m##name##File = get##name##File(); \
  243. } \
  244. }; \
  245. \
  246. inline StringTableEntry _get##name(void) const { return m##name##Asset.getAssetId(); } \
  247. GFXTexHandle get##name() { return m##name##Asset.notNull() ? m##name##Asset->getTexture(&profile) : NULL; } \
  248. AssetPtr<ImageAsset> get##name##Asset(void) { return m##name##Asset; } \
  249. static bool _set##name##Data(void* obj, const char* index, const char* data) { static_cast<className*>(obj)->_set##name(_getStringTable()->insert(data)); return false;} \
  250. StringTableEntry get##name##File(){ return m##name##Asset.notNull() ? m##name##Asset->getImageFile() : ""; }
  251. #define DECLARE_IMAGEASSET_NET(className, name, profile, mask) \
  252. private: \
  253. AssetPtr<ImageAsset> m##name##Asset; \
  254. StringTableEntry m##name##File = StringTable->EmptyString(); \
  255. public: \
  256. void _set##name(StringTableEntry _in){ \
  257. if(m##name##Asset.getAssetId() == _in) \
  258. return; \
  259. if(get##name##File() == _in) \
  260. return; \
  261. if(_in == NULL || !String::compare(_in,StringTable->EmptyString())) \
  262. { \
  263. m##name##Asset = NULL; \
  264. m##name##File = ""; \
  265. setMaskBits(mask); \
  266. return; \
  267. } \
  268. if(!AssetDatabase.isDeclaredAsset(_in)) \
  269. { \
  270. StringTableEntry imageAssetId = StringTable->EmptyString(); \
  271. AssetQuery query; \
  272. S32 foundAssetcount = AssetDatabase.findAssetLooseFile(&query, _in); \
  273. if (foundAssetcount != 0) \
  274. { \
  275. imageAssetId = query.mAssetList[0]; \
  276. } \
  277. else if(Torque::FS::IsFile(_in) || (_in[0] == '$' || _in[0] == '#')) \
  278. { \
  279. imageAssetId = ImageAsset::getAssetIdByFilename(_in); \
  280. if (imageAssetId == ImageAsset::smNoImageAssetFallback) \
  281. { \
  282. ImageAsset* privateImage = new ImageAsset(); \
  283. privateImage->setImageFile(_in); \
  284. imageAssetId = AssetDatabase.addPrivateAsset(privateImage); \
  285. } \
  286. } \
  287. else \
  288. { \
  289. Con::warnf("%s::%s: Could not find asset for: %s using fallback", #className, #name, _in); \
  290. imageAssetId = ImageAsset::smNoImageAssetFallback; \
  291. } \
  292. m##name##Asset = imageAssetId; \
  293. m##name##File = _in; \
  294. } \
  295. else \
  296. { \
  297. m##name##Asset = _in; \
  298. m##name##File = get##name##File(); \
  299. } \
  300. setMaskBits(mask); \
  301. }; \
  302. \
  303. inline StringTableEntry _get##name(void) const { return m##name##Asset.getAssetId(); } \
  304. GFXTexHandle get##name() { return m##name##Asset.notNull() ? m##name##Asset->getTexture(&profile) : NULL; } \
  305. AssetPtr<ImageAsset> get##name##Asset(void) { return m##name##Asset; } \
  306. static bool _set##name##Data(void* obj, const char* index, const char* data) { static_cast<className*>(obj)->_set##name(_getStringTable()->insert(data)); return false;} \
  307. StringTableEntry get##name##File(){ return m##name##Asset.notNull() ? m##name##Asset->getImageFile() : ""; }
  308. #define INITPERSISTFIELD_IMAGEASSET(name, consoleClass, docs) \
  309. addProtectedField(assetText(name, Asset), TypeImageAssetPtr, Offset(m##name##Asset, consoleClass), _set##name##Data, &defaultProtectedGetFn, assetDoc(name, asset docs.)); \
  310. addProtectedField(assetText(name, File), TypeFilename, Offset(m##name##File, consoleClass), _set##name##Data, &defaultProtectedGetFn, assetDoc(name, file docs.));
  311. #define DECLARE_IMAGEASSET_ARRAY(className, name, profile, max) \
  312. private: \
  313. AssetPtr<ImageAsset> m##name##Asset[max]; \
  314. StringTableEntry m##name##File[max] = {StringTable->EmptyString() }; \
  315. public: \
  316. void _set##name(StringTableEntry _in, const U32& index){ \
  317. if(m##name##Asset[index].getAssetId() == _in) \
  318. return; \
  319. if(get##name##File(index) == _in) \
  320. return; \
  321. if(_in == NULL || !String::compare(_in,StringTable->EmptyString())) \
  322. { \
  323. m##name##Asset[index] = NULL; \
  324. m##name##File[index] = ""; \
  325. return; \
  326. } \
  327. if(!AssetDatabase.isDeclaredAsset(_in)) \
  328. { \
  329. StringTableEntry imageAssetId = StringTable->EmptyString(); \
  330. AssetQuery query; \
  331. S32 foundAssetcount = AssetDatabase.findAssetLooseFile(&query, _in); \
  332. if (foundAssetcount != 0) \
  333. { \
  334. imageAssetId = query.mAssetList[0]; \
  335. } \
  336. else if(Torque::FS::IsFile(_in) || (_in[0] == '$' || _in[0] == '#')) \
  337. { \
  338. imageAssetId = ImageAsset::getAssetIdByFilename(_in); \
  339. if (imageAssetId == ImageAsset::smNoImageAssetFallback) \
  340. { \
  341. ImageAsset* privateImage = new ImageAsset(); \
  342. privateImage->setImageFile(_in); \
  343. imageAssetId = AssetDatabase.addPrivateAsset(privateImage); \
  344. } \
  345. } \
  346. else \
  347. { \
  348. Con::warnf("%s::%s: Could not find asset for: %s using fallback", #className, #name, _in); \
  349. imageAssetId = ImageAsset::smNoImageAssetFallback; \
  350. } \
  351. m##name##Asset[index] = imageAssetId; \
  352. m##name##File[index] = _in; \
  353. } \
  354. else \
  355. { \
  356. m##name##Asset[index] = _in; \
  357. m##name##File[index] = get##name##File(index); \
  358. } \
  359. }; \
  360. \
  361. inline StringTableEntry _get##name(const U32& index) const { return m##name##Asset[index].getAssetId(); } \
  362. GFXTexHandle get##name(const U32& index) { return get##name(&profile, index); } \
  363. GFXTexHandle get##name(GFXTextureProfile* requestedProfile, const U32& index) { return m##name##Asset[index].notNull() ? m##name##Asset[index]->getTexture(requestedProfile) : NULL; }\
  364. AssetPtr<ImageAsset> get##name##Asset(const U32& index) { return m##name##Asset[index]; } \
  365. static bool _set##name##Data(void* obj, const char* index, const char* data) { static_cast<className*>(obj)->_set##name(_getStringTable()->insert(data), dAtoi(index)); return false;}\
  366. StringTableEntry get##name##File(const U32& idx){ return m##name##Asset[idx].notNull() ? m##name##Asset[idx]->getImageFile() : ""; }
  367. #define DECLARE_IMAGEASSET_ARRAY_NET(className, name, profile, max, mask) \
  368. private: \
  369. AssetPtr<ImageAsset> m##name##Asset[max]; \
  370. StringTableEntry m##name##File[max] = {StringTable->EmptyString() }; \
  371. public: \
  372. void _set##name(StringTableEntry _in, const U32& index){ \
  373. if(m##name##Asset[index].getAssetId() == _in) \
  374. return; \
  375. if(get##name##File(index) == _in) \
  376. return; \
  377. if(_in == NULL || !String::compare(_in,StringTable->EmptyString())) \
  378. { \
  379. m##name##Asset[index] = NULL; \
  380. m##name##File[index] = ""; \
  381. setMaskBits(mask); \
  382. return; \
  383. } \
  384. if(!AssetDatabase.isDeclaredAsset(_in)) \
  385. { \
  386. StringTableEntry imageAssetId = StringTable->EmptyString(); \
  387. AssetQuery query; \
  388. S32 foundAssetcount = AssetDatabase.findAssetLooseFile(&query, _in); \
  389. if (foundAssetcount != 0) \
  390. { \
  391. imageAssetId = query.mAssetList[0]; \
  392. } \
  393. else if(Torque::FS::IsFile(_in) || (_in[0] == '$' || _in[0] == '#')) \
  394. { \
  395. imageAssetId = ImageAsset::getAssetIdByFilename(_in); \
  396. if (imageAssetId == ImageAsset::smNoImageAssetFallback) \
  397. { \
  398. ImageAsset* privateImage = new ImageAsset(); \
  399. privateImage->setImageFile(_in); \
  400. imageAssetId = AssetDatabase.addPrivateAsset(privateImage); \
  401. } \
  402. } \
  403. else \
  404. { \
  405. Con::warnf("%s::%s: Could not find asset for: %s using fallback", #className, #name, _in); \
  406. imageAssetId = ImageAsset::smNoImageAssetFallback; \
  407. } \
  408. m##name##Asset[index] = imageAssetId; \
  409. m##name##File[index] = _in; \
  410. } \
  411. else \
  412. { \
  413. m##name##Asset[index] = _in; \
  414. m##name##File[index] = get##name##File(index); \
  415. } \
  416. setMaskBits(mask); \
  417. }; \
  418. \
  419. inline StringTableEntry _get##name(const U32& index) const { return m##name##Asset[index].getAssetId(); } \
  420. GFXTexHandle get##name(const U32& index) { return m##name##Asset[index].notNull() ? m##name##Asset[index]->getTexture(&profile) : NULL; } \
  421. GFXTexHandle get##name(GFXTextureProfile* requestedProfile, const U32& index) { return m##name##Asset[index].notNull() ? m##name##Asset[index]->getTexture(requestedProfile) : NULL; }\
  422. AssetPtr<ImageAsset> get##name##Asset(const U32& index) { return m##name##Asset[index]; } \
  423. static bool _set##name##Data(void* obj, const char* index, const char* data) { static_cast<className*>(obj)->_set##name(_getStringTable()->insert(data), dAtoi(index)); return false;}\
  424. StringTableEntry get##name##File(const U32& idx){ return m##name##Asset[idx].notNull() ? m##name##Asset[idx]->getImageFile() : ""; }
  425. #define INITPERSISTFIELD_IMAGEASSET_ARRAY(name, arraySize, consoleClass, docs) \
  426. addProtectedField(assetText(name, Asset), TypeImageAssetPtr, Offset(m##name##Asset, consoleClass), _set##name##Data, &defaultProtectedGetFn, arraySize, assetDoc(name, asset docs.));
  427. #define DEF_IMAGEASSET_ARRAY_BINDS(className,name, max)\
  428. DefineEngineMethod(className, get##name, const char*, (S32 index), , "get name")\
  429. {\
  430. return object->get##name##Asset(index).notNull() ? object->get##name##Asset(index)->getImageFile() : ""; \
  431. }\
  432. DefineEngineMethod(className, get##name##Asset, const char*, (S32 index), , assetText(name, asset reference))\
  433. {\
  434. if(index >= max || index < 0)\
  435. return "";\
  436. return object->_get##name(index); \
  437. }\
  438. DefineEngineMethod(className, set##name, void, (const char* map, S32 index), , assetText(name,assignment. first tries asset then flat file.))\
  439. {\
  440. object->_set##name(StringTable->insert(map), index);\
  441. }
  442. #pragma endregion