ObjectPainter.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using UnityEditor;
  4. using UnityEngine;
  5. namespace MTE
  6. {
  7. internal class ObjectPainter : IEditor
  8. {
  9. public static ObjectPainter Instance;
  10. public int Id { get; } = 7;
  11. public string Header
  12. {
  13. get { return StringTable.Get(C.ObjectPainter_Header); }
  14. }
  15. public string Description
  16. {
  17. get { return StringTable.Get(C.ObjectPainter_Description); }
  18. }
  19. public bool Enabled { get; set; } = true;
  20. public string Name { get; } = "ObjectPainter";
  21. public Texture Icon { get; } =
  22. EditorGUIUtility.IconContent("Prefab Icon").image;
  23. public bool WantMouseMove { get; } = false;
  24. public bool WillEditMesh { get; } = false;
  25. #region Constant
  26. // default
  27. const float DefaultBrushSize = 1;
  28. const int DefaultBrushNumber = 1;
  29. const float DefaultBrushDirection = 0;
  30. const bool DefaultUseRandomDirection = true;
  31. const int DefaultReduction = 100;
  32. const bool DefaultAllowOverlap = false;
  33. // min/max
  34. const float MinBrushSize = 0.1f;
  35. const float MaxBrushSize = 10f;
  36. const int MinBrushNumber = 1;
  37. const int MaxBrushNumber = 50;
  38. private const int MinReduction = 1;
  39. private const int MaxReduction = 100;
  40. #endregion
  41. private ObjectDetail selectedDetail
  42. {
  43. get
  44. {
  45. return detailList[SelectedIndex];
  46. }
  47. }
  48. private GameObject target
  49. {
  50. get { return selectedDetail.Object; }
  51. }
  52. private Vector3 minScale
  53. {
  54. get { return selectedDetail.MinScale; }
  55. }
  56. private Vector3 maxScale
  57. {
  58. get { return selectedDetail.MaxScale; }
  59. }
  60. private float brushSize;
  61. private bool useRandomDirection;
  62. private float brushDirection = 0;
  63. public int brushNumber;
  64. public int reduction;
  65. private bool allowOverlap;
  66. private int containerInstanceId;
  67. private List<ObjectDetail> detailList = new List<ObjectDetail>();
  68. /// <summary>
  69. /// Selected object detail index
  70. /// </summary>
  71. public int SelectedIndex
  72. {
  73. get;
  74. set;
  75. }
  76. public Transform Container
  77. {
  78. get
  79. {
  80. if (containerInstanceId == 0)
  81. {
  82. return null;
  83. }
  84. return EditorUtility.InstanceIDToObject(containerInstanceId) as Transform;
  85. }
  86. set
  87. {
  88. if (value == null)
  89. {
  90. containerInstanceId = 0;
  91. }
  92. else
  93. {
  94. containerInstanceId = value.GetInstanceID();
  95. EditorPrefs.SetInt("MTE_ObjectPainter.containerInstanceId",
  96. containerInstanceId);
  97. }
  98. }
  99. }
  100. /// <summary>
  101. /// Brush size (unit: 1 BrushUnit)
  102. /// </summary>
  103. public float BrushSize
  104. {
  105. get { return brushSize; }
  106. set
  107. {
  108. value = Mathf.Clamp(value, MinBrushSize, MaxBrushSize);
  109. if (!MathEx.AmostEqual(brushSize, value))
  110. {
  111. brushSize = value;
  112. EditorPrefs.SetFloat("MTE_ObjectPainter.brushSize", value);
  113. }
  114. }
  115. }
  116. //real brush size
  117. private float BrushSizeInU3D
  118. {
  119. get { return BrushSize * Settings.BrushUnit; }
  120. }
  121. /// <summary>
  122. ///
  123. /// </summary>
  124. public int BrushNumber
  125. {
  126. get
  127. {
  128. return brushNumber;
  129. }
  130. set
  131. {
  132. brushNumber = value;
  133. EditorPrefs.SetFloat("MTE_ObjectPainter.brushNumber", value);
  134. }
  135. }
  136. /// <summary>
  137. /// Brush direction, angle to north(+z)
  138. /// </summary>
  139. public float BrushDirection
  140. {
  141. get
  142. {
  143. return this.brushDirection;
  144. }
  145. set
  146. {
  147. value = Mathf.Clamp(value, 0, 2 * Mathf.PI);
  148. if (!MathEx.AmostEqual(value, this.brushDirection))
  149. {
  150. EditorPrefs.SetFloat("MTE_ObjectPainter.brushDirection", this.brushDirection);
  151. this.brushDirection = value;
  152. }
  153. }
  154. }
  155. /// <summary>
  156. ///
  157. /// </summary>
  158. public bool UseRandomDirection
  159. {
  160. get { return this.useRandomDirection; }
  161. set
  162. {
  163. if (value != useRandomDirection)
  164. {
  165. useRandomDirection = value;
  166. EditorPrefs.SetBool("MTE_ObjectPainter.useRandomDirection", value);
  167. }
  168. }
  169. }
  170. /// <summary>
  171. /// Removing strength (percent)
  172. /// </summary>
  173. public int Reduction
  174. {
  175. get
  176. {
  177. return this.reduction;
  178. }
  179. set
  180. {
  181. if (this.reduction != value)
  182. {
  183. this.reduction = value;
  184. EditorPrefs.SetInt("MTE_ObjectPainter.reduction", value);
  185. }
  186. }
  187. }
  188. public bool AllowOverlap
  189. {
  190. get
  191. {
  192. return this.allowOverlap;
  193. }
  194. set
  195. {
  196. if (value != this.allowOverlap)
  197. {
  198. this.allowOverlap = value;
  199. EditorPrefs.SetBool("MTE_ObjectPainter.allowOverlap", value);
  200. }
  201. }
  202. }
  203. private DetailListBox<ObjectDetail> detailListBox;
  204. public ObjectPainter()
  205. {
  206. MTEContext.EnableEvent += (sender, args) =>
  207. {
  208. if (MTEContext.editor == this)
  209. {
  210. LoadSavedParamter();
  211. LoadObjectDetailList();
  212. }
  213. };
  214. MTEContext.EditTypeChangedEvent += (sender, args) =>
  215. {
  216. if (MTEContext.editor == this)
  217. {
  218. LoadSavedParamter();
  219. LoadObjectDetailList();
  220. }
  221. };
  222. // Load default parameters
  223. brushSize = DefaultBrushSize;
  224. brushNumber = DefaultBrushNumber;
  225. brushDirection = DefaultBrushDirection;
  226. useRandomDirection = DefaultUseRandomDirection;
  227. reduction = DefaultReduction;
  228. allowOverlap = DefaultAllowOverlap;
  229. Instance = this;
  230. }
  231. public HashSet<Hotkey> DefineHotkeys()
  232. {
  233. return new HashSet<Hotkey>
  234. {
  235. new Hotkey(this, KeyCode.LeftBracket, () =>
  236. {
  237. BrushSize -= 1;
  238. MTEEditorWindow.Instance.Repaint();
  239. }),
  240. new Hotkey(this, KeyCode.RightBracket, () =>
  241. {
  242. BrushSize += 1;
  243. MTEEditorWindow.Instance.Repaint();
  244. }),
  245. new Hotkey(this, KeyCode.Minus, () =>
  246. {
  247. BrushNumber -= 1;
  248. MTEEditorWindow.Instance.Repaint();
  249. }),
  250. new Hotkey(this, KeyCode.Equals, () =>
  251. {
  252. BrushNumber += 1;
  253. MTEEditorWindow.Instance.Repaint();
  254. })
  255. };
  256. }
  257. private void LoadSavedParamter()
  258. {
  259. // Load parameters from the EditorPrefs
  260. brushSize = EditorPrefs.GetFloat("MTE_ObjectPainter.brushSize", DefaultBrushSize);
  261. brushNumber = EditorPrefs.GetInt("MTE_ObjectPainter.brushNumber", DefaultBrushNumber);
  262. brushDirection = EditorPrefs.GetFloat("MTE_ObjectPainter.brushDirection", DefaultBrushDirection);
  263. useRandomDirection = EditorPrefs.GetBool("MTE_ObjectPainter.useRandomDirection", DefaultUseRandomDirection);
  264. reduction = EditorPrefs.GetInt("MTE_ObjectPainter.reduction", DefaultReduction);
  265. containerInstanceId = EditorPrefs.GetInt("MTE_ObjectPainter.containerInstanceId", 0);
  266. allowOverlap =
  267. EditorPrefs.GetBool("MTE_ObjectPainter.allowOverlap", DefaultAllowOverlap);
  268. }
  269. public void DoArgsGUI()
  270. {
  271. // Details
  272. if (!Settings.CompactGUI)
  273. {
  274. GUILayout.Label(StringTable.Get(C.Prefab), MTEStyles.SubHeader);
  275. }
  276. // detail list box
  277. SelectedIndex = detailListBox.DoGUI(SelectedIndex);
  278. //Settings
  279. if (!Settings.CompactGUI)
  280. {
  281. EditorGUILayout.Space();
  282. GUILayout.Label(StringTable.Get(C.Settings), MTEStyles.SubHeader);
  283. }
  284. EditorGUILayout.BeginHorizontal();
  285. {
  286. var label = new GUIContent(StringTable.Get(C.Container));
  287. var size = GUIStyle.none.CalcSize(label);
  288. EditorGUILayout.LabelField(label, GUILayout.Width(size.x + 10), GUILayout.MinWidth(80));
  289. Container = (Transform)EditorGUILayout.ObjectField(Container, typeof(Transform), true);
  290. }
  291. EditorGUILayout.EndHorizontal();
  292. BrushSize = EditorGUILayoutEx.Slider(StringTable.Get(C.Size), "-", "+", BrushSize, MinBrushSize, MaxBrushSize);
  293. BrushNumber = EditorGUILayoutEx.IntSlider(StringTable.Get(C.Number), "[", "]", BrushNumber, MinBrushNumber, MaxBrushNumber);
  294. Reduction = EditorGUILayoutEx.IntSlider(StringTable.Get(C.Reduction), Reduction, MinReduction, MaxReduction);
  295. EditorGUILayout.BeginHorizontal();
  296. {
  297. var label = new GUIContent(StringTable.Get(C.Direction));
  298. var size = GUIStyle.none.CalcSize(label);
  299. EditorGUILayout.LabelField(label, GUILayout.Width(size.x + 10), GUILayout.MinWidth(60));
  300. EditorGUILayout.BeginVertical();
  301. UseRandomDirection = GUILayout.Toggle(UseRandomDirection, StringTable.Get(C.Random));
  302. if (!UseRandomDirection)
  303. {
  304. EditorGUILayout.LabelField(string.Format("{0}°", Mathf.Rad2Deg * BrushDirection));
  305. EditorGUILayout.HelpBox(StringTable.Get(C.Info_HowToRotate), MessageType.Info);
  306. }
  307. EditorGUILayout.EndVertical();
  308. }
  309. EditorGUILayout.EndHorizontal();
  310. EditorGUILayout.BeginHorizontal();
  311. {
  312. var label = new GUIContent(StringTable.Get(C.AllowOverlap));
  313. var size = GUIStyle.none.CalcSize(label);
  314. EditorGUILayout.LabelField(label, GUILayout.Width(size.x + 10), GUILayout.MinWidth(60));
  315. AllowOverlap = EditorGUILayout.Toggle(AllowOverlap);
  316. }
  317. EditorGUILayout.EndHorizontal();
  318. }
  319. private List<GameObject> items = new List<GameObject>();
  320. private List<Vector2> targetPositions = new List<Vector2>();
  321. private readonly List<GameObject> removeList = new List<GameObject>(256);
  322. private readonly RaycastHit[] raycastHits = new RaycastHit[256];
  323. public void OnSceneGUI()
  324. {
  325. var e = Event.current;
  326. if (e.commandName == "UndoRedoPerformed")
  327. {
  328. SceneView.RepaintAll();
  329. return;
  330. }
  331. if(!UseRandomDirection && e.control)
  332. {
  333. RaycastHit hit;
  334. Ray ray1 = HandleUtility.GUIPointToWorldRay(e.mousePosition);
  335. if (Physics.Raycast(ray1, out hit,
  336. Mathf.Infinity,
  337. 1 << MTEContext.TargetLayer//only hit target layer
  338. ))
  339. {
  340. //check tag
  341. if (!hit.transform.CompareTag(MTEContext.TargetTag))
  342. {
  343. return;
  344. }
  345. Handles.ArrowHandleCap(0, hit.point,
  346. Quaternion.Euler(0, BrushDirection * Mathf.Rad2Deg, 0),
  347. 10 * Settings.PointSize, EventType.Repaint);
  348. }
  349. }
  350. // do nothing when mouse middle/right button, control/alt key is pressed
  351. if (e.button != 0 || e.alt)
  352. return;
  353. // no detail
  354. if (this.detailList.Count == 0)
  355. {
  356. return;
  357. }
  358. HandleUtility.AddDefaultControl(0);
  359. Ray ray = HandleUtility.GUIPointToWorldRay(e.mousePosition);
  360. var hitCount = Physics.RaycastNonAlloc(ray, raycastHits,
  361. Mathf.Infinity,
  362. 1 << MTEContext.TargetLayer //only hit target layer
  363. );
  364. if (hitCount > 0)
  365. {
  366. RaycastHit raycastHit = new RaycastHit();
  367. for (var index = 0; index < hitCount; index++)
  368. {
  369. var hit = raycastHits[index];
  370. if (!hit.transform)
  371. {
  372. continue;
  373. }
  374. if (!MTEContext.Targets.Contains(hit.transform.gameObject))
  375. {
  376. continue;
  377. }
  378. raycastHit = hit;
  379. }
  380. if (Settings.ShowBrushRect)
  381. {
  382. Utility.ShowBrushRect(raycastHit.point, BrushSizeInU3D);
  383. }
  384. var hitPoint = raycastHit.point;
  385. Handles.color = Color.green;
  386. Handles.DrawWireDisc(hitPoint, raycastHit.normal, BrushSizeInU3D);
  387. if (!UseRandomDirection)
  388. {
  389. if (e.control)
  390. {
  391. GetObjectItemsInCircle(target, hitPoint, BrushSizeInU3D, items);
  392. }
  393. }
  394. // not using random direction
  395. // hold control key and scroll wheel to change
  396. // 1. item's rotationY
  397. // 2. brush direction
  398. if (!UseRandomDirection && e.control && !e.isKey && e.type == EventType.ScrollWheel)
  399. {
  400. float oldDirection = BrushDirection;
  401. float direction = oldDirection;
  402. ChangeDirection(e.delta.y, ref direction);
  403. if (Mathf.Abs(direction - oldDirection) > Mathf.Epsilon)
  404. {
  405. UpdateObjects(items, Mathf.Rad2Deg * direction);
  406. MTEEditorWindow.Instance.Repaint();
  407. BrushDirection = direction;
  408. }
  409. e.Use();
  410. }
  411. else if (e.type == EventType.MouseDown || e.type == EventType.MouseDrag)
  412. {
  413. if (e.type == EventType.MouseDown)
  414. {
  415. objectPaintTransation =
  416. new Undo.UndoTransaction(
  417. e.shift ?
  418. "Object Painter: Delete Prefab Instances" :
  419. "Object Painter: Create Prefab Instances"
  420. );
  421. Undo.UndoRedoManager.Instance().StartTransaction(objectPaintTransation);
  422. }
  423. if (!e.shift)
  424. {//adding
  425. targetPositions.Clear();
  426. MathEx.UniformPointsInCircle(
  427. new Vector2(hitPoint.x, hitPoint.z),
  428. this.BrushSizeInU3D,
  429. this.BrushNumber,
  430. ref targetPositions);
  431. CreateObjectInstances();
  432. }
  433. else
  434. {//removing
  435. removeList.Clear();
  436. GetObjectItemsInCircle(target, hitPoint, BrushSizeInU3D, removeList);
  437. int removeCount = Mathf.CeilToInt(this.Reduction / 100.0f * this.removeList.Count);
  438. if (removeCount != 0)
  439. {
  440. var itemsRemoved = this.removeList.TakeRandom(removeCount).ToList();
  441. RemoveObjectInstances(itemsRemoved);
  442. }
  443. }
  444. }
  445. if (e.type == EventType.MouseUp)
  446. {
  447. if (objectPaintTransation != null)
  448. {
  449. Undo.UndoRedoManager.Instance().EndTransaction(objectPaintTransation);
  450. Utility.RefreshHistoryViewer();
  451. objectPaintTransation = null;
  452. }
  453. }
  454. }
  455. SceneView.RepaintAll();
  456. }
  457. Undo.UndoTransaction objectPaintTransation;
  458. internal struct ObjectPainterUndoData
  459. {
  460. public Vector3 position;
  461. public Quaternion rotation;
  462. public Vector3 localScale;
  463. public Transform parent;
  464. public Object prefab;
  465. }
  466. private static Bounds GetBounds(GameObject gameObject)
  467. {
  468. bool found = false;
  469. Bounds bounds = new Bounds();
  470. var meshFilter = gameObject.GetComponent<MeshFilter>();
  471. if (meshFilter)
  472. {
  473. bounds = meshFilter.sharedMesh.bounds;
  474. found = true;
  475. }
  476. var collider = gameObject.GetComponent<Collider>();
  477. if (collider is MeshCollider)
  478. {
  479. var meshCollider = collider as MeshCollider;
  480. bounds = meshCollider.sharedMesh.bounds;
  481. found = true;
  482. }
  483. if (collider is BoxCollider)
  484. {
  485. var boxCollider = collider as BoxCollider;
  486. bounds = new Bounds(boxCollider.center, boxCollider.size);
  487. found = true;
  488. }
  489. if (found)
  490. {
  491. bounds = bounds.Transform(gameObject.transform);
  492. //TODO add bounds debugger in Debug mode
  493. return bounds;
  494. }
  495. MTEDebug.LogWarning("Failed to fetch bounds of gameObject:" +
  496. " fallback to default bounds at GameObject position with size of GameObject scale");
  497. return new Bounds(gameObject.transform.position, gameObject.transform.localScale);
  498. }
  499. private bool IntersectWithExistingObjects(GameObject gameObject)
  500. {
  501. var bounds = GetBounds(gameObject);
  502. if (Container != null)
  503. {
  504. int n = Container.childCount;
  505. for (int i = 0; i < n; i++)
  506. {
  507. Transform child = Container.GetChild(i);
  508. if (gameObject == child.gameObject)
  509. {
  510. continue;
  511. }
  512. Bounds existingObjectBounds = GetBounds(child.gameObject);
  513. if (existingObjectBounds.Intersects(bounds))
  514. {
  515. return true;
  516. }
  517. }
  518. }
  519. else//loop through all object listed in detail
  520. {
  521. var objects = UnityEngine.Object.FindObjectsOfType<GameObject>();
  522. foreach (var o in objects)
  523. {
  524. if (gameObject == o)
  525. {
  526. continue;
  527. }
  528. foreach (var objectDetail in detailList)
  529. {
  530. var prefab = objectDetail.Object;
  531. if (!CompatibilityUtil.IsInstanceOfPrefab(o, prefab))
  532. {
  533. continue;
  534. }
  535. Bounds existingObjectBounds = GetBounds(o);
  536. if (existingObjectBounds.Intersects(bounds))
  537. {
  538. return true;
  539. }
  540. }
  541. }
  542. }
  543. return false;
  544. }
  545. private void CreateObjectInstances()
  546. {
  547. List<GameObject> createdInstances = new List<GameObject>(targetPositions.Count);
  548. for (int j = 0; j < targetPositions.Count; j++)
  549. {
  550. var rotationY = UseRandomDirection ? Random.Range(0f, 180f) : Mathf.Rad2Deg * this.BrushDirection;
  551. var targetRotation = Quaternion.Euler(0, rotationY, 0);
  552. var targetPosition = targetPositions[j];
  553. RaycastHit hit;
  554. //If overlap is not allowed, we cannot place object on any position that hit any other collider
  555. //except mesh-terrains(editing target, namely MTEContent.Targets).
  556. if (!AllowOverlap)
  557. {
  558. if (Physics.Raycast(
  559. new Ray(new Vector3(targetPosition.x, 10000, targetPosition.y),
  560. new Vector3(0, -1f, 0)),
  561. out hit,
  562. Mathf.Infinity
  563. ))
  564. {
  565. if (!MTEContext.Targets.Contains(hit.transform.gameObject))
  566. {
  567. continue;
  568. }
  569. }
  570. }
  571. if (Physics.Raycast(
  572. new Ray(new Vector3(targetPosition.x, 10000, targetPosition.y),
  573. new Vector3(0, -1f, 0)),
  574. out hit,
  575. Mathf.Infinity,
  576. 1 << MTEContext.TargetLayer //only hit target layer
  577. ))
  578. {
  579. if (!hit.transform)
  580. {
  581. continue;
  582. }
  583. if (!MTEContext.Targets.Contains(hit.transform.gameObject))
  584. {
  585. continue;
  586. }
  587. var o = PrefabUtility.InstantiatePrefab(target) as GameObject;
  588. o.transform.position = hit.point;
  589. o.transform.rotation = targetRotation;
  590. o.transform.parent = Container;
  591. if (selectedDetail.UseUnifiedScale)
  592. {
  593. var s = Random.Range(minScale.x, maxScale.x);
  594. o.transform.localScale = new Vector3(s, s, s);
  595. }
  596. else
  597. {
  598. o.transform.localScale =
  599. new Vector3(Random.Range(minScale.x, maxScale.x),
  600. Random.Range(minScale.y, maxScale.y),
  601. Random.Range(minScale.z, maxScale.z));
  602. }
  603. if (!AllowOverlap)
  604. {
  605. //remove object that will overlap with existing objects
  606. //only object in detail list are considered
  607. if (IntersectWithExistingObjects(o))
  608. {
  609. Object.DestroyImmediate(o);
  610. continue;
  611. }
  612. }
  613. createdInstances.Add(o);
  614. }
  615. }
  616. Undo.UndoRedoManager.Instance().Push(a =>
  617. {
  618. UndoCreate(a);
  619. }, createdInstances);
  620. }
  621. private void RemoveObjectInstances(List<GameObject> itemsRemoved)
  622. {
  623. List<ObjectPainterUndoData> removedObjects
  624. = new List<ObjectPainterUndoData>(itemsRemoved.Count);
  625. foreach (var instance in itemsRemoved)
  626. {
  627. var prefab = CompatibilityUtil.GetPrefabRoot(instance);
  628. if (!prefab)
  629. {//cannot get the prefab of this GameObject
  630. continue;
  631. }
  632. var t = instance.transform;
  633. var position = t.position;
  634. var rotation = t.rotation;
  635. var localScale = t.localScale;
  636. var parent = t.parent;
  637. var undoData = new ObjectPainterUndoData
  638. {
  639. position = position,
  640. rotation = rotation,
  641. localScale = localScale,
  642. parent = parent,
  643. prefab = prefab,
  644. };
  645. Object.DestroyImmediate(instance);
  646. removedObjects.Add(undoData);
  647. }
  648. Undo.UndoRedoManager.Instance().Push(a =>
  649. {
  650. RedoCreate(a);
  651. }, removedObjects);
  652. }
  653. private void RedoCreate(List<ObjectPainterUndoData> removedObjects)
  654. {
  655. List<GameObject> createdInstances = new List<GameObject>(removeList.Count);
  656. foreach (var undoData in removedObjects)
  657. {
  658. if (!undoData.prefab)
  659. {//ignore invalid prefab
  660. continue;
  661. }
  662. var o = PrefabUtility.InstantiatePrefab(undoData.prefab) as GameObject;
  663. o.transform.position = undoData.position;
  664. o.transform.rotation = undoData.rotation;
  665. o.transform.parent = undoData.parent;
  666. o.transform.localScale = undoData.localScale;
  667. createdInstances.Add(o);
  668. }
  669. Undo.UndoRedoManager.Instance().Push(a =>
  670. {
  671. UndoCreate(a);
  672. }, createdInstances);
  673. }
  674. private void UndoCreate(List<GameObject> createdInstances)
  675. {
  676. List<ObjectPainterUndoData> removedObjects
  677. = new List<ObjectPainterUndoData>(createdInstances.Count);
  678. foreach (var instance in createdInstances)
  679. {
  680. if (!instance)
  681. {//already destroyed by others
  682. continue;
  683. }
  684. var prefab = CompatibilityUtil.GetPrefabRoot(instance);
  685. if (!prefab)
  686. {//cannot get the prefab of this GameObject
  687. continue;
  688. }
  689. var t = instance.transform;
  690. Vector3 position = t.position;
  691. Quaternion rotation = t.rotation;
  692. Vector3 localScale = t.localScale;
  693. Transform parent = t.parent;
  694. var undoData = new ObjectPainterUndoData
  695. {
  696. position = position,
  697. rotation = rotation,
  698. localScale = localScale,
  699. parent = parent,
  700. prefab = prefab,
  701. };
  702. Object.DestroyImmediate(instance);
  703. removedObjects.Add(undoData);
  704. }
  705. Undo.UndoRedoManager.Instance().Push(a =>
  706. {
  707. RedoCreate(removedObjects);
  708. }, createdInstances);
  709. }
  710. private void ChangeDirection(float delta, ref float direction)
  711. {
  712. if(delta > 0)
  713. {
  714. direction -= Mathf.PI / 12;
  715. }
  716. else if(delta < 0)
  717. {
  718. direction += Mathf.PI / 12;
  719. }
  720. if(direction < 0)
  721. {
  722. direction += 2*Mathf.PI;
  723. }
  724. if (direction > 2*Mathf.PI)
  725. {
  726. direction -= 2*Mathf.PI;
  727. }
  728. }
  729. static Collider[] colliders = new Collider[256];
  730. private static void GetObjectItemsInCircle(GameObject prefab, Vector3 center, float radius, List<GameObject> result)
  731. {
  732. result.Clear();
  733. int length = Physics.OverlapSphereNonAlloc(center, radius, colliders, ~MTEContext.TargetLayer);
  734. for (int i = 0; i < length; i++)
  735. {
  736. var collider = colliders[i];
  737. var obj = collider.gameObject;
  738. if (CompatibilityUtil.IsPrefab(obj) && CompatibilityUtil.IsInstanceOfPrefab(obj, prefab))
  739. {
  740. result.Add(collider.gameObject);
  741. }
  742. }
  743. }
  744. private void LoadObjectDetailList()
  745. {
  746. if (detailListBox == null)
  747. {
  748. detailListBox = new ObjectDetailListBox();
  749. }
  750. var path = Res.DetailDir + "SavedObjectDetailList.asset";
  751. var relativePath = Utility.GetUnityPath(path);
  752. var obj = AssetDatabase.LoadAssetAtPath<ObjectDetailList>(relativePath);
  753. if (obj != null && obj.list != null)
  754. {
  755. detailList = obj.list;
  756. detailListBox.SetEditingTarget(detailList);
  757. MTEDebug.Log($"ObjectDetailList loaded from {path}");
  758. }
  759. else
  760. {
  761. obj = ScriptableObject.CreateInstance<ObjectDetailList>();
  762. obj.list = new List<ObjectDetail>(4);
  763. AssetDatabase.CreateAsset(obj, relativePath);
  764. EditorUtility.SetDirty(obj);
  765. detailListBox.SetEditingTarget(detailList);
  766. MTEDebug.Log($"No ObjectDetailList found at {path}, created a new SavedObjectDetailList.asset.");
  767. }
  768. }
  769. /// <summary>
  770. /// Update height of GameObjects
  771. /// </summary>
  772. /// <param name="items"></param>
  773. private static void UpdateObjects(List<GameObject> items)
  774. {
  775. foreach (var item in items)
  776. {
  777. var pos = item.transform.position;
  778. var pos2D = new Vector2(pos.x, pos.y);
  779. var rayOrigin = new Vector3(pos2D.x, 99999f, pos2D.y);
  780. var ray = new Ray(rayOrigin, Vector3.down);
  781. RaycastHit hit;
  782. if (Physics.Raycast(ray, out hit,
  783. Mathf.Infinity,
  784. 1 << MTEContext.TargetLayer//only hit target layer
  785. ))
  786. {
  787. //check tag
  788. if (!hit.transform.CompareTag(MTEContext.TargetTag))
  789. {
  790. return;
  791. }
  792. pos.y = hit.point.y;
  793. item.transform.position = pos;
  794. }
  795. }
  796. }
  797. /// <summary>
  798. /// Update rotation (Y) of GameObjects
  799. /// </summary>
  800. private static void UpdateObjects(List<GameObject> items, float rotationY)
  801. {
  802. foreach (var item in items)
  803. {
  804. item.transform.rotation = Quaternion.Euler(0, rotationY, 0);
  805. }
  806. }
  807. }
  808. }