GenericOnDemandTextureLoaderInspector.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. /******************************************************************************
  2. * Spine Runtimes License Agreement
  3. * Last updated July 28, 2023. Replaces all prior versions.
  4. *
  5. * Copyright (c) 2013-2023, Esoteric Software LLC
  6. *
  7. * Integration of the Spine Runtimes into software or otherwise creating
  8. * derivative works of the Spine Runtimes is permitted under the terms and
  9. * conditions of Section 2 of the Spine Editor License Agreement:
  10. * http://esotericsoftware.com/spine-editor-license
  11. *
  12. * Otherwise, it is permitted to integrate the Spine Runtimes into software or
  13. * otherwise create derivative works of the Spine Runtimes (collectively,
  14. * "Products"), provided that each user of the Products must obtain their own
  15. * Spine Editor license and redistribution of the Products in any form must
  16. * include this license and copyright notice.
  17. *
  18. * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
  19. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
  24. * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
  27. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *****************************************************************************/
  29. #if UNITY_2017_2_OR_NEWER
  30. #define NEWPLAYMODECALLBACKS
  31. #endif
  32. #define SPINE_OPTIONAL_ON_DEMAND_LOADING
  33. #if SPINE_OPTIONAL_ON_DEMAND_LOADING
  34. using System;
  35. using System.Collections.Generic;
  36. using System.Linq;
  37. using UnityEditor;
  38. using UnityEngine;
  39. namespace Spine.Unity.Editor {
  40. /// <summary>
  41. /// Base class for GenericOnDemandTextureLoader Inspector subclasses.
  42. /// For reference, see the <see cref="AddressablesTextureLoaderInspector"/> class available
  43. /// in the com.esotericsoftware.spine.addressables UPM package.
  44. /// </summary>
  45. /// <typeparam name="TargetReference">The implementation struct which holds an on-demand loading reference
  46. /// to the target texture to be loaded, derived from ITargetTextureReference.</typeparam>
  47. /// <typeparam name="TextureRequest">The implementation struct covering a single texture loading request,
  48. /// derived from IOnDemandRequest</typeparam>
  49. [InitializeOnLoad]
  50. [CustomEditor(typeof(GenericOnDemandTextureLoader<,>)), CanEditMultipleObjects]
  51. public abstract class GenericOnDemandTextureLoaderInspector<TargetReference, TextureRequest> : UnityEditor.Editor
  52. where TargetReference : Spine.Unity.ITargetTextureReference
  53. where TextureRequest : Spine.Unity.IOnDemandRequest {
  54. protected SerializedProperty atlasAsset;
  55. protected SerializedProperty skeletonDataAsset;
  56. protected SerializedProperty maxPlaceholderSize;
  57. protected SerializedProperty placeholderMap;
  58. protected SerializedProperty unloadAfterSecondsUnused;
  59. static protected bool placeholdersFoldout = true;
  60. protected SerializedProperty loadedDataAtMaterial;
  61. protected GenericOnDemandTextureLoader<TargetReference, TextureRequest> loader;
  62. protected GUIContent placeholderTexturesLabel;
  63. /// <summary>
  64. /// Called via InitializeOnLoad attribute upon Editor startup or compilation.
  65. /// </summary>
  66. static GenericOnDemandTextureLoaderInspector () {
  67. RegisterPlayModeChangedCallbacks();
  68. }
  69. public static void RegisterPlayModeChangedCallbacks () {
  70. #if NEWPLAYMODECALLBACKS
  71. EditorApplication.playModeStateChanged -= OnPlaymodeChanged;
  72. EditorApplication.playModeStateChanged += OnPlaymodeChanged;
  73. #else
  74. EditorApplication.playmodeStateChanged -= OnPlaymodeChanged;
  75. EditorApplication.playmodeStateChanged += OnPlaymodeChanged;
  76. #endif
  77. }
  78. /// <summary>
  79. /// Derive your implementation subclass of this class and implement the respective abstract methods.
  80. /// Note: Unfortunately the Unity menu entries are created via static methods, so this is a workaround
  81. /// to provide virtual static functions in old C# versions.
  82. /// </summary>
  83. public abstract class StaticMethodImplementations {
  84. public abstract GenericOnDemandTextureLoader<TargetReference, TextureRequest> GetOrCreateLoader (string loaderPath);
  85. /// <summary>
  86. /// Returns the on-demand loader asset's filename suffix. The filename
  87. /// is determined by the AtlasAsset, while this suffix replaces the "_Atlas" suffix.
  88. /// When set to e.g. "_Addressable", the loader asset created for
  89. /// the "Skeleton_Atlas" asset is named "Skeleton_Addressable".
  90. /// </summary>
  91. public virtual string LoaderSuffix { get { return "_Loader"; } }
  92. public abstract bool SetupOnDemandLoadingReference (
  93. ref TargetReference targetTextureReference, Texture targetTexture);
  94. /// <summary>
  95. /// Create a context menu wrapper in the main class for this generic implementation using the code below.
  96. /// <code>
  97. /// [MenuItem("CONTEXT/AtlasAssetBase/Add YourSubclass Loader")]
  98. /// static void AddYourSubclassLoader (MenuCommand cmd) {
  99. /// if (staticMethods == null)
  100. /// staticMethods = new YourSubclassMethodImplementations ();
  101. /// staticMethods.AddOnDemandLoader(cmd);
  102. /// }
  103. /// </code>
  104. /// </summary>
  105. public virtual void AddOnDemandLoader (MenuCommand cmd) {
  106. AtlasAssetBase atlasAsset = cmd.context as AtlasAssetBase;
  107. Debug.Log("Adding On-Demand Loader for " + atlasAsset.name, atlasAsset);
  108. if (atlasAsset.OnDemandTextureLoader != null) {
  109. Debug.LogWarning("AtlasAsset On-Demand TextureLoader is already set. " +
  110. "Please clear it if you want to assign a different one.");
  111. return;
  112. }
  113. atlasAsset.TextureLoadingMode = AtlasAssetBase.LoadingMode.OnDemand;
  114. EditorUtility.SetDirty(atlasAsset);
  115. string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset);
  116. string loaderPath = atlasAssetPath.Replace(AssetUtility.AtlasSuffix, LoaderSuffix);
  117. GenericOnDemandTextureLoader<TargetReference, TextureRequest> loader = staticMethods.GetOrCreateLoader(loaderPath);
  118. staticMethods.SetupForAtlasAsset(loader, atlasAsset);
  119. EditorUtility.SetDirty(loader);
  120. AssetDatabase.SaveAssets();
  121. }
  122. public virtual void SetupForAtlasAsset (GenericOnDemandTextureLoader<TargetReference, TextureRequest> loader, AtlasAssetBase atlasAsset) {
  123. if (loader.placeholderMap != null && loader.placeholderMap.Length > 0) {
  124. IEnumerable<Material> modifiedMaterials;
  125. loader.AssignTargetTextures(out modifiedMaterials); // start from normal textures
  126. }
  127. if (atlasAsset == null) {
  128. Debug.LogError("AddressableTextureLoader.SetupForAtlasAsset: atlasAsset was null, aborting setup.", atlasAsset);
  129. return;
  130. }
  131. int materialCount = atlasAsset.MaterialCount;
  132. loader.placeholderMap = new GenericOnDemandTextureLoader<TargetReference, TextureRequest>.PlaceholderMaterialMapping[materialCount];
  133. GenericOnDemandTextureLoader<TargetReference, TextureRequest>.PlaceholderMaterialMapping[] materialMap = loader.placeholderMap;
  134. atlasAsset.OnDemandTextureLoader = loader;
  135. int maxPlaceholderSize = loader.maxPlaceholderSize;
  136. int i = 0;
  137. foreach (Material targetMaterial in atlasAsset.Materials) {
  138. Texture targetTexture = targetMaterial.mainTexture;
  139. materialMap[i].textures = new GenericOnDemandTextureLoader<TargetReference, TextureRequest>.PlaceholderTextureMapping[1]; // Todo: currently only main texture is supported.
  140. int textureIndex = 0;
  141. GenericOnDemandTextureLoader<TargetReference, TextureRequest>.PlaceholderTextureMapping[] texturesMap = materialMap[i].textures;
  142. if (texturesMap[textureIndex].placeholderTexture != targetTexture) { // otherwise already set to placeholder
  143. SetupOnDemandLoadingReference(ref texturesMap[textureIndex].targetTextureReference, targetTexture);
  144. texturesMap[textureIndex].placeholderTexture = CreatePlaceholderTextureFor(targetTexture, maxPlaceholderSize, loader);
  145. }
  146. ++i;
  147. }
  148. // assign late since CreatePlaceholderTextureFor(texture) method above might save assets and clear these values.
  149. loader.placeholderMap = materialMap;
  150. loader.atlasAsset = atlasAsset;
  151. if (loader.skeletonDataAsset == null)
  152. AssignSkeletonDataAsset(loader, atlasAsset);
  153. }
  154. protected void AssignSkeletonDataAsset (GenericOnDemandTextureLoader<TargetReference, TextureRequest> loader, AtlasAssetBase atlasAsset) {
  155. string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset);
  156. string parentFolder = System.IO.Path.GetDirectoryName(atlasAssetPath);
  157. SkeletonDataAsset skeletonDataAsset = FindSkeletonDataAsset(parentFolder, atlasAsset);
  158. if (skeletonDataAsset) {
  159. loader.skeletonDataAsset = skeletonDataAsset;
  160. return;
  161. }
  162. string nextParentFolder = System.IO.Path.GetDirectoryName(parentFolder);
  163. skeletonDataAsset = FindSkeletonDataAsset(nextParentFolder, atlasAsset);
  164. if (skeletonDataAsset) {
  165. loader.skeletonDataAsset = skeletonDataAsset;
  166. return;
  167. }
  168. }
  169. protected SkeletonDataAsset FindSkeletonDataAsset (string searchFolder, AtlasAssetBase atlasAsset) {
  170. string[] guids = AssetDatabase.FindAssets("t:SkeletonDataAsset", new[] { searchFolder });
  171. foreach (string guid in guids) {
  172. string assetPath = AssetDatabase.GUIDToAssetPath(guid);
  173. SkeletonDataAsset skeletonDataAsset = AssetDatabase.LoadAssetAtPath<SkeletonDataAsset>(assetPath);
  174. if (skeletonDataAsset != null) {
  175. if (skeletonDataAsset.atlasAssets.Contains(atlasAsset)) {
  176. return skeletonDataAsset;
  177. }
  178. }
  179. }
  180. return null;
  181. }
  182. public virtual Texture CreatePlaceholderTextureFor (Texture originalTexture, int maxPlaceholderSize,
  183. GenericOnDemandTextureLoader<TargetReference, TextureRequest> loader) {
  184. const string AssetFolderName = "LoadingPlaceholderAssets";
  185. string originalPath = AssetDatabase.GetAssetPath(originalTexture);
  186. string parentFolder = System.IO.Path.GetDirectoryName(originalPath);
  187. string dataPath = parentFolder + "/" + AssetFolderName;
  188. if (!AssetDatabase.IsValidFolder(dataPath)) {
  189. AssetDatabase.CreateFolder(parentFolder, AssetFolderName);
  190. }
  191. string originalTextureName = System.IO.Path.GetFileNameWithoutExtension(originalPath);
  192. string texturePath = string.Format("{0}/{1}.png",
  193. dataPath, loader.GetPlaceholderTextureName(originalTextureName));
  194. Texture placeholderTexture = AssetDatabase.LoadAssetAtPath<Texture>(texturePath);
  195. if (placeholderTexture == null) {
  196. AssetDatabase.CopyAsset(originalPath, texturePath);
  197. const bool resizePhysically = true;
  198. TextureImporter importer = (TextureImporter)TextureImporter.GetAtPath(texturePath);
  199. const string defaultPlatform = "Default";
  200. TextureImporterPlatformSettings settings = importer.GetPlatformTextureSettings(defaultPlatform);
  201. settings.maxTextureSize = maxPlaceholderSize;
  202. importer.SetPlatformTextureSettings(settings);
  203. importer.maxTextureSize = maxPlaceholderSize;
  204. importer.isReadable = resizePhysically;
  205. importer.SaveAndReimport();
  206. if (resizePhysically) {
  207. bool hasOverrides = TextureImporterUtility.DisableOverrides(importer, out List<string> disabledPlatforms);
  208. Texture2D texture2D = AssetDatabase.LoadAssetAtPath<Texture2D>(texturePath);
  209. if (texture2D) {
  210. Color[] maxTextureSizePixels = texture2D.GetPixels();
  211. // SetPixels supports only uncompressed textures using certain formats.
  212. Texture2D uncompressedTexture =
  213. new Texture2D(texture2D.width, texture2D.height, TextureFormat.RGBA32, false);
  214. uncompressedTexture.SetPixels(maxTextureSizePixels);
  215. byte[] bytes = uncompressedTexture.EncodeToPNG();
  216. string targetPath = Application.dataPath + "/../" + texturePath;
  217. System.IO.File.WriteAllBytes(targetPath, bytes);
  218. importer.isReadable = false;
  219. importer.SaveAndReimport();
  220. EditorUtility.SetDirty(uncompressedTexture);
  221. AssetDatabase.SaveAssets();
  222. }
  223. if (hasOverrides)
  224. TextureImporterUtility.EnableOverrides(importer, disabledPlatforms);
  225. }
  226. placeholderTexture = AssetDatabase.LoadAssetAtPath<Texture>(texturePath);
  227. }
  228. UnityEngine.Object folderObject = AssetDatabase.LoadAssetAtPath(dataPath, typeof(UnityEngine.Object));
  229. if (folderObject != null) {
  230. EditorGUIUtility.PingObject(folderObject);
  231. }
  232. return placeholderTexture;
  233. }
  234. }
  235. public static StaticMethodImplementations staticMethods;
  236. void OnEnable () {
  237. atlasAsset = serializedObject.FindProperty("atlasAsset");
  238. skeletonDataAsset = serializedObject.FindProperty("skeletonDataAsset");
  239. maxPlaceholderSize = serializedObject.FindProperty("maxPlaceholderSize");
  240. placeholderMap = serializedObject.FindProperty("placeholderMap");
  241. unloadAfterSecondsUnused = serializedObject.FindProperty("unloadAfterSecondsUnused");
  242. loadedDataAtMaterial = serializedObject.FindProperty("loadedDataAtMaterial");
  243. placeholderTexturesLabel = new GUIContent("Placeholder Textures");
  244. loader = (GenericOnDemandTextureLoader<TargetReference, TextureRequest>)target;
  245. if (staticMethods == null)
  246. staticMethods = CreateStaticMethodImplementations();
  247. }
  248. #if NEWPLAYMODECALLBACKS
  249. static void OnPlaymodeChanged (PlayModeStateChange mode) {
  250. bool assignTargetTextures = mode == PlayModeStateChange.EnteredEditMode;
  251. #else
  252. static void OnPlaymodeChanged () {
  253. bool assignTargetTextures = !Application.isPlaying;
  254. #endif
  255. if (assignTargetTextures) {
  256. AssignTargetTexturesAtAllLoaders();
  257. }
  258. }
  259. public static void AssignTargetTexturesAtAllLoaders () {
  260. string[] loaderAssets = AssetDatabase.FindAssets("t:OnDemandTextureLoader");
  261. foreach (string loaderAsset in loaderAssets) {
  262. string assetPath = AssetDatabase.GUIDToAssetPath(loaderAsset);
  263. OnDemandTextureLoader loader = AssetDatabase.LoadAssetAtPath<OnDemandTextureLoader>(assetPath);
  264. AssignTargetTexturesAtLoader(loader);
  265. }
  266. }
  267. public static void AssignTargetTexturesAtLoader (OnDemandTextureLoader loader) {
  268. List<Material> placeholderMaterials;
  269. List<Material> nullTextureMaterials;
  270. bool anyPlaceholdersAssigned = loader.HasPlaceholderTexturesAssigned(out placeholderMaterials);
  271. bool anyMaterialNull = loader.HasNullMainTexturesAssigned(out nullTextureMaterials);
  272. if (anyPlaceholdersAssigned || anyMaterialNull) {
  273. Debug.Log("OnDemandTextureLoader detected placeholders assigned or null main textures at one or more materials. Resetting to target textures.", loader);
  274. AssetDatabase.StartAssetEditing();
  275. IEnumerable<Material> modifiedMaterials;
  276. loader.AssignTargetTextures(out modifiedMaterials);
  277. if (placeholderMaterials != null) {
  278. foreach (Material placeholderMaterial in placeholderMaterials) {
  279. EditorUtility.SetDirty(placeholderMaterial);
  280. }
  281. }
  282. if (nullTextureMaterials != null) {
  283. foreach (Material nullTextureMaterial in nullTextureMaterials) {
  284. EditorUtility.SetDirty(nullTextureMaterial);
  285. }
  286. }
  287. AssetDatabase.StopAssetEditing();
  288. AssetDatabase.SaveAssets();
  289. }
  290. }
  291. /// <summary>
  292. /// Override this method in your implementation subclass as follows.
  293. /// <code>
  294. /// protected override StaticMethodImplementations CreateStaticMethodImplementations () {
  295. /// return new YourStaticMethodImplementationsSubclass();
  296. /// }
  297. /// </code>
  298. /// </summary>
  299. protected abstract StaticMethodImplementations CreateStaticMethodImplementations ();
  300. /// <summary>Draws a single texture mapping entry in the Inspector.
  301. /// Can be overridden in subclasses where needed. Note that DrawSingleLineTargetTextureProperty
  302. /// can be overridden as well instead of overriding this method.
  303. /// Note that for the sake of space it should be drawn as a single line if possible.
  304. /// </summary>
  305. /// <param name="textureMapping">SerializedProperty pointing to a
  306. /// PlaceholderTextureMapping object of the placeholderMap array.</param>
  307. protected virtual void DrawPlaceholderMapping (SerializedProperty textureMapping) {
  308. EditorGUILayout.BeginHorizontal(GUILayout.Height(EditorGUIUtility.singleLineHeight + 5));
  309. var placeholderTextureProp = textureMapping.FindPropertyRelative("placeholderTexture");
  310. var targetTextureProp = textureMapping.FindPropertyRelative("targetTextureReference");
  311. GUILayout.Space(16f);
  312. EditorGUILayout.PropertyField(placeholderTextureProp, GUIContent.none);
  313. EditorGUIUtility.labelWidth = 1; // workaround since GUIContent.none below seems to be ignored
  314. DrawSingleLineTargetTextureProperty(targetTextureProp);
  315. EditorGUIUtility.labelWidth = 0; // change back to default
  316. EditorGUILayout.EndHorizontal();
  317. }
  318. /// <summary>Draws a single texture mapping TargetReference in the Inspector.
  319. /// Can be overridden in subclasses where needed. Note that this method is
  320. /// called inside a horizontal Inspector line of a BeginHorizontal() / EndHorizontal()
  321. /// pair, so it is limited to approximately half Inspector width.
  322. /// </summary>
  323. /// <param name="property">SerializedProperty pointing to a
  324. /// TargetReference object of the PlaceholderTextureMapping entry.</param>
  325. protected virtual void DrawSingleLineTargetTextureProperty (SerializedProperty property) {
  326. EditorGUILayout.PropertyField(property, GUIContent.none, true);
  327. }
  328. public override void OnInspectorGUI () {
  329. if (serializedObject.isEditingMultipleObjects) {
  330. DrawDefaultInspector();
  331. return;
  332. }
  333. serializedObject.Update();
  334. EditorGUILayout.PropertyField(atlasAsset);
  335. EditorGUILayout.PropertyField(skeletonDataAsset);
  336. EditorGUILayout.PropertyField(maxPlaceholderSize);
  337. EditorGUILayout.PropertyField(unloadAfterSecondsUnused);
  338. placeholdersFoldout = EditorGUILayout.Foldout(placeholdersFoldout, placeholderTexturesLabel, true);
  339. if (placeholdersFoldout) {
  340. for (int m = 0, materialCount = placeholderMap.arraySize; m < materialCount; ++m) {
  341. // line below equals: PlaceholderTextureMapping[] materialTextures = placeholderMap[m].textures;
  342. SerializedProperty materialTextures = placeholderMap.GetArrayElementAtIndex(m).FindPropertyRelative("textures");
  343. for (int t = 0, textureCount = materialTextures.arraySize; t < textureCount; ++t) {
  344. // line below equals: PlaceholderTextureMapping textureMapping = materialTextures[t];
  345. SerializedProperty textureMapping = materialTextures.GetArrayElementAtIndex(t);
  346. DrawPlaceholderMapping(textureMapping);
  347. }
  348. }
  349. }
  350. if (GUILayout.Button(new GUIContent("Regenerate", "Re-initialize the placeholder texture maps."), EditorStyles.miniButton, GUILayout.Width(160f)))
  351. ReinitPlaceholderTextures(loader);
  352. GUILayout.Space(16f);
  353. EditorGUILayout.LabelField("Testing", EditorStyles.boldLabel);
  354. EditorGUILayout.BeginHorizontal(GUILayout.Height(EditorGUIUtility.singleLineHeight + 5));
  355. if (GUILayout.Button(new GUIContent("Assign Placeholders", "Assign placeholder textures (for testing)."), EditorStyles.miniButton, GUILayout.Width(160f)))
  356. AssignPlaceholderTextures(loader);
  357. if (GUILayout.Button(new GUIContent("Assign Normal Textures", "Re-assign target textures."), EditorStyles.miniButton, GUILayout.Width(160f)))
  358. AssignTargetTextures(loader);
  359. EditorGUILayout.EndHorizontal();
  360. if (!Application.isPlaying)
  361. serializedObject.ApplyModifiedProperties();
  362. }
  363. public void DeletePlaceholderTextures (GenericOnDemandTextureLoader<TargetReference, TextureRequest> loader) {
  364. foreach (var materialMap in loader.placeholderMap) {
  365. var textures = materialMap.textures;
  366. if (textures == null || textures.Length == 0)
  367. continue;
  368. Texture texture = textures[0].placeholderTexture;
  369. if (texture)
  370. AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(texture));
  371. }
  372. loader.Clear(clearAtlasAsset: false);
  373. AssetDatabase.SaveAssets();
  374. }
  375. public void ReinitPlaceholderTextures (GenericOnDemandTextureLoader<TargetReference, TextureRequest> loader) {
  376. AssignTargetTextures(loader);
  377. DeletePlaceholderTextures(loader);
  378. staticMethods.SetupForAtlasAsset(loader, loader.atlasAsset);
  379. EditorUtility.SetDirty(loader);
  380. AssetDatabase.SaveAssets();
  381. }
  382. public bool AssignPlaceholderTextures (GenericOnDemandTextureLoader<TargetReference, TextureRequest> loader) {
  383. // re-setup placeholders to ensure the mapping is up to date.
  384. staticMethods.SetupForAtlasAsset(loader, loader.atlasAsset);
  385. IEnumerable<Material> modifiedMaterials;
  386. return loader.AssignPlaceholderTextures(out modifiedMaterials);
  387. }
  388. public bool AssignTargetTextures (GenericOnDemandTextureLoader<TargetReference, TextureRequest> loader) {
  389. IEnumerable<Material> modifiedMaterials;
  390. return loader.AssignTargetTextures(out modifiedMaterials);
  391. }
  392. }
  393. }
  394. #endif