FloorManager.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. /*
  2. Copyright(c) 2021 Omar Duarte
  3. Unauthorized copying of this file, via any medium is strictly prohibited.
  4. Writen by Omar Duarte, 2021.
  5. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  6. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  7. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  8. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  9. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  10. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  11. THE SOFTWARE.
  12. */
  13. using System.Linq;
  14. using UnityEngine;
  15. namespace PluginMaster
  16. {
  17. #region DATA & SETTINGS
  18. [System.Serializable]
  19. public struct FloorCellSize
  20. {
  21. [SerializeField] private string _name;
  22. [SerializeField] private Vector3 _size;
  23. public FloorCellSize(string name, Vector3 size)
  24. {
  25. _name = name;
  26. _size = size;
  27. }
  28. public string name { get => _name; set => _name = value; }
  29. public Vector3 size { get => _size; set => _size = value; }
  30. }
  31. [System.Serializable]
  32. public class FloorSettings : ModularToolBase, ISerializationCallbackReceiver
  33. {
  34. #region SIZES
  35. [SerializeField] private bool _swapXZ = false;
  36. [SerializeField] private FloorCellSize[] _sizes = null;
  37. private const string DEFAULT_SIZE_NAME = "Default";
  38. [SerializeField] private string _selectedSizeName = DEFAULT_SIZE_NAME;
  39. private System.Collections.Generic.Dictionary<string, Vector3> _sizesDictionary
  40. = new System.Collections.Generic.Dictionary<string, Vector3>() { { DEFAULT_SIZE_NAME, Vector3.one } };
  41. public string selectedSizeName
  42. {
  43. get => _selectedSizeName;
  44. set
  45. {
  46. if (_selectedSizeName == value) return;
  47. _selectedSizeName = value;
  48. moduleSize = _sizesDictionary[selectedSizeName];
  49. DataChanged();
  50. }
  51. }
  52. public void SaveSize(string name)
  53. {
  54. if (_sizesDictionary.ContainsKey(name)) _sizesDictionary[name] = moduleSize;
  55. else _sizesDictionary.Add(name, moduleSize);
  56. _selectedSizeName = name;
  57. DataChanged();
  58. }
  59. public string[] GetSizesNames() => _sizesDictionary.Keys.ToArray();
  60. public void DeleteSelectedSize()
  61. {
  62. _sizesDictionary.Remove(_selectedSizeName);
  63. selectedSizeName = DEFAULT_SIZE_NAME;
  64. }
  65. public int GetIndexOfSize(string name) => _sizesDictionary.Keys.Select((key, index) => new { key, index })
  66. .FirstOrDefault(pair => pair.key == name)?.index ?? -1;
  67. public int GetIndexOfSelectedSize() => GetIndexOfSize(selectedSizeName);
  68. public string GetSizeAt(int index) => _sizesDictionary.Keys.ElementAt(index);
  69. public void SelectSize(int index) => selectedSizeName = GetSizeAt(index);
  70. public void ResetSize()
  71. {
  72. moduleSize = _sizesDictionary[selectedSizeName];
  73. DataChanged();
  74. }
  75. public bool swapXZ => _swapXZ;
  76. public void SwapXZ()
  77. {
  78. _swapXZ = !_swapXZ;
  79. OnDataChanged();
  80. }
  81. public override Vector3 moduleSize
  82. {
  83. get
  84. {
  85. var size = base.moduleSize;
  86. if (_swapXZ)
  87. {
  88. size.x = base.moduleSize.z;
  89. size.z = base.moduleSize.x;
  90. }
  91. return size;
  92. }
  93. }
  94. #endregion
  95. public void OnBeforeSerialize()
  96. {
  97. _sizes = _sizesDictionary.Select(pair => new FloorCellSize(pair.Key, pair.Value)).ToArray();
  98. }
  99. public void OnAfterDeserialize()
  100. {
  101. if (_sizes == null || _sizes.Length == 0) return;
  102. _sizesDictionary = _sizes.ToDictionary(origin => origin.name, origin => origin.size);
  103. }
  104. }
  105. [System.Serializable]
  106. public class FloorManager : ToolManagerBase<FloorSettings>
  107. {
  108. public enum ToolState
  109. {
  110. FIRST_CORNER,
  111. SECOND_CORNER
  112. }
  113. public static ToolState _state = ToolState.FIRST_CORNER;
  114. public static ToolState state
  115. {
  116. get => _state;
  117. set
  118. {
  119. if (_state == value) return;
  120. _state = value;
  121. if (_state == ToolState.FIRST_CORNER) BrushstrokeManager.ResetCellCount();
  122. }
  123. }
  124. public static Vector3 firstCorner { get; set; } = Vector3.zero;
  125. public static Vector3 secondCorner { get; set; } = Vector3.zero;
  126. public static int quarterTurns { get; set; } = 0;
  127. }
  128. #endregion
  129. #region PWBIO
  130. public static partial class PWBIO
  131. {
  132. #region HANDLERS
  133. private static void FloorInitializeOnLoad()
  134. {
  135. FloorManager.settings.OnDataChanged += OnFloorSettingsChanged;
  136. BrushSettings.OnBrushSettingsChanged += UpdateFloorSettingsOnBrushChanged;
  137. SnapManager.settings.OnGridOriginChange += OnFloorGridOriginChange;
  138. }
  139. private static void SetSnapStepToFloorCellSize()
  140. {
  141. SnapManager.settings.step = FloorManager.settings.moduleSize + FloorManager.settings.spacing;
  142. UnityEditor.SceneView.RepaintAll();
  143. }
  144. private static void OnFloorSettingsChanged()
  145. {
  146. repaint = true;
  147. BrushstrokeManager.UpdateFloorBrushstroke(setNextIdx: false);
  148. SetSnapStepToFloorCellSize();
  149. }
  150. public static void UpdateFloorSettingsOnBrushChanged()
  151. {
  152. if (ToolManager.tool != ToolManager.PaintTool.FLOOR) return;
  153. FloorManager.quarterTurns = 0;
  154. FloorManager.settings.UpdateCellSize();
  155. SetSnapStepToFloorCellSize();
  156. FloorManager.state = FloorManager.ToolState.FIRST_CORNER;
  157. }
  158. public static void OnFloorGridOriginChange()
  159. {
  160. if (ToolManager.tool != ToolManager.PaintTool.FLOOR) return;
  161. repaint = true;
  162. BrushstrokeManager.UpdateFloorBrushstroke(setNextIdx: false);
  163. SetSnapStepToFloorCellSize();
  164. }
  165. #endregion
  166. public static void OnFloorEnabled()
  167. {
  168. SnapManager.settings.radialGridEnabled = false;
  169. SnapManager.settings.gridOnY = true;
  170. SnapManager.settings.visibleGrid = true;
  171. SnapManager.settings.lockedGrid = true;
  172. SnapManager.settings.snappingOnX = true;
  173. SnapManager.settings.snappingOnZ = true;
  174. SnapManager.settings.snappingEnabled = true;
  175. UpdateFloorSettingsOnBrushChanged();
  176. SnapManager.settings.DataChanged(repaint: true);
  177. FloorManager.state = FloorManager.ToolState.FIRST_CORNER;
  178. FloorManager.quarterTurns = 0;
  179. }
  180. private static void FloorToolDuringSceneGUI(UnityEditor.SceneView sceneView)
  181. {
  182. if (PaletteManager.selectedBrush == null) return;
  183. var mousePos2D = Event.current.mousePosition;
  184. var mouseRay = UnityEditor.HandleUtility.GUIPointToWorldRay(mousePos2D);
  185. var mousePos3D = Vector3.zero;
  186. var localMousePos3D = Vector3.zero;
  187. if (GridRaycast(mouseRay, out RaycastHit gridHit))
  188. mousePos3D = SnapFloorTilePosition(gridHit.point, out localMousePos3D);
  189. else return;
  190. if (FloorInput(sceneView.camera, mousePos3D)) return;
  191. switch (FloorManager.state)
  192. {
  193. case FloorManager.ToolState.FIRST_CORNER:
  194. if (_modularDeleteMode) PreviewFloorDeleteSingleTile(sceneView.camera, mousePos3D);
  195. else PreviewFloorSingleTile(sceneView.camera, mousePos3D);
  196. break;
  197. case FloorManager.ToolState.SECOND_CORNER:
  198. if (_modularDeleteMode)
  199. PreviewFloorDeleteRectangle(sceneView.camera, mousePos3D);
  200. else PreviewFloorRectangle(sceneView.camera);
  201. break;
  202. }
  203. FloorInfoText(sceneView, localMousePos3D);
  204. }
  205. private static void FloorInfoText(UnityEditor.SceneView sceneView, Vector3 localMousePos3D)
  206. {
  207. if (!PWBCore.staticData.showInfoText) return;
  208. var localX = Mathf.RoundToInt(localMousePos3D.x / SnapManager.settings.step.x);
  209. if (localX >= 0) ++localX;
  210. var localZ = Mathf.RoundToInt(localMousePos3D.z / SnapManager.settings.step.z);
  211. if (localZ >= 0) ++localZ;
  212. var labelTexts = new string[] { $"Position: (X: {localX}, Z: {localZ})",
  213. $"Size: (X: {BrushstrokeManager.cellsCountX}, Z: {BrushstrokeManager.cellsCountZ})"};
  214. InfoText.Draw(sceneView, labelTexts);
  215. }
  216. private static Vector3 _floorSecondCorner = Vector3.zero;
  217. private static bool FloorInput(Camera camera, Vector3 mousePos3D)
  218. {
  219. if ((Event.current.type == EventType.KeyUp || Event.current.type == EventType.KeyDown))
  220. {
  221. if (Event.current.control && !Event.current.alt && !Event.current.shift) _modularDeleteMode = true;
  222. else if (_modularDeleteMode && (!Event.current.control || Event.current.alt || Event.current.shift))
  223. {
  224. _modularDeleteMode = false;
  225. FloorManager.state = FloorManager.ToolState.FIRST_CORNER;
  226. return true;
  227. }
  228. }
  229. if (PWBSettings.shortcuts.floorRotate90YCW.Check())
  230. {
  231. ++FloorManager.quarterTurns;
  232. if (FloorManager.quarterTurns >= 4) FloorManager.quarterTurns = 0;
  233. FloorManager.settings.UpdateCellSize();
  234. SetSnapStepToFloorCellSize();
  235. FloorManager.state = FloorManager.ToolState.FIRST_CORNER;
  236. BrushstrokeManager.UpdateFloorBrushstroke(setNextIdx: false);
  237. return true;
  238. }
  239. if (Event.current.button == 0)
  240. {
  241. if (Event.current.type == EventType.MouseDown)
  242. {
  243. FloorManager.state = FloorManager.ToolState.SECOND_CORNER;
  244. FloorManager.secondCorner = FloorManager.firstCorner = mousePos3D;
  245. BrushstrokeManager.UpdateFloorBrushstroke(setNextIdx: false, _modularDeleteMode);
  246. return true;
  247. }
  248. if (FloorManager.state == FloorManager.ToolState.SECOND_CORNER)
  249. {
  250. if (Event.current.type == EventType.MouseDrag)
  251. {
  252. FloorManager.secondCorner = mousePos3D;
  253. if (_floorSecondCorner != FloorManager.secondCorner)
  254. BrushstrokeManager.UpdateFloorBrushstroke(setNextIdx: true, _modularDeleteMode);
  255. }
  256. if (Event.current.type == EventType.MouseUp || Event.current.type == EventType.MouseMove)
  257. {
  258. FloorManager.secondCorner = mousePos3D;
  259. var paintStrokeCount = _paintStroke.Count;
  260. if (_modularDeleteMode)
  261. {
  262. BrushstrokeManager.UpdateFloorBrushstroke(setNextIdx: false, _modularDeleteMode);
  263. DeleteFloor();
  264. }
  265. else Paint(FloorManager.settings);
  266. FloorManager.state = FloorManager.ToolState.FIRST_CORNER;
  267. if (paintStrokeCount == 1) BrushstrokeManager.UpdateFloorBrushstroke(setNextIdx: true);
  268. return true;
  269. }
  270. }
  271. _floorSecondCorner = FloorManager.secondCorner;
  272. }
  273. if (Event.current.isKey && Event.current.keyCode == KeyCode.Escape)
  274. {
  275. FloorManager.state = FloorManager.ToolState.FIRST_CORNER;
  276. BrushstrokeManager.UpdateFloorBrushstroke(setNextIdx: false);
  277. return true;
  278. }
  279. return false;
  280. }
  281. private static Vector3 GetCenterToPivot(GameObject prefab, Vector3 scaleMult, Quaternion rotation)
  282. {
  283. var itemBounds = BoundsUtils.GetBoundsRecursive(prefab.transform, Quaternion.identity);
  284. var centerToPivot = prefab.transform.position - itemBounds.center;
  285. centerToPivot = Vector3.Scale(centerToPivot, scaleMult);
  286. centerToPivot = rotation * centerToPivot;
  287. return centerToPivot;
  288. }
  289. private static void PreviewFloorSingleTile(Camera camera, Vector3 mousePos3D)
  290. {
  291. BrushstrokeItem[] brushstroke = BrushstrokeManager.brushstroke;
  292. if (brushstroke.Length == 0) return;
  293. var strokeItem = brushstroke[0].Clone();
  294. if (strokeItem.settings == null)
  295. {
  296. BrushstrokeManager.UpdateFloorBrushstroke(setNextIdx: false);
  297. return;
  298. }
  299. var prefab = strokeItem.settings.prefab;
  300. if (prefab == null) return;
  301. var toolSettings = FloorManager.settings;
  302. var itemRotation = Quaternion.Euler(strokeItem.additionalAngle);
  303. var halfCellSize = toolSettings.moduleSize / 2;
  304. var halfStep = Mathf.Min(SnapManager.settings.step.x, SnapManager.settings.step.z) * 0.4999;
  305. var nearbyObjects = new System.Collections.Generic.List<GameObject>();
  306. var cellCenter = mousePos3D;
  307. boundsOctree.GetColliding(cellCenter, halfCellSize, SnapManager.settings.rotation,
  308. itemRotation, nearbyObjects);
  309. if (nearbyObjects.Count > 0)
  310. {
  311. bool checkNextItem = false;
  312. foreach (var obj in nearbyObjects)
  313. {
  314. if (obj == null) continue;
  315. if (!obj.activeInHierarchy) continue;
  316. var objCenter = BoundsUtils.GetBoundsRecursive(obj.transform).center;
  317. var centerDistance = (objCenter - cellCenter).magnitude;
  318. if (centerDistance > halfStep) continue;
  319. if (PaletteManager.selectedPalette.ContainsSceneObject(obj))
  320. {
  321. checkNextItem = true;
  322. break;
  323. }
  324. }
  325. if (checkNextItem) return;
  326. }
  327. var scaleMult = strokeItem.scaleMultiplier;
  328. var centerToPivot = GetCenterToPivot(prefab, scaleMult, itemRotation);
  329. var itemPosition = cellCenter + centerToPivot;
  330. var translateMatrix = Matrix4x4.Translate(Quaternion.Inverse(itemRotation) * -prefab.transform.position);
  331. var rootToWorld = Matrix4x4.TRS(itemPosition, itemRotation, scaleMult) * translateMatrix;
  332. var layer = toolSettings.overwritePrefabLayer ? toolSettings.layer : prefab.layer;
  333. PreviewBrushItem(prefab, rootToWorld, layer, camera,
  334. redMaterial: false, reverseTriangles: false, flipX: false, flipY: false);
  335. var itemScale = Vector3.Scale(prefab.transform.localScale, scaleMult);
  336. Transform parentTransform = toolSettings.parent;
  337. var paintItem = new PaintStrokeItem(prefab, itemPosition, itemRotation,
  338. itemScale, layer, parentTransform, surface: null, flipX: false, flipY: false);
  339. _paintStroke.Clear();
  340. _paintStroke.Add(paintItem);
  341. }
  342. private static void PreviewFloorRectangle(Camera camera)
  343. {
  344. BrushstrokeItem[] brushstroke = null;
  345. if (PreviewIfBrushtrokestaysTheSame(out brushstroke, camera, forceUpdate: _paintStroke.Count == 0)) return;
  346. if (brushstroke.Length == 0) return;
  347. _paintStroke.Clear();
  348. var toolSettings = FloorManager.settings;
  349. var halfCellSize = toolSettings.moduleSize / 2;
  350. var halfStep = Mathf.Min(SnapManager.settings.step.x, SnapManager.settings.step.z) * 0.4999;
  351. for (int i = 0; i < brushstroke.Length; ++i)
  352. {
  353. var strokeItem = brushstroke[i];
  354. if (strokeItem.settings == null) return;
  355. var prefab = strokeItem.settings.prefab;
  356. if (prefab == null) return;
  357. var scaleMult = strokeItem.scaleMultiplier;
  358. var itemRotation = Quaternion.Euler(strokeItem.additionalAngle);
  359. var quarterTurns = FloorManager.quarterTurns;
  360. if (FloorManager.settings.swapXZ) ++quarterTurns;
  361. if (quarterTurns > 0)
  362. itemRotation = itemRotation
  363. * Quaternion.AngleAxis(90 * quarterTurns, toolSettings.upwardAxis);
  364. var cellCenter = strokeItem.tangentPosition;
  365. var centerToPivot = GetCenterToPivot(prefab, scaleMult, itemRotation);
  366. var itemPosition = cellCenter + centerToPivot;
  367. var nearbyObjects = new System.Collections.Generic.List<GameObject>();
  368. boundsOctree.GetColliding(cellCenter, halfCellSize, SnapManager.settings.rotation,
  369. itemRotation, nearbyObjects);
  370. if (nearbyObjects.Count > 0)
  371. {
  372. bool checkNextItem = false;
  373. foreach (var obj in nearbyObjects)
  374. {
  375. if (obj == null) continue;
  376. if (!obj.activeInHierarchy) continue;
  377. var objCenter = BoundsUtils.GetBoundsRecursive(obj.transform).center;
  378. var centerDistance = (objCenter - cellCenter).magnitude;
  379. if (centerDistance > halfStep) continue;
  380. if (PaletteManager.selectedPalette.ContainsSceneObject(obj))
  381. {
  382. checkNextItem = true;
  383. break;
  384. }
  385. }
  386. if (checkNextItem) continue;
  387. }
  388. var translateMatrix = Matrix4x4.Translate(Quaternion.Inverse(itemRotation) * -prefab.transform.position);
  389. var rootToWorld = Matrix4x4.TRS(itemPosition, itemRotation, scaleMult) * translateMatrix;
  390. var layer = toolSettings.overwritePrefabLayer ? toolSettings.layer : prefab.layer;
  391. PreviewBrushItem(prefab, rootToWorld, layer, camera,
  392. redMaterial: false, reverseTriangles: false, flipX: false, flipY: false);
  393. _previewData.Add(new PreviewData(prefab, rootToWorld, layer, flipX: false, flipY: false));
  394. var itemScale = Vector3.Scale(prefab.transform.localScale, scaleMult);
  395. Transform parentTransform = toolSettings.parent;
  396. var paintItem = new PaintStrokeItem(prefab, itemPosition, itemRotation,
  397. itemScale, layer, parentTransform, surface: null, flipX: false, flipY: false);
  398. _paintStroke.Add(paintItem);
  399. }
  400. }
  401. private static void PreviewFloorDeleteSingleTile(Camera camera, Vector3 mousePos3D)
  402. {
  403. if (BrushstrokeManager.brushstroke.Length == 0) return;
  404. var TRS = Matrix4x4.TRS(mousePos3D, SnapManager.settings.rotation, FloorManager.settings.moduleSize);
  405. Graphics.DrawMesh(cubeMesh, TRS, transparentRedMaterial2, 0, camera);
  406. }
  407. private static System.Collections.Generic.HashSet<Pose> _floorDeleteStroke
  408. = new System.Collections.Generic.HashSet<Pose>();
  409. private static void PreviewFloorDeleteRectangle(Camera camera, Vector3 mousePos3D)
  410. {
  411. if (BrushstrokeManager.brushstroke.Length == 0) return;
  412. var brushstroke = BrushstrokeManager.brushstroke;
  413. var toolSettings = FloorManager.settings;
  414. _floorDeleteStroke.Clear();
  415. for (int i = 0; i < brushstroke.Length; ++i)
  416. {
  417. var strokeItem = brushstroke[i];
  418. var itemPosition = strokeItem.tangentPosition;
  419. var rootToWorld = Matrix4x4.TRS(itemPosition, SnapManager.settings.rotation, FloorManager.settings.moduleSize);
  420. Graphics.DrawMesh(cubeMesh, rootToWorld, transparentRedMaterial2, layer: 0, camera);
  421. _floorDeleteStroke.Add(new Pose(itemPosition, Quaternion.Euler(strokeItem.additionalAngle)));
  422. }
  423. }
  424. private static void DeleteFloor()
  425. {
  426. if (_floorDeleteStroke.Count == 0) return;
  427. var toolSettings = FloorManager.settings;
  428. var toBeDeleted = new System.Collections.Generic.HashSet<GameObject>();
  429. var halfCellSize = toolSettings.moduleSize / 2;
  430. foreach (var cellPose in _floorDeleteStroke)
  431. {
  432. var nearbyObjects = new System.Collections.Generic.List<GameObject>();
  433. boundsOctree.GetColliding(cellPose.position, halfCellSize,
  434. SnapManager.settings.rotation, cellPose.rotation, nearbyObjects);
  435. if (nearbyObjects.Count == 0) continue;
  436. foreach (var obj in nearbyObjects)
  437. {
  438. if (obj == null) continue;
  439. if (!obj.activeInHierarchy) continue;
  440. var objCenter = BoundsUtils.GetBoundsRecursive(obj.transform).center;
  441. var centerDistance = (objCenter - cellPose.position).magnitude;
  442. var halfStep = Mathf.Min(SnapManager.settings.step.x, SnapManager.settings.step.z) * 0.4999;
  443. if (centerDistance > halfStep) continue;
  444. if (PaletteManager.selectedPalette.ContainsSceneObject(obj)) toBeDeleted.Add(obj);
  445. }
  446. }
  447. void EraseObject(GameObject obj)
  448. {
  449. if (obj == null) return;
  450. var root = UnityEditor.PrefabUtility.GetNearestPrefabInstanceRoot(obj);
  451. if (root != null) obj = root;
  452. PWBCore.DestroyTempCollider(obj.GetInstanceID());
  453. UnityEditor.Undo.DestroyObjectImmediate(obj);
  454. }
  455. foreach (var obj in toBeDeleted) EraseObject(obj);
  456. }
  457. }
  458. #endregion
  459. }