#if UNITY_EDITOR using UnityEngine; using UnityEditor; namespace O3DWB { [ExecuteInEditMode] public class ObjectPlacementGuide : MonoBehaviour, IMessageListener { #region Private Static Variables private static string _nameSuffix = "Active Prefab"; private static ObjectPlacementGuide _instance; #endregion #region Private Variables [SerializeField] private Prefab _sourcePrefab; [SerializeField] private Transform _transform; private ObjectMouseUniformScaleSession _mouseUniformScaleSession = new ObjectMouseUniformScaleSession(); private ObjectMouseRotationSession _mouseRotationSession = new ObjectMouseRotationSession(); private ObjectMouseMoveAlongDirectionSession _mouseMoveAlongDirectionSession = new ObjectMouseMoveAlongDirectionSession(); #endregion #region Public Static Properties public static ObjectPlacementGuide Instance { get { if (_instance == null) InstantiateFromActivePrefabIfPossible(); return _instance; } } public static GameObject SceneObject { get { return ExistsInScene ? Instance.gameObject : null; } } public static bool ExistsInScene { get { return _instance != null; } } public static bool ExistsInSceneAndIsActive { get { return ExistsInScene && Instance.gameObject.activeSelf; } } public static bool Active { set { if (ExistsInScene) Instance.gameObject.SetActive(value); } } #endregion #region Public Properties public Prefab SourcePrefab { get { return _sourcePrefab; } } public bool IsSourcePrefabAvailable { get { return SourcePrefab != null && SourcePrefab.UnityPrefab != null; } } public Vector3 WorldPosition { get { return _transform.position; } } public Quaternion WorldRotation { get { return _transform.rotation; } } public Vector3 WorldScale { get { return _transform.lossyScale; } set { gameObject.SetWorldScale(value); } } public bool IsMouseMoveAlongDirectionSessionActive { get { return _mouseMoveAlongDirectionSession.IsActive; } } public bool IsMouseUniformScaleSessionActive { get { return _mouseUniformScaleSession.IsActive; } } public bool IsMouseRotationSessionActive { get { return _mouseRotationSession.IsActive; } } public bool IsMouseRotationSessionForCustomAxis { get { return _mouseRotationSession.RotatingAroundCustomAxis; } } public TransformAxis MouseRotationSessionAxis { get { return _mouseRotationSession.RotationAxis; } } public bool IsAnyMouseSessionActive { get { return IsMouseRotationSessionActive || IsMouseUniformScaleSessionActive || IsMouseMoveAlongDirectionSessionActive; } } #endregion #region Public Static Functions public static bool ContainsChild(Transform potentialChild) { if (ExistsInScene) return potentialChild != null && potentialChild.IsChildOf(Instance._transform); return false; } public static bool Equals(GameObject gameObject) { if (ExistsInScene) return gameObject == Instance.gameObject; return false; } public static void CreateFromActivePrefabIfNotExists() { if (!ExistsInScene) InstantiateFromActivePrefabIfPossible(); } public static void DestroyIfExists() { if (ExistsInScene) Octave3DWorldBuilder.DestroyImmediate(ObjectPlacementGuide.Instance.gameObject); } #endregion #region Public Methods public void BeginMouseMoveAlongDirectionSession(Vector3 moveDirection) { if (!IsAnyMouseSessionActive) _mouseMoveAlongDirectionSession.Begin(gameObject, moveDirection, ObjectPlacementGuideSettings.Get().MouseOffsetFromPlacementSurfaceSettings); } public void EndMouseMoveAlongDirectionSession() { _mouseMoveAlongDirectionSession.End(); } public void BeginMouseScaleSession(Vector3 scalePivotPoint) { if (!IsAnyMouseSessionActive) { _mouseUniformScaleSession.PivotPoint = scalePivotPoint; _mouseUniformScaleSession.Begin(gameObject, ObjectPlacementSettings.Get().ObjectPlacementGuideSettings.MouseUniformScaleSettings); } } public void EndMouseScaleSession() { _mouseUniformScaleSession.End(); } public void BeginMouseRotationSession(TransformAxis rotationAxis) { if (!IsAnyMouseSessionActive) _mouseRotationSession.BeginRotationAroundAxis(gameObject, ObjectPlacementSettings.Get().ObjectPlacementGuideSettings.MouseRotationSettings, rotationAxis); } public void BeginMouseRotationSession(Vector3 customRotationAxis) { if (!IsAnyMouseSessionActive) _mouseRotationSession.BeginRotationAroundCustomAxis(gameObject, ObjectPlacementSettings.Get().ObjectPlacementGuideSettings.MouseRotationSettings, customRotationAxis); } public void EndMouseRotationSession() { _mouseRotationSession.End(); } public void UpdateActiveMouseSessionsForMouseMovement(Event e) { if(IsMouseMoveAlongDirectionSessionActive) _mouseMoveAlongDirectionSession.UpdateForMouseMovement(e); else if(IsMouseUniformScaleSessionActive) { _mouseUniformScaleSession.UpdateForMouseMovement(e); UndoEx.RecordForToolAction(_sourcePrefab.ActivationSettings); _sourcePrefab.ActivationSettings.WorldScale = _transform.lossyScale; } else if(IsMouseRotationSessionActive) { _mouseRotationSession.UpdateForMouseMovement(e); UndoEx.RecordForToolAction(_sourcePrefab.ActivationSettings); _sourcePrefab.ActivationSettings.WorldRotation = _transform.rotation; } } public void SetHierarchyWorldRotationAndPreserveHierarchyCenter(Quaternion rotation) { if (IsAnyMouseSessionActive) return; UndoEx.RecordForToolAction(_transform); gameObject.SetHierarchyWorldRotationAndPreserveHierarchyCenter(rotation); UndoEx.RecordForToolAction(_sourcePrefab.ActivationSettings); _sourcePrefab.ActivationSettings.WorldRotation = _transform.rotation; } public void SetHierarchyWorldScaleByPivotPoint(float scale, Vector3 pivotPoint) { if (IsAnyMouseSessionActive) return; UndoEx.RecordForToolAction(_transform); gameObject.SetHierarchyWorldScaleByPivotPoint(scale, pivotPoint); UndoEx.RecordForToolAction(_sourcePrefab.ActivationSettings); _sourcePrefab.ActivationSettings.WorldScale = _transform.lossyScale; } public void SetHierarchyWorldScaleByPivotPoint(Vector3 scale, Vector3 pivotPoint) { if (IsAnyMouseSessionActive) return; UndoEx.RecordForToolAction(_transform); gameObject.SetHierarchyWorldScaleByPivotPoint(scale, pivotPoint); UndoEx.RecordForToolAction(_sourcePrefab.ActivationSettings); _sourcePrefab.ActivationSettings.WorldScale = _transform.lossyScale; } public void Snap() { float offsetFromSurface = SourcePrefab.OffsetFromGridSurface; if (ObjectSnapping.Get().SnapSurfaceType != SnapSurfaceType.GridCell) offsetFromSurface = SourcePrefab.OffsetFromObjectSurface; if (AllShortcutCombos.Instance.PlaceGuideBehindSurfacePlane.IsActive()) offsetFromSurface *= -1.0f; if (AllShortcutCombos.Instance.SnapCenterToCenter.IsActive()) ObjectSnapping.Get().SnapObjectHierarchyToCenterOfSnapSurface(gameObject, ObjectPlacement.Get().CenterProjectedGuidePivotPoint, ObjectPlacement.Get().ProjectedGuidePivotPoints, offsetFromSurface); else ObjectSnapping.Get().SnapObjectHierarchy(gameObject, ObjectPlacement.Get().ProjectedGuidePivotPoints, offsetFromSurface); PersistentObjectPlacementGuideData.Get().LastUsedWorldPosition = _transform.position; } public void SetWorldPosition(Vector3 position) { _transform.position = position; PersistentObjectPlacementGuideData.Get().LastUsedWorldPosition = _transform.position; } public void SetWorldScale(Vector3 worldScale) { UndoEx.RecordForToolAction(_transform); _transform.SetWorldScale(worldScale); UndoEx.RecordForToolAction(_sourcePrefab.ActivationSettings); _sourcePrefab.ActivationSettings.WorldScale = worldScale; } public void RegisterCurrentPosition() { PersistentObjectPlacementGuideData.Get().LastUsedWorldPosition = _transform.position; } public void SetWorldRotation(Quaternion rotation) { _transform.rotation = rotation; //_sourcePrefab.ActivationSettings.WorldRotation = _transform.rotation; } public void RotateUsingKeyboardSettings(Vector3 customRotationAxis) { if (IsAnyMouseSessionActive) return; UndoEx.RecordForToolAction(_transform); ObjectKeyboardRotationSettings keyboardRotationSettings = ObjectPlacementGuideSettings.Get().KeyboardRotationSettings; gameObject.RotateHierarchyBoxAroundPoint(keyboardRotationSettings.CustomAxisRotationSettings.RotationAmountInDegrees, customRotationAxis, gameObject.GetHierarchyWorldOrientedBox().Center); UndoEx.RecordForToolAction(_sourcePrefab.ActivationSettings); _sourcePrefab.ActivationSettings.WorldRotation = _transform.rotation; } public void RotateUsingKeyboardSettings(TransformAxis rotationAxis) { if (IsAnyMouseSessionActive) return; ObjectKeyboardRotationSettings keyboardRotationSettings = ObjectPlacementGuideSettings.Get().KeyboardRotationSettings; UndoEx.RecordForToolAction(_transform); AxisKeyboardRotationSettings axisKeyboardRotationSettings = keyboardRotationSettings.XAxisRotationSettings; if (rotationAxis == TransformAxis.Y) axisKeyboardRotationSettings = keyboardRotationSettings.YAxisRotationSettings; else if (rotationAxis == TransformAxis.Z) axisKeyboardRotationSettings = keyboardRotationSettings.XAxisRotationSettings; gameObject.RotateHierarchyBoxAroundPoint(axisKeyboardRotationSettings.RotationAmountInDegrees, TransformAxes.GetVector(rotationAxis, TransformSpace.Global, _transform), gameObject.GetHierarchyWorldOrientedBox().Center); UndoEx.RecordForToolAction(_sourcePrefab.ActivationSettings); _sourcePrefab.ActivationSettings.WorldRotation = _transform.rotation; } #endregion #region Private Methods private void Awake() { if (!IsGuideInstanceAllowedToExist()) { DestroyImmediate(this); return; } Initialize(); } private void OnEnable() { if (!IsGuideInstanceAllowedToExist()) { DestroyImmediate(this); return; } Initialize(); } private void Initialize() { MessageListenerRegistration.PerformRegistrationForObjectPlacementGuide(this); _instance = this; _transform = transform; EditorApplication.update -= OnEditorUpdate; EditorApplication.update += OnEditorUpdate; } private bool IsGuideInstanceAllowedToExist() { if (_instance != null && _instance != this) return false; return true; } private void Start() { // Note: We will always ensure that the guide is placed in the last position which it had before // it was destroyed. Although not strictly necessary, it is more pleasent than to see it // suddenly snap to position <0, 0, 0> every time an Undo/Redo operation is performed or // when the scripts are recompiled. // Note: This has to be done from the 'Start' function because here we can be sure that the object // which holds the persistent guide data has been created. gameObject.transform.position = PersistentObjectPlacementGuideData.Get().LastUsedWorldPosition; } private void OnDestroy() { if (_instance == this) { MessageListenerDatabase.Instance.UnregisterListener(this); _instance = null; EditorApplication.update -= OnEditorUpdate; } } private void OnEditorUpdate() { if (!EditorApplication.isPlaying) { if (Octave3DWorldBuilder.ActiveInstance == null) UndoEx.DestroyObjectImmediate(gameObject); else if (!Selection.Contains(Octave3DWorldBuilder.ActiveInstance.gameObject)) ObjectPlacement.Get().DestroyPlacementGuide(); } } #endregion #region Message Handlers public void RespondToMessage(Message message) { switch(message.Type) { case MessageType.NewPrefabWasActivated: RespondToMessage(message as NewPrefabWasActivatedMessage); break; case MessageType.NewPrefabCategoryWasActivated: RespondToMessage(message as NewPrefabCategoryWasActivatedMessage); break; case MessageType.PrefabWasRemovedFromCategory: RespondToMessage(message as PrefabWasRemovedFromCategoryMessage); break; case MessageType.PrefabCategoryWasRemovedFromDatabase: RespondToMessage(message as PrefabCategoryWasRemovedFromDatabaseMessage); break; case MessageType.UndoRedoWasPerformed: RespondToMessage(message as UndoRedoWasPerformedMessage); break; } } private void RespondToMessage(NewPrefabWasActivatedMessage message) { if (Inspector.Get().ActiveInspectorGUIIdentifier == InspectorGUIIdentifier.ObjectSnapping || Inspector.Get().ActiveInspectorGUIIdentifier == InspectorGUIIdentifier.ObjectPlacement) { DestroyIfExists(); CreateFromActivePrefabIfNotExists(); } } private void RespondToMessage(NewPrefabCategoryWasActivatedMessage message) { if(Inspector.Get().ActiveInspectorGUIIdentifier == InspectorGUIIdentifier.ObjectSnapping || Inspector.Get().ActiveInspectorGUIIdentifier == InspectorGUIIdentifier.ObjectPlacement) { DestroyIfExists(); CreateFromActivePrefabIfNotExists(); } } private void RespondToMessage(PrefabWasRemovedFromCategoryMessage message) { DestroyIfExists(); CreateFromActivePrefabIfNotExists(); } private void RespondToMessage(PrefabCategoryWasRemovedFromDatabaseMessage message) { DestroyIfExists(); CreateFromActivePrefabIfNotExists(); } private void RespondToMessage(UndoRedoWasPerformedMessage message) { if(_sourcePrefab != PrefabCategoryDatabase.Get().ActivePrefabCategory.ActivePrefab) { DestroyIfExists(); CreateFromActivePrefabIfNotExists(); } } #endregion #region Private Static Functions private static void InstantiateFromActivePrefabIfPossible() { Prefab activePrefab = PrefabQueries.GetActivePrefab(); if (activePrefab != null) InstantiateFromPrefab(activePrefab); } private static void InstantiateFromPrefab(Prefab prefab) { _instance = ObjectInstantiation.InstantiateObjectPlacementGuide(prefab, prefab.Name + "(" + _nameSuffix + ")"); _instance._sourcePrefab = prefab; _instance.transform.rotation = prefab.ActivationSettings.WorldRotation; _instance.gameObject.SetWorldScale(prefab.ActivationSettings.WorldScale); ObjectPlacementGuideWasInstantiatedMessage.SendToInterestedListeners(); } #endregion } } #endif