SkyProfile.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. using System;
  2. using System.Collections;
  3. using System.Reflection;
  4. using System.Collections.Generic;
  5. using System.Runtime.InteropServices;
  6. using System.Security.Permissions;
  7. using Funly.SkyStudio;
  8. using UnityEngine;
  9. namespace Funly.SkyStudio
  10. {
  11. /**
  12. * The Sky Profile manages all properties for a sky, and the keyframe values
  13. * for any skybox animations or transitions.
  14. **/
  15. [CreateAssetMenu(fileName = "skyProfile.asset", menuName = "Sky Studio/Sky Profile", order = 0)]
  16. public class SkyProfile : ScriptableObject
  17. {
  18. public const string DefaultShaderName = "Funly/Sky Studio/Skybox/3D Standard";
  19. // Version of the shader for versions before 2019.1 that uses global keywords.
  20. public const string DefaultLegacyShaderName = "Funly/Sky Studio/Skybox/3D Standard - Global Keywords";
  21. // Reference to material paired with this profile for enabling shader features.
  22. [SerializeField]
  23. private Material m_SkyboxMaterial;
  24. public Material skyboxMaterial
  25. {
  26. get { return m_SkyboxMaterial; }
  27. set
  28. {
  29. if (value == null)
  30. {
  31. m_SkyboxMaterial = null;
  32. return;
  33. }
  34. if (m_SkyboxMaterial && m_SkyboxMaterial.shader.name != value.shader.name)
  35. {
  36. m_SkyboxMaterial = value;
  37. m_ShaderName = value.shader.name;
  38. ReloadDefinitions();
  39. }
  40. else
  41. {
  42. m_SkyboxMaterial = value;
  43. }
  44. }
  45. }
  46. // We cache the last valid shader name so we can create sky systems from sky profiles during the setup wizard.
  47. [SerializeField]
  48. private string m_ShaderName = DefaultShaderName;
  49. public string shaderName { get { return m_ShaderName; } }
  50. // The profile defintion and groups are data driven from this data stucture loaded.
  51. public IProfileDefinition profileDefinition;
  52. // List determines what shows up on the timeline editor, or is a single value.
  53. public List<string> timelineManagedKeys = new List<string>();
  54. // Groups of keyframes for sky properties.
  55. public KeyframeGroupDictionary keyframeGroups = new KeyframeGroupDictionary();
  56. // Fature status flags for Sky Studio (sun, moon, rain, clouds, etc.)
  57. public BoolDictionary featureStatus = new BoolDictionary();
  58. // Lighting art customization object.
  59. public LightningArtSet lightningArtSet;
  60. // Rain splash art customization object.
  61. public RainSplashArtSet rainSplashArtSet;
  62. // Star layer data textures.
  63. public Texture2D starLayer1DataTexture;
  64. public Texture2D starLayer2DataTexture;
  65. public Texture2D starLayer3DataTexture;
  66. [SerializeField]
  67. #pragma warning disable
  68. private int m_ProfileVersion = 2;
  69. #pragma warning restore
  70. // Keep a mapping of key to definition for fast lookups.
  71. private Dictionary<string, ProfileGroupDefinition> m_KeyToGroupInfo;
  72. // Definitions.
  73. public ProfileGroupSection[] groupDefinitions
  74. {
  75. get { return profileDefinition != null ? profileDefinition.groups : null; }
  76. }
  77. public ProfileFeatureSection[] featureDefinitions
  78. {
  79. get { return profileDefinition != null ? profileDefinition.features : null; }
  80. }
  81. // Access a numeric value from the sky profile (cloud density, horizon position, star size etc.).
  82. public float GetNumberPropertyValue(string propertyKey)
  83. {
  84. return GetNumberPropertyValue(propertyKey, 0);
  85. }
  86. // Access a numeric value from the sky profile (cloud density, horizon position, star size, etc.).
  87. public float GetNumberPropertyValue(string propertyKey, float timeOfDay)
  88. {
  89. NumberKeyframeGroup group = GetGroup<NumberKeyframeGroup>(propertyKey);
  90. if (group == null) {
  91. Debug.LogError("Can't find number group with property key: " + propertyKey);
  92. return -1;
  93. }
  94. return group.NumericValueAtTime(timeOfDay);
  95. }
  96. // Access a color value from the sky profile (sky gradient, sun color, etc.)
  97. public Color GetColorPropertyValue(string propertyKey)
  98. {
  99. return GetColorPropertyValue(propertyKey, 0);
  100. }
  101. public Color GetColorPropertyValue(string propertyKey, float timeOfDay)
  102. {
  103. ColorKeyframeGroup group = GetGroup<ColorKeyframeGroup>(propertyKey);
  104. if (group == null) {
  105. Debug.LogError("Can't find color group with property key: " + propertyKey);
  106. return Color.white;
  107. }
  108. return group.ColorForTime(timeOfDay);
  109. }
  110. // Access a texture value from the sky profile (sun texture, star texture, etc.)
  111. public Texture GetTexturePropertyValue(string propertyKey)
  112. {
  113. return GetTexturePropertyValue(propertyKey, 0);
  114. }
  115. public Texture GetTexturePropertyValue(string propertyKey, float timeOfDay)
  116. {
  117. TextureKeyframeGroup group = GetGroup<TextureKeyframeGroup>(propertyKey);
  118. if (group == null) {
  119. Debug.LogError("Can't find texture group with property key: " + propertyKey);
  120. return null;
  121. }
  122. return group.TextureForTime(timeOfDay);
  123. }
  124. // Access a spherical position on the skybox (sun position, moon position, etc.)
  125. public SpherePoint GetSpherePointPropertyValue(string propertyKey)
  126. {
  127. return GetSpherePointPropertyValue(propertyKey, 0);
  128. }
  129. public SpherePoint GetSpherePointPropertyValue(string propertyKey, float timeOfDay)
  130. {
  131. SpherePointKeyframeGroup group = GetGroup<SpherePointKeyframeGroup>(propertyKey);
  132. if (group == null) {
  133. Debug.LogError("Can't find a sphere point group with property key: " + propertyKey);
  134. return null;
  135. }
  136. return group.SpherePointForTime(timeOfDay);
  137. }
  138. // Access a boolean property value in the sky profile (fog enabled, etc.)
  139. public bool GetBoolPropertyValue(string propertyKey)
  140. {
  141. return GetBoolPropertyValue(propertyKey, 0);
  142. }
  143. public bool GetBoolPropertyValue(string propertyKey, float timeOfDay)
  144. {
  145. BoolKeyframeGroup group = GetGroup<BoolKeyframeGroup>(propertyKey);
  146. if (group == null) {
  147. Debug.LogError("Can't find boolean group with property key: " + propertyKey);
  148. return false;
  149. }
  150. return group.BoolForTime(timeOfDay);
  151. }
  152. public SkyProfile()
  153. {
  154. // Build the profile definition table.
  155. ReloadFullProfile();
  156. }
  157. private void OnEnable()
  158. {
  159. ReloadFullProfile();
  160. }
  161. private void ReloadFullProfile()
  162. {
  163. ReloadDefinitions();
  164. MergeProfileWithDefinitions();
  165. RebuildKeyToGroupInfoMapping();
  166. ValidateTimelineGroupKeys();
  167. }
  168. private void ReloadDefinitions()
  169. {
  170. profileDefinition = GetShaderInfoForMaterial(m_ShaderName);
  171. }
  172. private IProfileDefinition GetShaderInfoForMaterial(string shaderName)
  173. {
  174. // We currently only support 1 shader.
  175. return new Standard3dShaderDefinition();
  176. }
  177. public void MergeProfileWithDefinitions()
  178. {
  179. MergeGroupsWithDefinitions();
  180. MergeShaderKeywordsWithDefinitions();
  181. }
  182. public void MergeGroupsWithDefinitions()
  183. {
  184. HashSet<string> validProperties = ProfilePropertyKeys.GetPropertyKeysSet();
  185. // Build our groups from the profile definition table.
  186. foreach (ProfileGroupSection section in groupDefinitions)
  187. {
  188. foreach (ProfileGroupDefinition groupInfo in section.groups)
  189. {
  190. // Filter out old groups that are no longer valid.
  191. if (!validProperties.Contains(groupInfo.propertyKey))
  192. {
  193. continue;
  194. }
  195. if (groupInfo.type == ProfileGroupDefinition.GroupType.Color) {
  196. if (keyframeGroups.ContainsKey(groupInfo.propertyKey) == false)
  197. {
  198. AddColorGroup(groupInfo.propertyKey, groupInfo.groupName, groupInfo.color);
  199. }
  200. else
  201. {
  202. keyframeGroups[groupInfo.propertyKey].name = groupInfo.groupName;
  203. }
  204. } else if (groupInfo.type == ProfileGroupDefinition.GroupType.Number) {
  205. if (keyframeGroups.ContainsKey(groupInfo.propertyKey) == false)
  206. {
  207. AddNumericGroup(groupInfo.propertyKey, groupInfo.groupName,
  208. groupInfo.minimumValue, groupInfo.maximumValue, groupInfo.value);
  209. }
  210. else
  211. {
  212. NumberKeyframeGroup numberGroup = keyframeGroups.GetGroup<NumberKeyframeGroup>(groupInfo.propertyKey);
  213. numberGroup.name = groupInfo.groupName;
  214. numberGroup.minValue = groupInfo.minimumValue;
  215. numberGroup.maxValue = groupInfo.maximumValue;
  216. }
  217. } else if (groupInfo.type == ProfileGroupDefinition.GroupType.Texture) {
  218. if (keyframeGroups.ContainsKey(groupInfo.propertyKey) == false)
  219. {
  220. AddTextureGroup(groupInfo.propertyKey, groupInfo.groupName, groupInfo.texture);
  221. }
  222. else
  223. {
  224. keyframeGroups[groupInfo.propertyKey].name = groupInfo.groupName;
  225. }
  226. } else if (groupInfo.type == ProfileGroupDefinition.GroupType.SpherePoint) {
  227. if (keyframeGroups.ContainsKey(groupInfo.propertyKey) == false) {
  228. AddSpherePointGroup(groupInfo.propertyKey, groupInfo.groupName, groupInfo.spherePoint);
  229. } else
  230. {
  231. keyframeGroups[groupInfo.propertyKey].name = groupInfo.groupName;
  232. }
  233. } else if (groupInfo.type == ProfileGroupDefinition.GroupType.Boolean) {
  234. if (keyframeGroups.ContainsKey(groupInfo.propertyKey) == false) {
  235. AddBooleanGroup(groupInfo.propertyKey, groupInfo.groupName, groupInfo.boolValue);
  236. } else {
  237. keyframeGroups[groupInfo.propertyKey].name = groupInfo.groupName;
  238. }
  239. }
  240. }
  241. }
  242. }
  243. public Dictionary<string, ProfileGroupDefinition> GroupDefinitionDictionary() {
  244. ProfileGroupSection[] sections = ProfileDefinitionTable();
  245. Dictionary<string, ProfileGroupDefinition> dict = new Dictionary<string, ProfileGroupDefinition>();
  246. foreach (ProfileGroupSection sectionInfo in sections) {
  247. foreach (ProfileGroupDefinition groupInfo in sectionInfo.groups) {
  248. dict.Add(groupInfo.propertyKey, groupInfo);
  249. }
  250. }
  251. return dict;
  252. }
  253. public ProfileGroupSection[] ProfileDefinitionTable()
  254. {
  255. return groupDefinitions;
  256. }
  257. private void AddNumericGroup(string propKey, string groupName, float min, float max, float value)
  258. {
  259. NumberKeyframeGroup group = new NumberKeyframeGroup(
  260. groupName, min, max, new NumberKeyframe(0, value));
  261. keyframeGroups[propKey] = group;
  262. }
  263. private void AddColorGroup(string propKey, string groupName, Color color)
  264. {
  265. ColorKeyframeGroup group = new ColorKeyframeGroup(
  266. groupName, new ColorKeyframe(color, 0));
  267. keyframeGroups[propKey] = group;
  268. }
  269. private void AddTextureGroup(string propKey, string groupName, Texture2D texture)
  270. {
  271. TextureKeyframeGroup group = new TextureKeyframeGroup(
  272. groupName, new TextureKeyframe(texture, 0));
  273. keyframeGroups[propKey] = group;
  274. }
  275. private void AddSpherePointGroup(string propKey, string groupName, SpherePoint point)
  276. {
  277. SpherePointKeyframeGroup group = new SpherePointKeyframeGroup(groupName, new SpherePointKeyframe(point, 0));
  278. keyframeGroups[propKey] = group;
  279. }
  280. private void AddBooleanGroup(string propKey, string groupName, bool value)
  281. {
  282. BoolKeyframeGroup group = new BoolKeyframeGroup(groupName, new BoolKeyframe(0, value));
  283. keyframeGroups[propKey] = group;
  284. }
  285. public T GetGroup<T>(string propertyKey) where T : class
  286. {
  287. if (!keyframeGroups.ContainsKey(propertyKey)) {
  288. Debug.Log("Key does not exist in sky profile, ignoring: " + propertyKey);
  289. return null;
  290. }
  291. return keyframeGroups[propertyKey] as T;
  292. }
  293. public IKeyframeGroup GetGroup(string propertyKey)
  294. {
  295. return keyframeGroups[propertyKey];
  296. }
  297. public IKeyframeGroup GetGroupWithId(string groupId)
  298. {
  299. if (groupId == null) {
  300. return null;
  301. }
  302. foreach (string key in keyframeGroups)
  303. {
  304. IKeyframeGroup group = keyframeGroups[key];
  305. if (group.id == groupId) {
  306. return group;
  307. }
  308. }
  309. return null;
  310. }
  311. // This returns the groups that exist in the profile for easy iteration.
  312. public ProfileGroupSection[] GetProfileDefinitions()
  313. {
  314. return groupDefinitions;
  315. }
  316. public ProfileGroupSection GetSectionInfo(string sectionKey)
  317. {
  318. foreach (ProfileGroupSection section in groupDefinitions)
  319. {
  320. if (section.sectionKey == sectionKey)
  321. {
  322. return section;
  323. }
  324. }
  325. return null;
  326. }
  327. // Check if a group is managed by the timeline.
  328. public bool IsManagedByTimeline(string propertyKey)
  329. {
  330. return timelineManagedKeys.Contains(propertyKey);
  331. }
  332. public void ValidateTimelineGroupKeys()
  333. {
  334. List<string> removeKeys = new List<string>();
  335. HashSet<string> validProperties = ProfilePropertyKeys.GetPropertyKeysSet();
  336. foreach (string timelineKey in timelineManagedKeys)
  337. {
  338. if (!IsManagedByTimeline(timelineKey) || !validProperties.Contains(timelineKey))
  339. {
  340. removeKeys.Add(timelineKey);
  341. }
  342. }
  343. foreach (string removeKey in removeKeys)
  344. {
  345. if (timelineManagedKeys.Contains(removeKey))
  346. {
  347. timelineManagedKeys.Remove(removeKey);
  348. }
  349. }
  350. }
  351. public List<ProfileGroupDefinition> GetGroupDefinitionsManagedByTimeline() {
  352. List<ProfileGroupDefinition> groups = new List<ProfileGroupDefinition>();
  353. foreach (string groupKey in timelineManagedKeys)
  354. {
  355. ProfileGroupDefinition groupDefinition = GetGroupDefinitionForKey(groupKey);
  356. if (groupDefinition == null)
  357. {
  358. continue;
  359. }
  360. groups.Add(groupDefinition);
  361. }
  362. return groups;
  363. }
  364. public List<ProfileGroupDefinition> GetGroupDefinitionsNotManagedByTimeline()
  365. {
  366. List<ProfileGroupDefinition> groups = new List<ProfileGroupDefinition>();
  367. foreach (ProfileGroupSection sectionInfo in groupDefinitions)
  368. {
  369. foreach (ProfileGroupDefinition groupInfo in sectionInfo.groups)
  370. {
  371. if (IsManagedByTimeline(groupInfo.propertyKey) == false && CanGroupBeOnTimeline(groupInfo))
  372. {
  373. groups.Add(groupInfo);
  374. }
  375. }
  376. }
  377. return groups;
  378. }
  379. public ProfileGroupDefinition GetGroupDefinitionForKey(string propertyKey)
  380. {
  381. ProfileGroupDefinition def = null;
  382. if (m_KeyToGroupInfo.TryGetValue(propertyKey, out def))
  383. {
  384. return def;
  385. }
  386. return null;
  387. }
  388. public void RebuildKeyToGroupInfoMapping()
  389. {
  390. m_KeyToGroupInfo = new Dictionary<string, ProfileGroupDefinition>();
  391. foreach (ProfileGroupSection sectionInfo in groupDefinitions) {
  392. foreach (ProfileGroupDefinition groupInfo in sectionInfo.groups)
  393. {
  394. m_KeyToGroupInfo[groupInfo.propertyKey] = groupInfo;
  395. }
  396. }
  397. }
  398. public void TrimGroupToSingleKeyframe(string propertyKey)
  399. {
  400. IKeyframeGroup group = GetGroup(propertyKey);
  401. if (group == null)
  402. {
  403. return;
  404. }
  405. group.TrimToSingleKeyframe();
  406. }
  407. // Blacklist some groups from being on the timeline.
  408. public bool CanGroupBeOnTimeline(ProfileGroupDefinition definition)
  409. {
  410. if (definition.type == ProfileGroupDefinition.GroupType.Texture ||
  411. (definition.propertyKey.Contains("Star") && definition.propertyKey.Contains("Density")) ||
  412. definition.propertyKey.Contains("Sprite") ||
  413. definition.type == ProfileGroupDefinition.GroupType.Boolean) {
  414. return false;
  415. }
  416. else
  417. {
  418. return true;
  419. }
  420. }
  421. protected void MergeShaderKeywordsWithDefinitions()
  422. {
  423. foreach (ProfileFeatureSection section in profileDefinition.features)
  424. {
  425. foreach (ProfileFeatureDefinition definition in section.featureDefinitions)
  426. {
  427. string featureKey = null;
  428. bool featureValue = false;
  429. if (definition.featureType == ProfileFeatureDefinition.FeatureType.BooleanValue ||
  430. definition.featureType == ProfileFeatureDefinition.FeatureType.ShaderKeyword) {
  431. featureKey = definition.featureKey;
  432. featureValue = definition.value;
  433. } else if (definition.featureType == ProfileFeatureDefinition.FeatureType.ShaderKeywordDropdown) {
  434. featureKey = definition.featureKeys[definition.dropdownSelectedIndex];
  435. featureValue = true;
  436. }
  437. if (featureKey == null) {
  438. continue;
  439. }
  440. if (featureStatus.dict.ContainsKey(featureKey) == false) {
  441. SetFeatureEnabled(featureKey, featureValue);
  442. }
  443. }
  444. }
  445. }
  446. public bool IsFeatureEnabled(string featureKey, bool recursive = true)
  447. {
  448. if (featureKey == null)
  449. {
  450. return false;
  451. }
  452. // Load the definition for this feature so we can check for dependent features.
  453. ProfileFeatureDefinition feature = profileDefinition.GetFeatureDefinition(featureKey);
  454. if (feature == null) {
  455. return false;
  456. }
  457. if (featureStatus.dict.ContainsKey(featureKey) == false || featureStatus[featureKey] == false) {
  458. return false;
  459. }
  460. // Don't scan up the parent hiearchy any further.
  461. if (recursive == false) {
  462. return true;
  463. }
  464. // Check the full dependency chain to check if this feature is disabled by a parent.
  465. ProfileFeatureDefinition childFeature = feature;
  466. ProfileFeatureDefinition parentFeature;
  467. while (childFeature != null) {
  468. parentFeature = profileDefinition.GetFeatureDefinition(childFeature.dependsOnFeature);
  469. if (parentFeature == null || parentFeature.featureKey == null) {
  470. break;
  471. }
  472. if (featureStatus[parentFeature.featureKey] != childFeature.dependsOnValue) {
  473. return false;
  474. }
  475. childFeature = parentFeature;
  476. }
  477. return true;
  478. }
  479. public void SetFeatureEnabled(string featureKey, bool value)
  480. {
  481. if (featureKey == null) {
  482. Debug.LogError("Can't set null feature key value");
  483. return;
  484. }
  485. featureStatus[featureKey] = value;
  486. }
  487. }
  488. }