ObjectPlacementGuide.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. #if UNITY_EDITOR
  2. using UnityEngine;
  3. using UnityEditor;
  4. namespace O3DWB
  5. {
  6. [ExecuteInEditMode]
  7. public class ObjectPlacementGuide : MonoBehaviour, IMessageListener
  8. {
  9. #region Private Static Variables
  10. private static string _nameSuffix = "Active Prefab";
  11. private static ObjectPlacementGuide _instance;
  12. #endregion
  13. #region Private Variables
  14. [SerializeField]
  15. private Prefab _sourcePrefab;
  16. [SerializeField]
  17. private Transform _transform;
  18. private ObjectMouseUniformScaleSession _mouseUniformScaleSession = new ObjectMouseUniformScaleSession();
  19. private ObjectMouseRotationSession _mouseRotationSession = new ObjectMouseRotationSession();
  20. private ObjectMouseMoveAlongDirectionSession _mouseMoveAlongDirectionSession = new ObjectMouseMoveAlongDirectionSession();
  21. #endregion
  22. #region Public Static Properties
  23. public static ObjectPlacementGuide Instance
  24. {
  25. get
  26. {
  27. if (_instance == null) InstantiateFromActivePrefabIfPossible();
  28. return _instance;
  29. }
  30. }
  31. public static GameObject SceneObject { get { return ExistsInScene ? Instance.gameObject : null; } }
  32. public static bool ExistsInScene { get { return _instance != null; } }
  33. public static bool ExistsInSceneAndIsActive { get { return ExistsInScene && Instance.gameObject.activeSelf; } }
  34. public static bool Active { set { if (ExistsInScene) Instance.gameObject.SetActive(value); } }
  35. #endregion
  36. #region Public Properties
  37. public Prefab SourcePrefab { get { return _sourcePrefab; } }
  38. public bool IsSourcePrefabAvailable { get { return SourcePrefab != null && SourcePrefab.UnityPrefab != null; } }
  39. public Vector3 WorldPosition { get { return _transform.position; } }
  40. public Quaternion WorldRotation { get { return _transform.rotation; } }
  41. public Vector3 WorldScale { get { return _transform.lossyScale; } set { gameObject.SetWorldScale(value); } }
  42. public bool IsMouseMoveAlongDirectionSessionActive { get { return _mouseMoveAlongDirectionSession.IsActive; } }
  43. public bool IsMouseUniformScaleSessionActive { get { return _mouseUniformScaleSession.IsActive; } }
  44. public bool IsMouseRotationSessionActive { get { return _mouseRotationSession.IsActive; } }
  45. public bool IsMouseRotationSessionForCustomAxis { get { return _mouseRotationSession.RotatingAroundCustomAxis; } }
  46. public TransformAxis MouseRotationSessionAxis { get { return _mouseRotationSession.RotationAxis; } }
  47. public bool IsAnyMouseSessionActive { get { return IsMouseRotationSessionActive || IsMouseUniformScaleSessionActive || IsMouseMoveAlongDirectionSessionActive; } }
  48. #endregion
  49. #region Public Static Functions
  50. public static bool ContainsChild(Transform potentialChild)
  51. {
  52. if (ExistsInScene) return potentialChild != null && potentialChild.IsChildOf(Instance._transform);
  53. return false;
  54. }
  55. public static bool Equals(GameObject gameObject)
  56. {
  57. if (ExistsInScene) return gameObject == Instance.gameObject;
  58. return false;
  59. }
  60. public static void CreateFromActivePrefabIfNotExists()
  61. {
  62. if (!ExistsInScene) InstantiateFromActivePrefabIfPossible();
  63. }
  64. public static void DestroyIfExists()
  65. {
  66. if (ExistsInScene) Octave3DWorldBuilder.DestroyImmediate(ObjectPlacementGuide.Instance.gameObject);
  67. }
  68. #endregion
  69. #region Public Methods
  70. public void BeginMouseMoveAlongDirectionSession(Vector3 moveDirection)
  71. {
  72. if (!IsAnyMouseSessionActive) _mouseMoveAlongDirectionSession.Begin(gameObject, moveDirection, ObjectPlacementGuideSettings.Get().MouseOffsetFromPlacementSurfaceSettings);
  73. }
  74. public void EndMouseMoveAlongDirectionSession()
  75. {
  76. _mouseMoveAlongDirectionSession.End();
  77. }
  78. public void BeginMouseScaleSession(Vector3 scalePivotPoint)
  79. {
  80. if (!IsAnyMouseSessionActive)
  81. {
  82. _mouseUniformScaleSession.PivotPoint = scalePivotPoint;
  83. _mouseUniformScaleSession.Begin(gameObject, ObjectPlacementSettings.Get().ObjectPlacementGuideSettings.MouseUniformScaleSettings);
  84. }
  85. }
  86. public void EndMouseScaleSession()
  87. {
  88. _mouseUniformScaleSession.End();
  89. }
  90. public void BeginMouseRotationSession(TransformAxis rotationAxis)
  91. {
  92. if (!IsAnyMouseSessionActive)
  93. _mouseRotationSession.BeginRotationAroundAxis(gameObject, ObjectPlacementSettings.Get().ObjectPlacementGuideSettings.MouseRotationSettings, rotationAxis);
  94. }
  95. public void BeginMouseRotationSession(Vector3 customRotationAxis)
  96. {
  97. if (!IsAnyMouseSessionActive)
  98. _mouseRotationSession.BeginRotationAroundCustomAxis(gameObject, ObjectPlacementSettings.Get().ObjectPlacementGuideSettings.MouseRotationSettings, customRotationAxis);
  99. }
  100. public void EndMouseRotationSession()
  101. {
  102. _mouseRotationSession.End();
  103. }
  104. public void UpdateActiveMouseSessionsForMouseMovement(Event e)
  105. {
  106. if(IsMouseMoveAlongDirectionSessionActive) _mouseMoveAlongDirectionSession.UpdateForMouseMovement(e);
  107. else
  108. if(IsMouseUniformScaleSessionActive)
  109. {
  110. _mouseUniformScaleSession.UpdateForMouseMovement(e);
  111. UndoEx.RecordForToolAction(_sourcePrefab.ActivationSettings);
  112. _sourcePrefab.ActivationSettings.WorldScale = _transform.lossyScale;
  113. }
  114. else
  115. if(IsMouseRotationSessionActive)
  116. {
  117. _mouseRotationSession.UpdateForMouseMovement(e);
  118. UndoEx.RecordForToolAction(_sourcePrefab.ActivationSettings);
  119. _sourcePrefab.ActivationSettings.WorldRotation = _transform.rotation;
  120. }
  121. }
  122. public void SetHierarchyWorldRotationAndPreserveHierarchyCenter(Quaternion rotation)
  123. {
  124. if (IsAnyMouseSessionActive) return;
  125. UndoEx.RecordForToolAction(_transform);
  126. gameObject.SetHierarchyWorldRotationAndPreserveHierarchyCenter(rotation);
  127. UndoEx.RecordForToolAction(_sourcePrefab.ActivationSettings);
  128. _sourcePrefab.ActivationSettings.WorldRotation = _transform.rotation;
  129. }
  130. public void SetHierarchyWorldScaleByPivotPoint(float scale, Vector3 pivotPoint)
  131. {
  132. if (IsAnyMouseSessionActive) return;
  133. UndoEx.RecordForToolAction(_transform);
  134. gameObject.SetHierarchyWorldScaleByPivotPoint(scale, pivotPoint);
  135. UndoEx.RecordForToolAction(_sourcePrefab.ActivationSettings);
  136. _sourcePrefab.ActivationSettings.WorldScale = _transform.lossyScale;
  137. }
  138. public void SetHierarchyWorldScaleByPivotPoint(Vector3 scale, Vector3 pivotPoint)
  139. {
  140. if (IsAnyMouseSessionActive) return;
  141. UndoEx.RecordForToolAction(_transform);
  142. gameObject.SetHierarchyWorldScaleByPivotPoint(scale, pivotPoint);
  143. UndoEx.RecordForToolAction(_sourcePrefab.ActivationSettings);
  144. _sourcePrefab.ActivationSettings.WorldScale = _transform.lossyScale;
  145. }
  146. public void Snap()
  147. {
  148. float offsetFromSurface = SourcePrefab.OffsetFromGridSurface;
  149. if (ObjectSnapping.Get().SnapSurfaceType != SnapSurfaceType.GridCell) offsetFromSurface = SourcePrefab.OffsetFromObjectSurface;
  150. if (AllShortcutCombos.Instance.PlaceGuideBehindSurfacePlane.IsActive()) offsetFromSurface *= -1.0f;
  151. if (AllShortcutCombos.Instance.SnapCenterToCenter.IsActive()) ObjectSnapping.Get().SnapObjectHierarchyToCenterOfSnapSurface(gameObject, ObjectPlacement.Get().CenterProjectedGuidePivotPoint, ObjectPlacement.Get().ProjectedGuidePivotPoints, offsetFromSurface);
  152. else ObjectSnapping.Get().SnapObjectHierarchy(gameObject, ObjectPlacement.Get().ProjectedGuidePivotPoints, offsetFromSurface);
  153. PersistentObjectPlacementGuideData.Get().LastUsedWorldPosition = _transform.position;
  154. }
  155. public void SetWorldPosition(Vector3 position)
  156. {
  157. _transform.position = position;
  158. PersistentObjectPlacementGuideData.Get().LastUsedWorldPosition = _transform.position;
  159. }
  160. public void SetWorldScale(Vector3 worldScale)
  161. {
  162. UndoEx.RecordForToolAction(_transform);
  163. _transform.SetWorldScale(worldScale);
  164. UndoEx.RecordForToolAction(_sourcePrefab.ActivationSettings);
  165. _sourcePrefab.ActivationSettings.WorldScale = worldScale;
  166. }
  167. public void RegisterCurrentPosition()
  168. {
  169. PersistentObjectPlacementGuideData.Get().LastUsedWorldPosition = _transform.position;
  170. }
  171. public void SetWorldRotation(Quaternion rotation)
  172. {
  173. _transform.rotation = rotation;
  174. //_sourcePrefab.ActivationSettings.WorldRotation = _transform.rotation;
  175. }
  176. public void RotateUsingKeyboardSettings(Vector3 customRotationAxis)
  177. {
  178. if (IsAnyMouseSessionActive) return;
  179. UndoEx.RecordForToolAction(_transform);
  180. ObjectKeyboardRotationSettings keyboardRotationSettings = ObjectPlacementGuideSettings.Get().KeyboardRotationSettings;
  181. gameObject.RotateHierarchyBoxAroundPoint(keyboardRotationSettings.CustomAxisRotationSettings.RotationAmountInDegrees,
  182. customRotationAxis, gameObject.GetHierarchyWorldOrientedBox().Center);
  183. UndoEx.RecordForToolAction(_sourcePrefab.ActivationSettings);
  184. _sourcePrefab.ActivationSettings.WorldRotation = _transform.rotation;
  185. }
  186. public void RotateUsingKeyboardSettings(TransformAxis rotationAxis)
  187. {
  188. if (IsAnyMouseSessionActive) return;
  189. ObjectKeyboardRotationSettings keyboardRotationSettings = ObjectPlacementGuideSettings.Get().KeyboardRotationSettings;
  190. UndoEx.RecordForToolAction(_transform);
  191. AxisKeyboardRotationSettings axisKeyboardRotationSettings = keyboardRotationSettings.XAxisRotationSettings;
  192. if (rotationAxis == TransformAxis.Y) axisKeyboardRotationSettings = keyboardRotationSettings.YAxisRotationSettings;
  193. else if (rotationAxis == TransformAxis.Z) axisKeyboardRotationSettings = keyboardRotationSettings.XAxisRotationSettings;
  194. gameObject.RotateHierarchyBoxAroundPoint(axisKeyboardRotationSettings.RotationAmountInDegrees,
  195. TransformAxes.GetVector(rotationAxis, TransformSpace.Global, _transform),
  196. gameObject.GetHierarchyWorldOrientedBox().Center);
  197. UndoEx.RecordForToolAction(_sourcePrefab.ActivationSettings);
  198. _sourcePrefab.ActivationSettings.WorldRotation = _transform.rotation;
  199. }
  200. #endregion
  201. #region Private Methods
  202. private void Awake()
  203. {
  204. if (!IsGuideInstanceAllowedToExist()) { DestroyImmediate(this); return; }
  205. Initialize();
  206. }
  207. private void OnEnable()
  208. {
  209. if (!IsGuideInstanceAllowedToExist()) { DestroyImmediate(this); return; }
  210. Initialize();
  211. }
  212. private void Initialize()
  213. {
  214. MessageListenerRegistration.PerformRegistrationForObjectPlacementGuide(this);
  215. _instance = this;
  216. _transform = transform;
  217. EditorApplication.update -= OnEditorUpdate;
  218. EditorApplication.update += OnEditorUpdate;
  219. }
  220. private bool IsGuideInstanceAllowedToExist()
  221. {
  222. if (_instance != null && _instance != this) return false;
  223. return true;
  224. }
  225. private void Start()
  226. {
  227. // Note: We will always ensure that the guide is placed in the last position which it had before
  228. // it was destroyed. Although not strictly necessary, it is more pleasent than to see it
  229. // suddenly snap to position <0, 0, 0> every time an Undo/Redo operation is performed or
  230. // when the scripts are recompiled.
  231. // Note: This has to be done from the 'Start' function because here we can be sure that the object
  232. // which holds the persistent guide data has been created.
  233. gameObject.transform.position = PersistentObjectPlacementGuideData.Get().LastUsedWorldPosition;
  234. }
  235. private void OnDestroy()
  236. {
  237. if (_instance == this)
  238. {
  239. MessageListenerDatabase.Instance.UnregisterListener(this);
  240. _instance = null;
  241. EditorApplication.update -= OnEditorUpdate;
  242. }
  243. }
  244. private void OnEditorUpdate()
  245. {
  246. if (!EditorApplication.isPlaying)
  247. {
  248. if (Octave3DWorldBuilder.ActiveInstance == null) UndoEx.DestroyObjectImmediate(gameObject);
  249. else
  250. if (!Selection.Contains(Octave3DWorldBuilder.ActiveInstance.gameObject)) ObjectPlacement.Get().DestroyPlacementGuide();
  251. }
  252. }
  253. #endregion
  254. #region Message Handlers
  255. public void RespondToMessage(Message message)
  256. {
  257. switch(message.Type)
  258. {
  259. case MessageType.NewPrefabWasActivated:
  260. RespondToMessage(message as NewPrefabWasActivatedMessage);
  261. break;
  262. case MessageType.NewPrefabCategoryWasActivated:
  263. RespondToMessage(message as NewPrefabCategoryWasActivatedMessage);
  264. break;
  265. case MessageType.PrefabWasRemovedFromCategory:
  266. RespondToMessage(message as PrefabWasRemovedFromCategoryMessage);
  267. break;
  268. case MessageType.PrefabCategoryWasRemovedFromDatabase:
  269. RespondToMessage(message as PrefabCategoryWasRemovedFromDatabaseMessage);
  270. break;
  271. case MessageType.UndoRedoWasPerformed:
  272. RespondToMessage(message as UndoRedoWasPerformedMessage);
  273. break;
  274. }
  275. }
  276. private void RespondToMessage(NewPrefabWasActivatedMessage message)
  277. {
  278. if (Inspector.Get().ActiveInspectorGUIIdentifier == InspectorGUIIdentifier.ObjectSnapping ||
  279. Inspector.Get().ActiveInspectorGUIIdentifier == InspectorGUIIdentifier.ObjectPlacement)
  280. {
  281. DestroyIfExists();
  282. CreateFromActivePrefabIfNotExists();
  283. }
  284. }
  285. private void RespondToMessage(NewPrefabCategoryWasActivatedMessage message)
  286. {
  287. if(Inspector.Get().ActiveInspectorGUIIdentifier == InspectorGUIIdentifier.ObjectSnapping ||
  288. Inspector.Get().ActiveInspectorGUIIdentifier == InspectorGUIIdentifier.ObjectPlacement)
  289. {
  290. DestroyIfExists();
  291. CreateFromActivePrefabIfNotExists();
  292. }
  293. }
  294. private void RespondToMessage(PrefabWasRemovedFromCategoryMessage message)
  295. {
  296. DestroyIfExists();
  297. CreateFromActivePrefabIfNotExists();
  298. }
  299. private void RespondToMessage(PrefabCategoryWasRemovedFromDatabaseMessage message)
  300. {
  301. DestroyIfExists();
  302. CreateFromActivePrefabIfNotExists();
  303. }
  304. private void RespondToMessage(UndoRedoWasPerformedMessage message)
  305. {
  306. if(_sourcePrefab != PrefabCategoryDatabase.Get().ActivePrefabCategory.ActivePrefab)
  307. {
  308. DestroyIfExists();
  309. CreateFromActivePrefabIfNotExists();
  310. }
  311. }
  312. #endregion
  313. #region Private Static Functions
  314. private static void InstantiateFromActivePrefabIfPossible()
  315. {
  316. Prefab activePrefab = PrefabQueries.GetActivePrefab();
  317. if (activePrefab != null) InstantiateFromPrefab(activePrefab);
  318. }
  319. private static void InstantiateFromPrefab(Prefab prefab)
  320. {
  321. _instance = ObjectInstantiation.InstantiateObjectPlacementGuide(prefab, prefab.Name + "(" + _nameSuffix + ")");
  322. _instance._sourcePrefab = prefab;
  323. _instance.transform.rotation = prefab.ActivationSettings.WorldRotation;
  324. _instance.gameObject.SetWorldScale(prefab.ActivationSettings.WorldScale);
  325. ObjectPlacementGuideWasInstantiatedMessage.SendToInterestedListeners();
  326. }
  327. #endregion
  328. }
  329. }
  330. #endif