GrassPainter.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952
  1. using System.Collections.Generic;
  2. using UnityEditor;
  3. using UnityEditor.SceneManagement;
  4. using UnityEngine;
  5. using UnityEngine.SceneManagement;
  6. namespace MTE
  7. {
  8. internal partial class GrassPainter : IEditor
  9. {
  10. public int Id { get; } = 6;
  11. public bool Enabled { get; set; } = true;
  12. public string Name { get; } = "GrassPainter";
  13. public Texture Icon { get; } =
  14. EditorGUIUtility.IconContent("TerrainInspector.TerrainToolPlants").image;
  15. public bool WantMouseMove { get; } = false;
  16. public bool WillEditMesh { get; } = false;
  17. #region Parameters
  18. #region Constant
  19. // default
  20. const float DefaultBrushSize = 1;
  21. const float DefaultBrushOpacity = 0.5f;
  22. const float DefaultBrushDirection = 0;
  23. const bool DefaultUseRandomDirection = true;
  24. const int DefaultReduction = 100;
  25. // min/max
  26. const float MinBrushSize = 0.1f;
  27. const float MaxBrushSize = 10f;
  28. const float MinBrushOpacity = 0.0f;
  29. const float MaxBrushOpacity = 1.0f;
  30. private const int MinReduction = 1;
  31. private const int MaxReduction = 100;
  32. // limit
  33. private const int MaxPositionNumber = 50;
  34. #endregion
  35. public float brushSize;
  36. public float brushOpacity;
  37. public int reduction;
  38. private GrassDetail SelectedGrassDetail => grassDetailList[SelectedGrassIndex];
  39. /// <summary>
  40. /// Brush size (unit: 1 BrushUnit)
  41. /// </summary>
  42. public float BrushSize
  43. {
  44. get { return brushSize; }
  45. set
  46. {
  47. value = Mathf.Clamp(value, MinBrushSize, MaxBrushSize);
  48. if (!MathEx.AmostEqual(brushSize, value))
  49. {
  50. brushSize = value;
  51. EditorPrefs.SetFloat("MTE_GrassPainter.brushSize", value);
  52. }
  53. }
  54. }
  55. //real brush size
  56. private float BrushSizeInU3D { get { return BrushSize * Settings.BrushUnit; } }
  57. /// <summary>
  58. ///
  59. /// </summary>
  60. public float BrushOpacity
  61. {
  62. get
  63. {
  64. return brushOpacity;
  65. }
  66. set
  67. {
  68. if (Mathf.Abs(brushOpacity - value) > 0.0001f)
  69. {
  70. brushOpacity = value;
  71. EditorPrefs.SetFloat("MTE_GrassPainter.brushOpacity", value);
  72. }
  73. }
  74. }
  75. /// <summary>
  76. /// Removing strength (percent)
  77. /// </summary>
  78. public int Reduction
  79. {
  80. get
  81. {
  82. return this.reduction;
  83. }
  84. set
  85. {
  86. if (this.reduction != value)
  87. {
  88. this.reduction = value;
  89. EditorPrefs.SetInt("MTE_GrassPainter.reduction", value);
  90. }
  91. }
  92. }
  93. /// <summary>
  94. /// Selected grass texture index
  95. /// </summary>
  96. public int SelectedGrassIndex
  97. {
  98. get;
  99. set;
  100. }
  101. private float brushDirection = 0;
  102. /// <summary>
  103. /// Brush direction, angle to north(+z)
  104. /// </summary>
  105. public float BrushDirection
  106. {
  107. get
  108. {
  109. return this.brushDirection;
  110. }
  111. set
  112. {
  113. value = Mathf.Clamp(value, 0, 2 * Mathf.PI);
  114. if (!MathEx.AmostEqual(value, this.brushDirection))
  115. {
  116. EditorPrefs.SetFloat("MTE_GrassPainter.brushDirection", this.brushDirection);
  117. this.brushDirection = value;
  118. }
  119. }
  120. }
  121. private bool useRandomDirection;
  122. /// <summary>
  123. ///
  124. /// </summary>
  125. public bool UseRandomDirection
  126. {
  127. get { return this.useRandomDirection; }
  128. set
  129. {
  130. if (value != useRandomDirection)
  131. {
  132. useRandomDirection = value;
  133. EditorPrefs.SetBool("MTE_GrassPainter.useRandomDirection", value);
  134. }
  135. }
  136. }
  137. #endregion
  138. public static GrassPainter Instance;
  139. private List<GrassDetail> grassDetailList = null;
  140. internal void LoadGrassDetailList()
  141. {
  142. if (detailListBox == null)
  143. {
  144. detailListBox = new GrassDetailListBox();
  145. }
  146. var path = Res.DetailDir + "SavedGrassDetailList.asset";
  147. var relativePath = Utility.GetUnityPath(path);
  148. var obj = AssetDatabase.LoadAssetAtPath<GrassDetailList>(relativePath);
  149. if (obj != null && obj.grassDetailList != null)
  150. {
  151. grassDetailList = obj.grassDetailList;
  152. detailListBox.SetEditingTarget(grassDetailList);
  153. MTEDebug.LogFormat("GrassDetailList loaded from {0}", path);
  154. }
  155. else
  156. {
  157. obj = ScriptableObject.CreateInstance<GrassDetailList>();
  158. obj.grassDetailList = new List<GrassDetail>(4);
  159. AssetDatabase.CreateAsset(obj, relativePath);
  160. EditorUtility.SetDirty(obj);
  161. grassDetailList = obj.grassDetailList;
  162. detailListBox.SetEditingTarget(grassDetailList);
  163. MTEDebug.LogFormat("No GrassDetailList found in {0}, created a new SavedGrassDetailList.asset.", path);
  164. }
  165. }
  166. public GrassPainter()
  167. {
  168. MTEContext.EnableEvent += (sender, args) =>
  169. {
  170. if (MTEContext.editor == this)
  171. {
  172. LoadSavedParamter();
  173. LoadGrassDetailList();
  174. CheckIfCanAttachGrassLoader();
  175. ForceReloadGrass();
  176. }
  177. };
  178. MTEContext.EditTypeChangedEvent += (sender, args) =>
  179. {
  180. if (MTEContext.editor == this)
  181. {
  182. LoadSavedParamter();
  183. LoadGrassDetailList();
  184. CheckIfCanAttachGrassLoader();
  185. ForceReloadGrass();
  186. }
  187. };
  188. MTEContext.SelectionChangedEvent += (sender, args) =>
  189. {
  190. if (MTEContext.editor == this)
  191. {
  192. CheckIfCanAttachGrassLoader();
  193. }
  194. };
  195. MTEContext.MeshColliderUpdatedEvent += (sender, args) =>
  196. {
  197. UpdateAllGrasses();
  198. };
  199. // Load default parameters
  200. brushSize = DefaultBrushSize;
  201. brushOpacity = DefaultBrushOpacity;
  202. brushDirection = DefaultBrushDirection;
  203. useRandomDirection = DefaultUseRandomDirection;
  204. this.reduction = DefaultReduction;
  205. GrassPainter.Instance = this;
  206. }
  207. private void ForceReloadGrass()
  208. {
  209. // force reload the grass loader
  210. var foundGrassLoader = MTEContext.TheGrassLoader;
  211. if (foundGrassLoader != null)
  212. {
  213. GrassEditorUtil.ReloadGrassesFromFile(foundGrassLoader);
  214. }
  215. }
  216. public HashSet<Hotkey> DefineHotkeys()
  217. {
  218. return new HashSet<Hotkey>
  219. {
  220. new Hotkey(this, KeyCode.LeftBracket, () =>
  221. {
  222. BrushSize -= 1;
  223. MTEEditorWindow.Instance.Repaint();
  224. }),
  225. new Hotkey(this, KeyCode.RightBracket, () =>
  226. {
  227. BrushSize += 1;
  228. MTEEditorWindow.Instance.Repaint();
  229. }),
  230. new Hotkey(this, KeyCode.Minus, () =>
  231. {
  232. BrushOpacity -= 0.01f;
  233. MTEEditorWindow.Instance.Repaint();
  234. }),
  235. new Hotkey(this, KeyCode.Equals, () =>
  236. {
  237. BrushOpacity += 0.01f;
  238. MTEEditorWindow.Instance.Repaint();
  239. })
  240. };
  241. }
  242. private void LoadSavedParamter()
  243. {
  244. // Load parameters from the EditorPrefs
  245. brushSize = EditorPrefs.GetFloat("MTE_GrassPainter.brushSize", DefaultBrushSize);
  246. brushOpacity = EditorPrefs.GetFloat("MTE_GrassPainter.brushOpacity", DefaultBrushOpacity);
  247. brushDirection = EditorPrefs.GetFloat("MTE_GrassPainter.brushDirection", DefaultBrushDirection);
  248. useRandomDirection = EditorPrefs.GetBool("MTE_GrassPainter.useRandomDirection", DefaultUseRandomDirection);
  249. reduction = EditorPrefs.GetInt("MTE_GrassPainter.reduction", DefaultReduction);
  250. }
  251. public string Header { get { return StringTable.Get(C.PaintGrass_Header); } }
  252. public string Description { get { return StringTable.Get(C.PaintGrass_Description); } }
  253. public void DoArgsGUI()
  254. {
  255. if (!MTEContext.TheGrassLoader)
  256. {
  257. EditorGUILayout.HelpBox(StringTable.Get(C.Warning_NoGrassLoader), MessageType.Warning);
  258. EditorGUILayout.BeginHorizontal();
  259. {
  260. if (GUILayout.Button(StringTable.Get(C.CreateGrassLoader), GUILayout.Width(100),
  261. GUILayout.Height(40)))
  262. {
  263. CreateGrassContainer();
  264. }
  265. GUILayout.Space(20);
  266. EditorGUILayout.LabelField(
  267. StringTable.Get(C.Info_ToolDescription_CreateGrassLoader),
  268. MTEStyles.labelFieldWordwrap);
  269. }
  270. EditorGUILayout.EndHorizontal();
  271. EditorGUILayout.BeginHorizontal();
  272. {
  273. GUI.enabled = CanAttachGrassLoader;
  274. if (GUILayout.Button(StringTable.Get(C.AttachGrassLoader), GUILayout.Width(100), GUILayout.Height(40)))
  275. {
  276. AttachGrassLoader();
  277. }
  278. GUILayout.Space(20);
  279. EditorGUILayout.LabelField(StringTable.Get(C.Info_ToolDescription_AttachGrassLoader), MTEStyles.labelFieldWordwrap);
  280. GUI.enabled = true;
  281. if (!CanAttachGrassLoader)
  282. {
  283. EditorGUILayout.BeginHorizontal();
  284. {
  285. var content = EditorGUIUtility.IconContent("console.warnicon");
  286. content.tooltip = CannotAttachGrassReason;
  287. GUILayout.Label(content, "button");
  288. }
  289. EditorGUILayout.EndHorizontal();
  290. }
  291. }
  292. EditorGUILayout.EndHorizontal();
  293. return;
  294. }
  295. // Grasses
  296. if (!Settings.CompactGUI)
  297. {
  298. GUILayout.Label(StringTable.Get(C.Grasses), MTEStyles.SubHeader);
  299. }
  300. // grass detail list
  301. SelectedGrassIndex = detailListBox.DoGUI(SelectedGrassIndex);
  302. //Settings
  303. if (!Settings.CompactGUI)
  304. {
  305. EditorGUILayout.Space();
  306. GUILayout.Label(StringTable.Get(C.Settings), MTEStyles.SubHeader);
  307. }
  308. BrushSize = EditorGUILayoutEx.Slider(StringTable.Get(C.Size), "-", "+", BrushSize, MinBrushSize, MaxBrushSize);
  309. BrushOpacity = EditorGUILayoutEx.Slider(StringTable.Get(C.Density), "[", "]", BrushOpacity, MinBrushOpacity, MaxBrushOpacity);
  310. Reduction = EditorGUILayoutEx.IntSlider(StringTable.Get(C.Reduction), Reduction, MinReduction, MaxReduction);
  311. EditorGUILayout.BeginHorizontal();
  312. {
  313. var label = new GUIContent(StringTable.Get(C.Direction));
  314. var size = GUIStyle.none.CalcSize(label);
  315. EditorGUILayout.LabelField(label, GUILayout.Width(size.x + 10), GUILayout.MinWidth(60));
  316. EditorGUILayout.BeginVertical();
  317. UseRandomDirection = GUILayout.Toggle(UseRandomDirection, StringTable.Get(C.Random));
  318. if (!UseRandomDirection)
  319. {
  320. EditorGUILayout.LabelField(string.Format("{0}°", Mathf.Rad2Deg * BrushDirection));
  321. EditorGUILayout.HelpBox(StringTable.Get(C.Info_HowToRotate), MessageType.Info);
  322. }
  323. EditorGUILayout.EndVertical();
  324. }
  325. EditorGUILayout.EndHorizontal();
  326. // Tools
  327. if (!Settings.CompactGUI)
  328. {
  329. EditorGUILayout.Space();
  330. GUILayout.Label(StringTable.Get(C.Tools), MTEStyles.SubHeader);
  331. }
  332. EditorGUILayout.BeginVertical();
  333. {
  334. EditorGUILayout.BeginHorizontal();
  335. {
  336. if (GUILayout.Button(StringTable.Get(C.BakePointCloudToMesh), GUILayout.Width(100),
  337. GUILayout.Height(40)))
  338. {
  339. BakePointCloudToMesh();
  340. }
  341. GUILayout.Space(20);
  342. EditorGUILayout.LabelField(
  343. StringTable.Get(C.Info_ToolDescription_BakePointCloudToMesh),
  344. MTEStyles.labelFieldWordwrap);
  345. }
  346. EditorGUILayout.EndHorizontal();
  347. }
  348. EditorGUILayout.EndVertical();
  349. GUILayout.FlexibleSpace();
  350. EditorGUILayout.HelpBox(StringTable.Get(C.Info_WillBeSavedInstantly),
  351. MessageType.Info, true);
  352. }
  353. private HashSet<MeshRenderer> highlightedRenderers = new HashSet<MeshRenderer>();
  354. List<GrassItem> editingItems = new List<GrassItem>();
  355. public void OnSceneGUI()
  356. {
  357. var e = Event.current;
  358. if (e.commandName == "UndoRedoPerformed")
  359. {
  360. SceneView.RepaintAll();
  361. return;
  362. }
  363. if (!(EditorWindow.mouseOverWindow is SceneView))
  364. {
  365. MTEDebug.Log("Mouse not in SceneView.");
  366. ClearHighlight();
  367. return;
  368. }
  369. if(!UseRandomDirection && e.control)
  370. {
  371. RaycastHit hit;
  372. Ray ray1 = HandleUtility.GUIPointToWorldRay(e.mousePosition);
  373. if (Physics.Raycast(ray1, out hit,
  374. Mathf.Infinity,
  375. 1 << MTEContext.TargetLayer//only hit target layer
  376. ))
  377. {
  378. //check tag
  379. if (!hit.transform.CompareTag(MTEContext.TargetTag))
  380. {
  381. return;
  382. }
  383. Handles.ArrowHandleCap(0, hit.point,
  384. Quaternion.Euler(0, BrushDirection * Mathf.Rad2Deg, 0),
  385. 10 * Settings.PointSize, EventType.Repaint);
  386. }
  387. }
  388. // do nothing when mouse middle/right button, control/alt key is pressed
  389. if (e.button != 0 || e.alt)
  390. return;
  391. // no grass
  392. if (grassDetailList == null || grassDetailList.Count == 0)
  393. {
  394. MTEDebug.Log("Return: No grass detail.");
  395. return;
  396. }
  397. // grass loader not specified
  398. if (MTEContext.TheGrassLoader == null)
  399. {
  400. MTEDebug.Log("Return: No grass loader.");
  401. return;
  402. }
  403. HandleUtility.AddDefaultControl(0);
  404. RaycastHit raycastHit;
  405. Ray ray = HandleUtility.GUIPointToWorldRay(e.mousePosition);
  406. if (Physics.Raycast(ray, out raycastHit,
  407. Mathf.Infinity,
  408. 1 << MTEContext.TargetLayer//only hit target layer
  409. ))
  410. {
  411. //check tag
  412. if (!raycastHit.transform.CompareTag(MTEContext.TargetTag))
  413. {
  414. return;
  415. }
  416. if (Settings.ShowBrushRect)
  417. {
  418. Utility.ShowBrushRect(raycastHit.point, BrushSizeInU3D);
  419. }
  420. var hitPoint = raycastHit.point;
  421. Handles.color = Color.green;
  422. Handles.DrawWireDisc(hitPoint, raycastHit.normal, BrushSizeInU3D);
  423. if (!UseRandomDirection)
  424. {
  425. ClearHighlight();
  426. if (e.control)
  427. {
  428. GrassMap.GetGrassItemsInCircle(hitPoint, BrushSizeInU3D, editingItems);
  429. foreach (var grassItem in editingItems)
  430. {
  431. var renderer = grassItem.gameObject.GetComponent<MeshRenderer>();
  432. Utility.SetHighlight(renderer, true);
  433. highlightedRenderers.Add(renderer);
  434. }
  435. }
  436. }
  437. // not using random direction
  438. // hold control key and scroll wheel to change
  439. // 1. grasses' rotationY
  440. // 2. brush direction
  441. if (!UseRandomDirection && e.control && !e.isKey && e.type == EventType.ScrollWheel)
  442. {
  443. float oldDirection = BrushDirection;
  444. float direction = oldDirection;
  445. ChangeDirection(e.delta.y, ref direction);
  446. if (Mathf.Abs(direction - oldDirection) > Mathf.Epsilon)
  447. {
  448. UpdateGrasses(editingItems, Mathf.Rad2Deg * direction);
  449. MTEEditorWindow.Instance.Repaint();
  450. BrushDirection = direction;
  451. }
  452. e.Use();
  453. }
  454. else if (e.type == EventType.MouseDown || e.type == EventType.MouseDrag)
  455. {
  456. if (e.type == EventType.MouseDown)
  457. {
  458. grassPaintTransation =
  459. new Undo.UndoTransaction(
  460. e.shift ?
  461. "Grass Painter: Delete Grass Instances" :
  462. "Grass Painter: Create Grass Instances"
  463. );
  464. Undo.UndoRedoManager.Instance().StartTransaction(grassPaintTransation);
  465. }
  466. if (!e.shift)
  467. {//adding grasses
  468. MTEDebug.Log("1: generate grass positions");
  469. var grassDetail = grassDetailList[this.SelectedGrassIndex];
  470. grassPositions.Clear();
  471. if (grassDetail.MaxWidth > this.BrushSizeInU3D)
  472. {//TODO single mode: click and create a single grass; dragging is not allowed.
  473. grassPositions.Add(new Vector2(hitPoint.x, hitPoint.z));
  474. }
  475. else
  476. {
  477. var positionNumber = Mathf.CeilToInt(
  478. Mathf.PI * this.BrushSizeInU3D * this.BrushSizeInU3D / this.BrushOpacity);
  479. positionNumber = Mathf.Min(MaxPositionNumber, positionNumber);
  480. MathEx.UniformPointsInCircle(
  481. new Vector2(hitPoint.x, hitPoint.z),
  482. this.BrushSizeInU3D,
  483. positionNumber,
  484. ref grassPositions);
  485. }
  486. MTEDebug.Log("2: added grass positions number = " + grassPositions.Count);
  487. CreateGrassInstances();
  488. }
  489. else
  490. {//removing grasses
  491. removeList.Clear();
  492. GrassMap.GetGrassItemsInCircle(hitPoint, BrushSizeInU3D, removeList);
  493. int removeCount = Mathf.CeilToInt(this.reduction / 100.0f * this.removeList.Count);
  494. if (removeCount != 0)
  495. {
  496. var grassItemsRemoved = this.removeList.TakeRandom(removeCount);
  497. RemoveGrassInstances(grassItemsRemoved);
  498. }
  499. }
  500. }
  501. // auto save when mouse up
  502. if (e.type == EventType.MouseUp && e.button == 0)
  503. {
  504. SaveGrass();
  505. MTEDebug.Log("5: saved grass asset file");
  506. if (grassPaintTransation != null)
  507. {
  508. Undo.UndoRedoManager.Instance().EndTransaction(grassPaintTransation);
  509. Utility.RefreshHistoryViewer();
  510. grassPaintTransation = null;
  511. }
  512. }
  513. }
  514. SceneView.RepaintAll();
  515. }
  516. Undo.UndoTransaction grassPaintTransation;
  517. private void CreateGrassInstances()
  518. {
  519. List<GrassItem> createdInstances = new List<GrassItem>(grassPositions.Count);
  520. var grassDetail = SelectedGrassDetail;
  521. int createdGrassObjectNumber = 0;
  522. for (int j = 0; j < grassPositions.Count; j++)
  523. {
  524. var width = Random.Range(grassDetail.MinWidth, grassDetail.MaxWidth);
  525. var height = Random.Range(grassDetail.MinHeight, grassDetail.MaxHeight);
  526. var rotationY = UseRandomDirection ? Random.Range(0f, 180f) : Mathf.Rad2Deg * this.BrushDirection;
  527. var grassPosition = grassPositions[j];
  528. RaycastHit hit;
  529. if (Physics.Raycast(
  530. new Ray(new Vector3(grassPosition.x, 10000, grassPosition.y),
  531. new Vector3(0, -1f, 0)),
  532. out hit,
  533. Mathf.Infinity,
  534. 1 << MTEContext.TargetLayer //only hit target layer
  535. ))
  536. {
  537. //only consider target tag
  538. if (!hit.transform.CompareTag(MTEContext.TargetTag))
  539. {
  540. return;
  541. }
  542. GrassItem grassItem = null;
  543. if (grassDetail.GrassType == GrassType.OneQuad)
  544. {
  545. grassItem = CreateGrassQuad(
  546. grassDetail.Material, hit.point, rotationY, width, height);
  547. }
  548. else if(grassDetail.GrassType == GrassType.ThreeQuad)
  549. {
  550. grassItem = CreateGrassStar(
  551. grassDetail.Material, hit.point, rotationY, width, height);
  552. }
  553. else
  554. {
  555. throw new System.ArgumentOutOfRangeException(
  556. $"Unknown grass type {grassDetail.GrassType}");
  557. }
  558. createdGrassObjectNumber++;
  559. createdInstances.Add(grassItem);
  560. }
  561. }
  562. MTEDebug.Log("4: created grass object number = " + createdGrassObjectNumber);
  563. if (createdInstances.Count > 0)
  564. {
  565. Undo.UndoRedoManager.Instance().Push(a =>
  566. {
  567. UndoCreate(a);
  568. }, createdInstances);
  569. }
  570. }
  571. private void RemoveGrassInstances(IEnumerable<GrassItem> grassItemsRemoved)
  572. {
  573. List<GrassItem> removedItems = new List<GrassItem>();
  574. foreach (var grassItem in grassItemsRemoved)
  575. {
  576. if (grassItem.Star != null)
  577. {
  578. MTEContext.TheGrassLoader.grassInstanceList.grasses.Remove(grassItem.Star);
  579. }
  580. else if (grassItem.Quad != null)
  581. {
  582. MTEContext.TheGrassLoader.grassInstanceList.quads.Remove(grassItem.Quad);
  583. }
  584. Object.DestroyImmediate(grassItem.gameObject);
  585. grassItem.gameObject = null;
  586. GrassMap.Remove(grassItem);
  587. removedItems.Add(grassItem);
  588. }
  589. Undo.UndoRedoManager.Instance().Push(a =>
  590. {
  591. RedoCreate(removedItems);
  592. }, removedItems);
  593. }
  594. private GrassItem CreateGrassQuad(
  595. Material material,
  596. Vector3 position, float rotationY,
  597. float width, float height)
  598. {
  599. GameObject grassObject;
  600. MeshRenderer grassMeshRenderer; //not used
  601. Mesh grassMesh; //not used
  602. var rotation = Quaternion.Euler(0, rotationY, 0);
  603. GrassUtil.GenerateGrassQuadObject(position, rotation, width, height,
  604. material, out grassObject, out grassMeshRenderer, out grassMesh);
  605. MTEDebug.Log("3: created quad grass object " + grassObject.GetInstanceID());
  606. grassObject.transform.SetParent(MTEContext.TheGrassLoader.transform, true);
  607. GrassQuad quad = new GrassQuad();
  608. quad.Init(material, position, rotationY, width, height);
  609. MTEContext.TheGrassLoader.grassInstanceList.quads.Add(quad);
  610. var grassItem = new GrassItem(quad, grassObject);
  611. GrassMap.Insert(grassItem);
  612. return grassItem;
  613. }
  614. private GrassItem CreateGrassStar(
  615. Material material,
  616. Vector3 position, float rotationY,
  617. float width, float height)
  618. {
  619. GameObject grassObject;
  620. MeshRenderer grassMeshRenderer; //not used
  621. Mesh grassMesh; //not used
  622. var rotation = Quaternion.Euler(0, rotationY, 0);
  623. GrassUtil.GenerateGrassStarObject(position, rotation, width, height,
  624. material, out grassObject, out grassMeshRenderer, out grassMesh);
  625. MTEDebug.Log("3: created star grass object " + grassObject.GetInstanceID());
  626. grassObject.transform.SetParent(MTEContext.TheGrassLoader.transform, true);
  627. GrassStar grassInstance = new GrassStar();
  628. grassInstance.Init(material, position, rotationY, width, height);
  629. MTEContext.TheGrassLoader.grassInstanceList.grasses.Add(grassInstance);
  630. var grassItem = new GrassItem(grassInstance, grassObject);
  631. GrassMap.Insert(grassItem);
  632. return grassItem;
  633. }
  634. private void UndoCreate(List<GrassItem> createdInstances)
  635. {
  636. List<GrassItem> removedItems
  637. = new List<GrassItem>(createdInstances.Count);
  638. //remove created grass instances
  639. foreach (var grassItem in createdInstances)
  640. {
  641. if (grassItem == null)
  642. {
  643. continue;
  644. }
  645. if (grassItem.gameObject)
  646. {
  647. Object.DestroyImmediate(grassItem.gameObject);
  648. grassItem.gameObject = null;
  649. }
  650. GrassMap.Remove(grassItem);
  651. if (grassItem.Star == null && grassItem.Quad == null)
  652. {
  653. continue;
  654. }
  655. if (grassItem.Star != null)
  656. {
  657. MTEContext.TheGrassLoader.grassInstanceList.grasses.Remove(grassItem.Star);
  658. }
  659. else
  660. {
  661. MTEContext.TheGrassLoader.grassInstanceList.quads.Remove(grassItem.Quad);
  662. }
  663. removedItems.Add(grassItem);
  664. }
  665. Undo.UndoRedoManager.Instance().Push(a =>
  666. {
  667. RedoCreate(removedItems);
  668. }, createdInstances);
  669. }
  670. private void RedoCreate(List<GrassItem> removedObjects)
  671. {
  672. List<GrassItem> createdItems = new List<GrassItem>(removeList.Count);
  673. foreach (var undoData in removedObjects)
  674. {
  675. if (undoData.Quad == null && undoData.Star == null)
  676. {//ignore invalid grass item
  677. continue;
  678. }
  679. GrassItem grassItem = null;
  680. if (undoData.Quad != null)
  681. {
  682. var quad = undoData.Quad;
  683. grassItem = CreateGrassQuad(
  684. quad.Material,
  685. quad.Position, quad.RotationY,
  686. quad.Width, quad.Height);
  687. }
  688. else if(undoData.Star != null)
  689. {
  690. var star = undoData.Star;
  691. grassItem = CreateGrassStar(
  692. star.Material,
  693. star.Position, star.RotationY,
  694. star.Width, star.Height);
  695. }
  696. else
  697. {
  698. MTEDebug.LogWarning("Ignored a null grass item when undo/redo.");
  699. continue;
  700. }
  701. createdItems.Add(grassItem);
  702. }
  703. Undo.UndoRedoManager.Instance().Push(a =>
  704. {
  705. UndoCreate(a);
  706. }, createdItems);
  707. }
  708. private void ClearHighlight()
  709. {
  710. foreach (var renderer in highlightedRenderers)
  711. {
  712. if (renderer)
  713. {
  714. Utility.SetHighlight(renderer, false);
  715. }
  716. }
  717. highlightedRenderers.Clear();
  718. }
  719. private void ChangeDirection(float delta, ref float direction)
  720. {
  721. if(delta > 0)
  722. {
  723. direction -= Mathf.PI / 12;
  724. }
  725. else if(delta < 0)
  726. {
  727. direction += Mathf.PI / 12;
  728. }
  729. if(direction < 0)
  730. {
  731. direction += 2*Mathf.PI;
  732. }
  733. if (direction > 2*Mathf.PI)
  734. {
  735. direction -= 2*Mathf.PI;
  736. }
  737. }
  738. private void SaveGrass()
  739. {
  740. EditorUtility.SetDirty(MTEContext.TheGrassLoader.grassInstanceList);
  741. }
  742. /// <summary>
  743. /// Update height of grass items
  744. /// </summary>
  745. private static void UpdateGrasses(IEnumerable<GrassItem> items)
  746. {
  747. bool updated = false;
  748. foreach (var item in items)
  749. {
  750. var pos2D = new Vector2(item.Position2D.x, item.Position2D.y);
  751. var rayOrigin = new Vector3(pos2D.x, 99999f, pos2D.y);
  752. var ray = new Ray(rayOrigin, Vector3.down);
  753. RaycastHit hit;
  754. if (Physics.Raycast(ray, out hit,
  755. Mathf.Infinity,
  756. 1 << MTEContext.TargetLayer//only hit target layer
  757. ))
  758. {
  759. if (!hit.transform.CompareTag(MTEContext.TargetTag))
  760. {
  761. return;
  762. }
  763. item.Height = hit.point.y;
  764. updated = true;
  765. }
  766. }
  767. if (updated)
  768. {
  769. GrassPainter.Instance.SaveGrass();
  770. }
  771. }
  772. /// <summary>
  773. /// Update rotation (Y) of grass items
  774. /// </summary>
  775. private static void UpdateGrasses(IEnumerable<GrassItem> items, float rotationY)
  776. {
  777. foreach (var item in items)
  778. {
  779. var pos2D = new Vector2(item.Position2D.x, item.Position2D.y);
  780. var rayOrigin = new Vector3(pos2D.x, 99999f, pos2D.y);
  781. var ray = new Ray(rayOrigin, Vector3.down);
  782. if (Physics.Raycast(ray, Mathf.Infinity, ~MTEContext.TargetLayer))
  783. {
  784. item.RotationY = rotationY;
  785. }
  786. }
  787. GrassPainter.Instance.SaveGrass();
  788. }
  789. /// <summary>
  790. /// Update height of grass items inside a circular region
  791. /// </summary>
  792. /// <param name="center">center of the circular region</param>
  793. /// <param name="radius">radius of the circular region</param>
  794. public void UpdateGrass(Vector3 center, float radius)
  795. {
  796. var items = new List<GrassItem>();
  797. GrassMap.GetGrassItemsInCircle(center, radius, items);
  798. UpdateGrasses(items);
  799. }
  800. /// <summary>
  801. /// Update height of all grass items
  802. /// </summary>
  803. public void UpdateAllGrasses()
  804. {
  805. var items = GrassMap.GetAllGrassItems();
  806. UpdateGrasses(items);
  807. }
  808. private void BakePointCloudToMesh()
  809. {
  810. bool confirmed = EditorUtility.DisplayDialog(
  811. StringTable.Get(C.Warning),
  812. StringTable.Get(C.Warning_Confirm_UnrecoverableOperation),
  813. StringTable.Get(C.Yes), StringTable.Get(C.No));
  814. if (!confirmed)
  815. {
  816. return;
  817. }
  818. if (!MTEContext.TheGrassLoader)
  819. {
  820. EditorUtility.DisplayDialog(
  821. StringTable.Get(C.Warning),
  822. StringTable.Get(C.Warning_NoGrassLoader_CannotBakePointCloudToMesh),
  823. StringTable.Get(C.OK));
  824. return;
  825. }
  826. MTEContext.TheGrassLoader.RemoveOldGrasses();
  827. MTEContext.TheGrassLoader.GenerateGrasses(new GrassGenerationSettings
  828. {
  829. UseStaticBatch = false,
  830. HideGrassObjectInEditor = false
  831. });
  832. bool removeGrassLoader = EditorUtility.DisplayDialog(
  833. StringTable.Get(C.Info),
  834. StringTable.Get(C.Info_RemoveGrassLoader),
  835. StringTable.Get(C.Yes),
  836. StringTable.Get(C.No));
  837. if (removeGrassLoader)
  838. {
  839. UnityEngine.Object.DestroyImmediate(MTEContext.TheGrassLoader);
  840. }
  841. EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
  842. }
  843. }
  844. }