using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; using static JBooth.MicroVerseCore.Browser.ContentBrowser; #if __MICROVERSE_ROADS__ using UnityEngine.Splines; #endif namespace JBooth.MicroVerseCore.Browser { public class SceneDragHandler { /// /// Identifier for the generic data of a drag and drop operation /// public static string EnabledCollidersId = "Enabled Colliders"; /// /// Identifier whether the draggable is a MicroVerse object or not. /// Had to be introduced in case the content browser remained open the entire session. /// Otherwise it would handle all objects that are dragged into the scene and eg remove them after the drop /// public static string IsMicroverseDraggableId = "MicroVerse Draggable"; /// /// Whether shift was pressed during the start of the drag operation or not /// public static string WasShiftPressed = "Shift Pressed"; /// /// Whether control was pressed during the start of the drag operation or not /// public static string WasControlPressed = "Control Pressed"; private ContentBrowser browser; public SceneDragHandler(ContentBrowser browser) { this.browser = browser; } public void OnEnable() { SceneView.beforeSceneGui += this.OnSceneGUI; } public void OnDisable() { SceneView.beforeSceneGui -= this.OnSceneGUI; } private void OnSceneGUI(SceneView obj) { HandleDragAndDropEvents(); } public void OnDragStart( PresetItem preset) { if (MicroVerse.instance == null) return; // flag that the height stamp is being added // signals microverse that it shouldn't sync the heightmap // otherwise the raycast would cast against this new stamp and we'd get flicker MicroVerse.instance.IsAddingHeightStamp = browser.GetSelectedTab() == Tab.Height; DragAndDrop.PrepareStartDrag(); DragAndDrop.visualMode = DragAndDropVisualMode.Copy; DragAndDrop.SetGenericData("preset", preset); DragAndDrop.SetGenericData(IsMicroverseDraggableId, true); // record if shift was pressed at the start of the drag operation bool wasShiftPressed = Event.current.shift; DragAndDrop.SetGenericData(WasShiftPressed, wasShiftPressed); // record if control was pressed at the start of the drag operation bool wasControlPressed = Event.current.control; DragAndDrop.SetGenericData(WasControlPressed, wasControlPressed); DragAndDrop.StartDrag("Dragging MyData"); } public void HandleDragAndDropEvents() { if (Event.current.type != EventType.DragUpdated && Event.current.type != EventType.DragPerform) return; if (EditorWindow.mouseOverWindow == browser) return; object isMicroVerseDraggable = DragAndDrop.GetGenericData( IsMicroverseDraggableId); bool valid = isMicroVerseDraggable != null && isMicroVerseDraggable is bool && (bool)isMicroVerseDraggable == true; if (!valid) return; object wasShiftPressedObject = DragAndDrop.GetGenericData(WasShiftPressed); object wasControlPressedObject = DragAndDrop.GetGenericData(WasControlPressed); bool wasShiftPressed = wasShiftPressedObject != null && wasShiftPressedObject is bool && (bool)wasShiftPressedObject == true; bool wasControlPressed = wasControlPressedObject != null && wasControlPressedObject is bool && (bool)wasControlPressedObject == true; PresetItem preset = (PresetItem)DragAndDrop.GetGenericData("preset"); if (preset != null) { DragAndDrop.SetGenericData("preset", null); GameObject draggable = browser.contentTabs[(int)browser.GetSelectedTab()].Spawn(browser, preset, wasShiftPressed); DragAndDrop.objectReferences = new GameObject[] { draggable }; DragAndDrop.paths = null; // disable colliders, we don't want to raycast against self; store as generic data for re-enabling later Collider[] enabledColliders = draggable.GetComponentsInChildren().Where(x => x.enabled == true).ToArray(); foreach (Collider collider in enabledColliders) { collider.enabled = false; } DragAndDrop.SetGenericData( EnabledCollidersId, enabledColliders); } if (Event.current.type == EventType.DragUpdated) { DragAndDrop.visualMode = DragAndDropVisualMode.Copy; Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition); Vector3 point; if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity)) { // if( !instance.activeInHierarchy) // instance.SetActive(true); point = hit.point; } else { point = ray.origin + ray.direction * 120; if (point.y < 0 && ray.origin.y > 0) { // if we are above 0, lets clamp the ray at the world axis Plane plane = new Plane(Vector3.up, Vector3.zero); float distance = 0; if (plane.Raycast(ray, out distance)) { // get the hit point: point = ray.GetPoint(distance); } } } if (DragAndDrop.objectReferences.Length == 1) { if (DragAndDrop.objectReferences[0] is GameObject) { GameObject go = DragAndDrop.objectReferences[0] as GameObject; if (browser.GetSelectedTab() == Tab.Height) { point.y = 0; // height stamps always at 0 } else if (browser.GetSelectedTab() == Tab.Roads || browser.GetSelectedTab() == Tab.Caves) { #if __MICROVERSE_ROADS__ point.y += browser.roadHeightOffset; #endif } go.transform.position = point; } } } else if (Event.current.type == EventType.DragPerform) { DragAndDrop.SetGenericData(IsMicroverseDraggableId, false); DragAndDrop.SetGenericData(WasShiftPressed, false); DragAndDrop.SetGenericData(WasControlPressed, false); DragAndDrop.AcceptDrag(); if (DragAndDrop.objectReferences.Length == 1) { // re-enable colliders which were diabled before the drag operation Collider[] enabledColliders = (Collider[])DragAndDrop.GetGenericData(EnabledCollidersId); if (enabledColliders != null) { foreach (Collider collider in enabledColliders) { collider.enabled = true; } } // reference to new object GameObject go = DragAndDrop.objectReferences[0] as GameObject; bool centerTerrain = wasControlPressed; if (centerTerrain) { Terrain[] terrains = MicroVerse.instance.GetComponentsInChildren(); Bounds worldBounds = TerrainUtil.ComputeTerrainBounds(terrains); // position float y = worldBounds.center.y; if (browser.GetSelectedTab() == Tab.Roads || browser.GetSelectedTab() == Tab.Caves) { #if __MICROVERSE_ROADS__ y += browser.roadHeightOffset; #endif } go.transform.transform.position = new Vector3(worldBounds.center.x, y, worldBounds.center.z); } // select new object Selection.activeObject = go; } // cleanup drag DragFinished(); DragAndDrop.objectReferences = new Object[0]; DragAndDrop.visualMode = DragAndDropVisualMode.None; DragAndDrop.SetGenericData(EnabledCollidersId, null); Event.current.Use(); } /* note: doesn't seem to work, DragExited is also invoked after DragPerform // eg escape pressed else if (Event.current.type == EventType.DragExited) { if (DragAndDrop.objectReferences.Length == 1) { GameObject go = DragAndDrop.objectReferences[0] as GameObject; GameObject.DestroyImmediate(go); } // cleanup drag DragAndDrop.objectReferences = new Object[0]; DragAndDrop.visualMode = DragAndDropVisualMode.None; Event.current.Use(); DragFinished(); } */ } private void DragFinished() { GameObject go = DragAndDrop.objectReferences[0] as GameObject; #if __MICROVERSE_ROADS__ if (go != null) { SplineRelativeTransform srt = go.GetComponent(); if (srt != null) { RoadSystem[] rss = GameObject.FindObjectsOfType(); if (rss != null) { RoadSystem roadSystem = null; Road closest = null; float closestDist = 9999999; foreach (var rs in rss) { Road[] roads = rs.GetComponentsInChildren(); foreach (var road in roads) { if (road.splineContainer != null && road.splineContainer.Splines.Count > 0) { var spline = road.splineContainer.Spline; var dist = SplineUtility.GetNearestPoint(spline, road.splineContainer.transform.worldToLocalMatrix.MultiplyPoint( go.transform.position), out _, out _); if (dist < closestDist) { closest = road; closestDist = dist; roadSystem = rs; } } } } if (closest != null && closestDist < 30) { srt.splineContainer = closest.splineContainer; srt.transform.SetParent(roadSystem.transform, true); srt.CaptureOffset(); srt.enabled = true; } } } } #endif // height stamp dragging finished => allow syncing again MicroVerse.instance.IsAddingHeightStamp = false; // perform action after drop for gameobjects that implement the interface #region IContentBrowserDropAction if( go != null) { PerformDropAction(go, out bool destroyGameObject); // optionally destroy the gameobject in case eg only data got applied if (destroyGameObject) { GameObject.DestroyImmediate(go); } } #endregion IExecuteOnContentBrowserDrop if (go != null) { Undo.RegisterCreatedObjectUndo(go, go.name); } MicroVerse.instance?.Invalidate(null); // TODO : Do Better? } /// /// Execute actions in case the dropped gameobject implements them /// /// /// private void PerformDropAction( GameObject dropGo, out bool destroyGameObject) { // find gameobjects that implement the interface IContentBrowserDropAction[] dropActions = dropGo.GetComponentsInChildren(); destroyGameObject = false; // execute the actions, find out if gameobject should be destroyed for (int i = 0; i < dropActions.Length; i++) { IContentBrowserDropAction dropAction = dropActions[i]; if (dropAction == null) continue; // perform action dropAction.Execute(out bool destroyAfterExecute); // collect information if all instances require to destroy the gameobject; if there's at least 1 that doesn't want it destroyed, then it won't be destroyGameObject = i == 0 ? destroyAfterExecute : destroyGameObject && destroyAfterExecute; } } } }