#if UNITY_EDITOR

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEditor;
using System.Linq;
using System.Reflection;
using UnityEditor.Callbacks;
namespace Sabresaurus.SabreCSG
{
    [ExecuteInEditMode]
    public class CSGModel : CSGModelBase
    {
#if UNITY_EDITOR

        [SerializeField, HideInInspector]
        private bool firstRun = true;

        private const int MODEL_VERSION = 1;

        // Warning disabled as this field is not in use yet, but helps future proof some cases
#pragma warning disable 414

        [SerializeField, HideInInspector]
        private int modelVersion = 0;

#pragma warning restore 414

        [SerializeField, HideInInspector]
        private bool autoRebuild = false;

        private bool editMode = false;
        private static CSGModel editModeModel = null;

        private bool mouseIsDragging = false;
        private bool mouseIsHeld = false;
        private bool mouseIsDoubleClick = false;
        private DateTime mouseReleaseTime = DateTime.MinValue;

        // Tools
        private Tool activeTool = null;

        // Used to track what objects have been previously clicked on, so that the user can cycle click through objects
        // on the same (or similar) ray cast
        private List<GameObject> previousHits = new List<GameObject>();

        private List<GameObject> lastHitSet = new List<GameObject>();

        // Marked as serialized to persist through recompiles, used by draw tool
        [SerializeField, HideInInspector]
        private Brush lastSelectedBrush = null;

        private float currentFrameTimestamp = 0;
        private float currentFrameDelta = 0;

        private static bool anyCSGModelsInEditMode = false;

        private static UnityEngine.Object[] deferredSelection = null;

        public float CurrentFrameDelta
        {
            get
            {
                return currentFrameDelta;
            }
        }

		Dictionary<MainMode, Tool> tools = new Dictionary<MainMode, Tool>()
		{
			{ MainMode.Resize, new ResizeEditor() },
			{ MainMode.Vertex, new VertexEditor() },
			{ MainMode.Face, new SurfaceEditor() },
			{ MainMode.Clip, new ClipEditor() },
			{ MainMode.Draw, new DrawEditor() },
            { MainMode.Paint, new PaintEditor() },
		};

        private Dictionary<OverrideMode, Tool> overrideTools = new Dictionary<OverrideMode, Tool>()
        {
            { OverrideMode.TransformModel, new TransformModelEditor() },
            //{ OverrideMode.Clip, new ClipEditor() },
            //{ OverrideMode.Draw, new DrawEditor() },
        };

        public Tool ActiveTool
        {
            get
            {
                return activeTool;
            }
        }

        public bool MouseIsDragging
        {
            get
            {
                return mouseIsDragging;
            }
        }

        public bool MouseIsHeld
        {
            get
            {
                return mouseIsHeld;
            }
        }

        public bool MouseIsHeldOrRecent
        {
            get
            {
                return mouseIsHeld || (MouseReleaseDuration < 0.22f);
            }
        }

        public double MouseReleaseDuration
        {
            get
            {
                return (DateTime.UtcNow - mouseReleaseTime).TotalSeconds;
            }
        }

        public Brush LastSelectedBrush
        {
            get
            {
                return lastSelectedBrush;
            }
        }

        public bool AutoRebuild
        {
            get
            {
                return autoRebuild;
            }
            set
            {
                autoRebuild = value;
            }
        }

        protected override void Start()
        {
            UpdateUtility.RunCleanup();

            // SabreCSG turns off Auto Generate, but loading a scene in editor will look too dark without it, so force refresh
            DynamicGI.UpdateEnvironment();

            base.Start();

            if (firstRun)
            {
                // Make sure editing is turned on
                EditMode = true;

                brushes = new List<Brush>(transform.GetComponentsInChildren<Brush>(false));

                // Only create a brush if the model doesn't already contain one
                if (brushes.Count == 0)
                {
                    // Create the default brush
                    GameObject newBrushObject = CreateBrush(PrimitiveBrushType.Cube, new Vector3(0, 1, 0));
                    // Set the selection to the new object
                    Selection.activeGameObject = newBrushObject;
                }

                firstRun = false;
                EditorHelper.SetDirty(this);
            }

            // Make sure we are correctly tracking whether any CSG Models are actually in edit mode
            bool wereAnyInEditMode = anyCSGModelsInEditMode;
            anyCSGModelsInEditMode = false;
            CSGModel[] csgModels = FindObjectsOfType<CSGModel>();

            // Loop through all the CSG Models in the scene and check if any are in edit mode
            for (int i = 0; i < csgModels.Length; i++)
            {
                if (csgModels[i] != this
                    && csgModels[i].EditMode)
                {
                    anyCSGModelsInEditMode = true;
                }
            }

            // If the status of whether any Models are in edit mode has changed, make sure all the brushes update their
            // visibility. For example we moved from editing a CSG Model to opening another scene where no CSG Model is
            // in edit mode. wereAnyInEditMode would be true and anyCSGModelsInEditMode would now be false
            if (anyCSGModelsInEditMode != wereAnyInEditMode)
            {
                UpdateAllBrushesVisibility();
            }

            if (modelVersion < MODEL_VERSION)
            {
                // Upgrading or a new model, so grab all the brushes in case it's an upgrade
                brushes = new List<Brush>(transform.GetComponentsInChildren<Brush>(false));

                // Make sure all brushes have a valid brush cache and need rebuilding
                for (int i = 0; i < brushes.Count; i++)
                {
                    if (brushes[i] != null)
                    {
                        brushes[i].RecachePolygons(true);
                    }
                }

                // Force all brushes to recalculate intersections
                for (int i = 0; i < brushes.Count; i++)
                {
                    if (brushes[i] != null)
                    {
                        brushes[i].RecalculateIntersections(brushes, false);
                    }
                }

                // Finally now that the potential upgrade is complete, track that the model is the correct version now
                modelVersion = MODEL_VERSION;
            }
        }

        public override void Build(bool forceRebuild, bool buildInBackground)
        {
            // Build can take place if meshes are not saved to the DB, or if the scene is saved
            if (!buildSettings.SaveMeshesAsAssets || EnsureUntitledSceneHasBeenSaved("Scene must be saved for SaveMeshesAsAssets to work"))
            {
                base.Build(forceRebuild, buildInBackground);
            }
        }

        private bool EnsureUntitledSceneHasBeenSaved(string message)
        {
#if UNITY_5_6_OR_NEWER || UNITY_5_6
            return UnityEditor.SceneManagement.EditorSceneManager.EnsureUntitledSceneHasBeenSaved(message);

#elif UNITY_5_3_OR_NEWER
            if(string.IsNullOrEmpty(SceneManager.GetActiveScene().path)) // Scene not saved
            {
                // Ask the user to save
                UnityEditor.SceneManagement.EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo();

                // Check that the scene was saved
                if(!string.IsNullOrEmpty(SceneManager.GetActiveScene().path))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return true;
            }
#else
            if(string.IsNullOrEmpty(EditorApplication.currentScene))
            {
                // Ask the user to save
                EditorApplication.SaveCurrentSceneIfUserWantsTo();

                // Check that the scene was saved
                if(!string.IsNullOrEmpty(EditorApplication.currentScene))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return true;
            }
#endif
        }

        public override void OnBuildComplete()
        {
            base.OnBuildComplete();

            EditorUtility.ClearProgressBar();

            UpdateAllBrushesVisibility();

            EditorHelper.SetDirty(this);
            SetContextDirty();
        }

        public void OnSceneGUI(SceneView sceneView)
        {
            if (Event.current.type == EventType.ExecuteCommand)
            {
                if (Event.current.commandName == "Duplicate")
                {
                    if (EditorHelper.DuplicateSelection())
                    {
                        Event.current.Use();
                    }
                }
            }
            Event e = Event.current;

#if UNITY_2021_2_OR_NEWER
            if (SabreToolbarOverlay.Instance != null) SabreToolbarOverlay.Instance.displayed = EditMode;
            if (SabreToolsOverlay.Instance != null) SabreToolsOverlay.Instance.displayed = EditMode;
#endif

            if (!EditMode)
            {
                return;
            }

            RadialMenu.OnEarlySceneGUI(sceneView);

            // Frame rate tracking
            if (e.type == EventType.Repaint)
            {
                currentFrameDelta = Time.realtimeSinceStartup - currentFrameTimestamp;
                currentFrameTimestamp = Time.realtimeSinceStartup;
            }

            // Raw checks for tracking mouse events (use raw so that consumed events are not ignored)
            if (e.rawType == EventType.MouseDown)
            {
                mouseIsDragging = false;
                mouseIsHeld = true;

                if (e.button == 0 && GUIUtility.hotControl == 0)
                {
                    GUIUtility.keyboardControl = 0;
                }
            }
            else if (e.rawType == EventType.MouseDrag)
            {
                mouseIsDragging = true;
            }
            else if (e.rawType == EventType.MouseUp)
            {
                mouseIsHeld = false;
                mouseReleaseTime = DateTime.UtcNow;
            }

            //			if (CurrentSettings.BrushesVisible)
            {
                // No idea what this line of code means, but it seems to stop normal mouse selection
                HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));
            }

            if (EditMode && CurrentSettings.OverrideMode != OverrideMode.TransformModel)
            {
                // In CSG mode, prevent the normal tools, so that the user must use our tools instead
                Tools.current = UnityEditor.Tool.None;
            }

            int concaveBrushCount = 0;
            for (int i = 0; i < brushes.Count; i++)
            {
                if (brushes[i] != null && !brushes[i].IsBrushConvex)
                {
                    concaveBrushCount++;
                }
            }
            if (concaveBrushCount > 0)
            {
                Toolbar.WarningMessage = concaveBrushCount + " Concave Brush" + (concaveBrushCount > 1 ? "es" : "") + " Detected";
            }
            else
            {
                //				Toolbar.WarningMessage = "";
            }

            Toolbar.CSGModel = this;
            Toolbar.OnSceneGUI(sceneView, e);

            if (e.type == EventType.Repaint)// || e.type == EventType.Layout)
            {
                if (CurrentSettings.ShowExcludedPolygons)
                {
                    List<Polygon> allPolygons = BuildContext.VisualPolygons ?? new List<Polygon>();
                    List<Polygon> excluded = new List<Polygon>();
                    for (int i = 0; i < allPolygons.Count; i++)
                    {
                        if (allPolygons[i].UserExcludeFromFinal)
                        {
                            excluded.Add(allPolygons[i]);
                        }
                    }
                    SabreCSGResources.GetExcludedMaterial().SetPass(0);

                    Mesh mesh = new Mesh();
                    BrushFactory.GenerateMeshFromPolygonsFast(excluded.ToArray(), ref mesh, 0.0f);

                    Graphics.DrawMeshNow(mesh, Vector3.zero, Quaternion.identity);

                    //					SabreGraphics.DrawPolygons(new Color(1,1,0,0.65f), new Color(1,0.8f,0,0.9f), excluded.ToArray());
                }

                if (activeTool == null || activeTool.BrushesHandleDrawing)
                {
                    SabreCSGResources.GetSelectedBrushMaterial().SetPass(0);
                    // Selection
                    GL.Begin(GL.LINES);
                    Color outlineColor = Color.blue;

                    for (int brushIndex = 0; brushIndex < brushes.Count; brushIndex++)
                    {
                        Brush brush = brushes[brushIndex];
                        if (brush == null)
                        {
                            continue;
                        }
                        GameObject brushGameObject = brush.gameObject;

                        if (!brushGameObject.activeInHierarchy)
                        {
                            continue;
                        }

                        if (Selection.Contains(brushGameObject))
                        {
                            if (brushes[brushIndex].Mode == CSGMode.Volume)
                            {
                                outlineColor = new Color32(192, 192, 192, 255);
                            }
                            else if (!brushes[brushIndex].IsVisible)
                            {
                                outlineColor = new Color(0.6f, 1.0f, 0.6f);
                            }
                            else if (brushes[brushIndex].IsNoCSG)
                            {
                                outlineColor = new Color(1f, 0.6f, 1.0f);
                            }
                            else
                            {
                                if (brushes[brushIndex].Mode == CSGMode.Add)
                                {
                                    outlineColor = Color.cyan;
                                }
                                else
                                {
                                    outlineColor = Color.yellow;
                                }
                            }
                        }
                        else if (CurrentSettings.BrushesVisible)
                        {
                            if (brushes[brushIndex].Mode == CSGMode.Volume)
                            {
                                outlineColor = new Color32(0, 0, 0, 255);
                            }
                            else if (!brushes[brushIndex].IsVisible)
                            {
                                outlineColor = new Color(0.0f, 1.0f, 0.0f);
                            }
                            else if (brushes[brushIndex].IsNoCSG)
                            {
                                outlineColor = new Color(0.8f, 0.3f, 1.0f);
                            }
                            else
                            {
                                if (brushes[brushIndex].Mode == CSGMode.Add)
                                {
                                    outlineColor = Color.blue;
                                }
                                else
                                {
                                    outlineColor = new Color32(255, 130, 0, 255);
                                }
                            }
                        }
                        else
                        {
                            continue;
                        }

                        GL.Color(outlineColor);

                        Polygon[] polygons = brush.GetPolygons();
                        Transform brushTransform = brush.transform;

                        // Brush Outline
                        for (int i = 0; i < polygons.Length; i++)
                        {
                            Polygon polygon = polygons[i];

                            for (int j = 0; j < polygon.Vertices.Length; j++)
                            {
                                Vector3 position = brushTransform.TransformPoint(polygon.Vertices[j].Position);
                                GL.Vertex(position);

                                if (j < polygon.Vertices.Length - 1)
                                {
                                    Vector3 position2 = brushTransform.TransformPoint(polygon.Vertices[j + 1].Position);
                                    GL.Vertex(position2);
                                }
                                else
                                {
                                    Vector3 position2 = brushTransform.TransformPoint(polygon.Vertices[0].Position);
                                    GL.Vertex(position2);
                                }
                            }
                        }
                    }

                    GL.End();

                    for (int i = 0; i < brushes.Count; i++)
                    {
                        if (brushes[i] is PrimitiveBrush && brushes[i] != null && brushes[i].gameObject.activeInHierarchy)
                        {
                            ((PrimitiveBrush)brushes[i]).OnRepaint(sceneView, e);
                        }
                    }
                }
            }

            if (e.type == EventType.Repaint)
            {
                Rect rect = new Rect(0, 0, Screen.width, Screen.height);
                EditorGUIUtility.AddCursorRect(rect, SabreMouse.ActiveCursor);
            }

            if (e.type == EventType.Layout)
            {
                // Don't need to do this for every OnSceneGUI call, so limit it to Layout events
                if (activeTool == null
                    || activeTool != GetActiveTool())
                {
                    UpdateActiveTool();
                }
            }

            if (activeTool != null)
            {
                activeTool.CSGModel = this;
                activeTool.SetSelection(Selection.activeGameObject, Selection.gameObjects);
                activeTool.OnSceneGUI(sceneView, e);
            }

            //			if(e.type == EventType.DragPerform)
            //			{
            //				Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
            //
            //				RaycastHit hit = new RaycastHit();
            //
            //				int layerMask = 1 << LayerMask.NameToLayer("CSGMesh");
            //				// Invert the layer mask
            //				layerMask = ~layerMask;
            //
            //				// Shift mode means only add what they click (clicking nothing does nothing)
            //				if (Physics.Raycast(ray, out hit, float.PositiveInfinity, layerMask))
            //				{
            //										OnDragDrop(hit.collider.gameObject);
            //				}
            //			}

            if (e.type == EventType.MouseDown && e.clickCount == 2)
            {
                mouseIsDoubleClick = true;
                OnMouseDoubleClick(sceneView, e);
            }
            if (e.type == EventType.MouseUp && !RadialMenu.IsActive)
            {
                OnMouseUp(sceneView, e);
                mouseIsDoubleClick = false;

                SabreMouse.ResetCursor();
            }
            else if (e.type == EventType.KeyDown || e.type == EventType.KeyUp)
            {
                OnKeyAction(sceneView, e);
            }

            if (CurrentSettings.OverrideFlyCamera)
            {
                LinearFPSCam.OnSceneGUI(sceneView);
            }

            RadialMenu.OnLateSceneGUI(sceneView);
        }

        private void OnMouseDoubleClick(SceneView sceneView, Event e)
        {
            if (mouseIsDragging
                || (activeTool != null && activeTool.PreventBrushSelection)
                || EditorHelper.IsMousePositionInInvalidRects(e.mousePosition))
            {
                SceneView.RepaintAll();
                return;
            }

            // Left double click - select individual brush inside of a group.
            if (e.button == 0)
            {
                Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
                List<PolygonRaycastHit> hits = RaycastBrushesAll(ray, true);
                List<Brush> hitBrushes = hits.Select(hit => hit.GameObject.GetComponent<Brush>()).ToList();
                List<GameObject> hitObjects = hitBrushes.Select(hit => hit.gameObject).ToList();

                if (hitObjects.Count >= 1)
                {
                    if (hitObjects[0].transform.parent && hitObjects[0].transform.parent.GetComponent<GroupBrush>() != null)
                    {
                        Selection.activeGameObject = hitObjects[0];
                        previousHits.Clear();
                        lastHitSet.Clear();
                    }
                }
            }
        }

        private void OnMouseUp(SceneView sceneView, Event e)
        {
            if (mouseIsDragging
                || (activeTool != null && activeTool.PreventBrushSelection)
                || EditorHelper.IsMousePositionInInvalidRects(e.mousePosition))
            {
                SceneView.RepaintAll();
                return;
            }

            // don't change the selection when double clicked.
            if (mouseIsDoubleClick) return;

            // Left click - select
            if (e.button == 0)
            {
                Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
                List<PolygonRaycastHit> hits = RaycastBrushesAll(ray, true);
                List<Brush> hitBrushes = hits.Select(hit => hit.GameObject.GetComponent<Brush>()).ToList();
                List<BrushBase> hitBases = ExtractBrushBases(hitBrushes);
                List<GameObject> hitObjects = hitBases.Select(hit => hit.gameObject).ToList();

                GameObject selectedObject = null;

                if (hitObjects.Count == 0) // Didn't hit anything, blank the selection
                {
                    previousHits.Clear();
                    lastHitSet.Clear();
                }
                else if (hitObjects.Count == 1) // Only hit one thing, no ambiguity, this is what is selected
                {
                    selectedObject = hitObjects[0];
                    previousHits.Clear();
                    lastHitSet.Clear();
                }
                else
                {
                    if (!hitObjects.ContentsEquals(lastHitSet))
                    {
                        selectedObject = hitObjects[0];
                        previousHits.Clear();
                        lastHitSet = hitObjects;
                    }
                    else
                    {
                        // First try and select anything other than what has been previously hit
                        for (int i = 0; i < hitObjects.Count; i++)
                        {
                            if (!previousHits.Contains(hitObjects[i]))
                            {
                                selectedObject = hitObjects[i];
                                break;
                            }
                        }

                        // Only found previously hit objects
                        if (selectedObject == null)
                        {
                            // Walk backwards to find the oldest previous hit that has been hit by this ray
                            for (int i = previousHits.Count - 1; i >= 0 && selectedObject == null; i--)
                            {
                                for (int j = 0; j < hitObjects.Count; j++)
                                {
                                    if (hitObjects[j] == previousHits[i])
                                    {
                                        selectedObject = previousHits[i];
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }

                if (selectedObject != null)
                {
                    previousHits.Remove(selectedObject);
                    // Most recent hit
                    previousHits.Insert(0, selectedObject);
                }

                if (EnumHelper.IsFlagSet(e.modifiers, EventModifiers.Shift)
                    || EnumHelper.IsFlagSet(e.modifiers, EventModifiers.Control)
                    || EnumHelper.IsFlagSet(e.modifiers, EventModifiers.Command))
                {
                    List<UnityEngine.Object> objects = new List<UnityEngine.Object>(Selection.objects);

                    if (objects.Contains(selectedObject))
                    {
                        objects.Remove(selectedObject);
                    }
                    else
                    {
                        objects.Add(selectedObject);
                    }
                    Selection.objects = objects.ToArray();
                }
                else
                {
                    Selection.activeGameObject = selectedObject;
                }

                e.Use();
            }
        }

        /// <summary>
        /// Subscribes to both KeyDown and KeyUp events from the SceneView delegate. This allows us to easily store key
        /// events in one place and mark them as used as necessary (for example to prevent error sounds on key down)
        /// </summary>
        private void OnKeyAction(SceneView sceneView, Event e)
        {
            OnGenericKeyAction(sceneView, e);
        }

        private void OnGenericKeyAction(SceneView sceneView, Event e)
        {
            if (KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.ToggleMode))
                || KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.ToggleModeBack)))
            {
                // Toggle mode - immediately (key down)
                if (e.type == EventType.KeyDown)
                {
                    int currentModeInt = (int)CurrentSettings.CurrentMode;
                    int count = Enum.GetNames(typeof(MainMode)).Length;

                    if (KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.ToggleModeBack)))
                    {
                        currentModeInt--;
                    }
                    else
                    {
                        currentModeInt++;
                    }

                    if (currentModeInt >= count)
                    {
                        currentModeInt = 0;
                    }
                    else if (currentModeInt < 0)
                    {
                        currentModeInt = count - 1;
                    }

                    SetCurrentMode((MainMode)currentModeInt);

                    SceneView.RepaintAll();
                }
                e.Use();
            }
            else if (!MouseIsHeld && KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.ActivateClipMode)))
            {
                // Activate mode - immediately (key down)
                if (e.type == EventType.KeyDown && !MouseIsHeldOrRecent)
                {
                    SetCurrentMode(MainMode.Clip);
                    //					SetOverrideMode(OverrideMode.Clip);

                    SceneView.RepaintAll();
                }
                e.Use();
            }
            else if (!MouseIsHeld && KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.ActivateDrawMode)))
            {
                // Activate mode - immediately (key down)
                if (e.type == EventType.KeyDown && !MouseIsHeldOrRecent)
                {
                    SetCurrentMode(MainMode.Draw);
                    //					SetOverrideMode(OverrideMode.Draw);

                    SceneView.RepaintAll();
                }
                e.Use();
            }
            else if (!MouseIsHeld && KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.ActivateFaceMode)))
            {
                // Activate mode - immediately (key down)
                if (e.type == EventType.KeyDown && !MouseIsHeldOrRecent)
                {
                    SetCurrentMode(MainMode.Face);
                    //					SetOverrideMode(OverrideMode.Face);

                    SceneView.RepaintAll();
                }
                e.Use();
            }
            else if (!MouseIsHeld && KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.ActivateVertexMode)))
            {
                // Activate mode - immediately (key down)
                if (e.type == EventType.KeyDown && !MouseIsHeldOrRecent)
                {
                    SetCurrentMode(MainMode.Vertex);
                    //					SetOverrideMode(OverrideMode.Vertex);

                    SceneView.RepaintAll();
                }
                e.Use();
            }
            else if (!MouseIsHeld && KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.ActivateResizeMode)))
            {
                if (e.type == EventType.KeyDown && !MouseIsHeldOrRecent)
                {
                    SetCurrentMode(MainMode.Resize);
                    SceneView.RepaintAll();
                }
                e.Use();
            }
            else if (!MouseIsHeld && KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.ActivatePaintMode)))
            {
                if (e.type == EventType.KeyDown && !MouseIsHeldOrRecent)
                {
                    SetCurrentMode(MainMode.Paint);
                    SceneView.RepaintAll();
                }
                e.Use();
            }
            else if (KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.IncreasePosSnapping))
                && !SabreGUIHelper.AnyControlFocussed)
            {
                if (e.type == EventType.KeyUp)
                {
                    CurrentSettings.ChangePosSnapDistance(2f);
                    SceneView.RepaintAll();
                }
                e.Use();
            }
            else if (KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.DecreasePosSnapping))
                && !SabreGUIHelper.AnyControlFocussed)
            {
                if (e.type == EventType.KeyUp)
                {
                    CurrentSettings.ChangePosSnapDistance(.5f);
                    SceneView.RepaintAll();
                }
                e.Use();
            }
            else if (KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.TogglePosSnapping))
                && !SabreGUIHelper.AnyControlFocussed)
            {
                if (e.type == EventType.KeyUp)
                {
                    CurrentSettings.PositionSnappingEnabled = !CurrentSettings.PositionSnappingEnabled;
                    SceneView.RepaintAll();
                }
                e.Use();
            }
            else if (KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.IncreaseAngSnapping))
                && !SabreGUIHelper.AnyControlFocussed)
            {
                if (e.type == EventType.KeyUp)
                {
                    if (CurrentSettings.AngleSnapDistance >= 15)
                    {
                        CurrentSettings.AngleSnapDistance += 15;
                    }
                    else
                    {
                        CurrentSettings.AngleSnapDistance += 5;
                    }
                    SceneView.RepaintAll();
                }
                e.Use();
            }
            else if (KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.DecreaseAngSnapping))
                && !SabreGUIHelper.AnyControlFocussed)
            {
                if (e.type == EventType.KeyUp)
                {
                    if (CurrentSettings.AngleSnapDistance > 15)
                    {
                        CurrentSettings.AngleSnapDistance -= 15;
                    }
                    else
                    {
                        CurrentSettings.AngleSnapDistance -= 5;
                    }
                    SceneView.RepaintAll();
                }
                e.Use();
            }
            else if (KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.ToggleAngSnapping))
                && !SabreGUIHelper.AnyControlFocussed)
            {
                if (e.type == EventType.KeyUp)
                {
                    CurrentSettings.AngleSnappingEnabled = !CurrentSettings.AngleSnappingEnabled;
                    SceneView.RepaintAll();
                }
                e.Use();
            }
            else if (KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.ToggleBrushesHidden))
                && !SabreGUIHelper.AnyControlFocussed)
            {
                if (e.type == EventType.KeyUp)
                {
                    CurrentSettings.BrushesHidden = !CurrentSettings.BrushesHidden;
                    UpdateAllBrushesVisibility();
                    SceneView.RepaintAll();
                }
                e.Use();
            }
            else if (KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.ToggleProjectedGrid))
                && !SabreGUIHelper.AnyControlFocussed)
            {
                if (e.type == EventType.KeyUp)
                {
                    CurrentSettings.ProjectedGridEnabled = !CurrentSettings.ProjectedGridEnabled;

                    SceneView.RepaintAll();
                }
                e.Use();
            }
            else if (KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.ToggleBrushesAsWireframes))
                && !SabreGUIHelper.AnyControlFocussed)
            {
                if (e.type == EventType.KeyUp)
                {
                    CurrentSettings.ShowBrushesAsWireframes = !CurrentSettings.ShowBrushesAsWireframes;

                    SceneView.RepaintAll();
                }
                e.Use();
            }
            else if (KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.EnableRadialMenu))
                && !SabreGUIHelper.AnyControlFocussed)
            {
                if (e.type == EventType.KeyUp)
                {
                    RadialMenu.IsActive = true;
                    SceneView.RepaintAll();
                }
                e.Use();
            }
            else if (!MouseIsHeld && (KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.ChangeBrushToAdditive))
                || KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.ChangeBrushToAdditive2)))
            )
            {
                if (e.type == EventType.KeyDown && !MouseIsHeldOrRecent)
                {
                    bool anyChanged = false;

                    for (int i = 0; i < Selection.gameObjects.Length; i++)
                    {
                        BrushBase brushBase = Selection.gameObjects[i].GetComponent<BrushBase>();
                        if (brushBase != null)
                        {
                            Undo.RecordObject(brushBase, "Change Brush To Add");
                            brushBase.Mode = CSGMode.Add;
                            anyChanged = true;
                        }
                    }
                    if (anyChanged)
                    {
                        // Need to update the icon for the csg mode in the hierarchy
                        EditorApplication.RepaintHierarchyWindow();
                        SceneView.RepaintAll();
                    }
                }
                e.Use();
            }
            else if (!MouseIsHeld && (KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.ChangeBrushToSubtractive))
                || KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.ChangeBrushToSubtractive2)))
            )
            {
                if (e.type == EventType.KeyDown && !MouseIsHeldOrRecent)
                {
                    bool anyChanged = false;

                    for (int i = 0; i < Selection.gameObjects.Length; i++)
                    {
                        BrushBase brushBase = Selection.gameObjects[i].GetComponent<BrushBase>();
                        if (brushBase != null)
                        {
                            Undo.RecordObject(brushBase, "Change Brush To Subtract");
                            brushBase.Mode = CSGMode.Subtract;
                            anyChanged = true;
                        }
                    }
                    if (anyChanged)
                    {
                        // Need to update the icon for the csg mode in the hierarchy
                        EditorApplication.RepaintHierarchyWindow();
                        SceneView.RepaintAll();
                    }
                }
                e.Use();
            }
            else if (!MouseIsHeld && (KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.Group))
                    && !SabreGUIHelper.AnyControlFocussed)
            )
            {
                if (e.type == EventType.KeyDown && !MouseIsHeldOrRecent)
                {
                    TransformHelper.GroupSelection();
                }
                e.Use();
            }
            else if (!MouseIsHeld && (KeyMappings.EventsMatch(e, Event.KeyboardEvent(KeyMappings.Instance.Ungroup))
                && !SabreGUIHelper.AnyControlFocussed)
            )
            {
                if (e.type == EventType.KeyDown && !MouseIsHeldOrRecent)
                {
                    TransformHelper.UngroupSelection();
                }
                e.Use();
            }
        }

#if !(UNITY_5_0 || UNITY_5_1)

        private void OnSelectionChanged()
        {
            bool onlyCSGModelsSelected = true; // Does not include brushes
            bool anyCSGObjectsSelected = false; // Includes brushes and models
            bool anyNonCSGSelected = false;

            List<CSGModel> foundModels = new List<CSGModel>();
            Dictionary<CSGModel, List<UnityEngine.Object>> selectedBrushes = new Dictionary<CSGModel, List<UnityEngine.Object>>();

            for (int i = 0; i < Selection.gameObjects.Length; i++)
            {
                // Skip any selected prefabs in the project window
#if UNITY_2018_2_OR_NEWER
				if(PrefabUtility.GetCorrespondingObjectFromSource(Selection.gameObjects[i]) == null
#else
                if (PrefabUtility.GetPrefabParent(Selection.gameObjects[i]) == null
#endif
#if !UNITY_2018_3_OR_NEWER
					&& PrefabUtility.GetPrefabObject(Selection.gameObjects[i].transform) != null)
#else
					&& PrefabUtility.GetPrefabInstanceHandle(Selection.gameObjects[i].transform) != null)
#endif
				{
					continue;
				}

				PrimitiveBrush primitiveBrush = Selection.gameObjects[i].GetComponent<PrimitiveBrush>();
                CSGModel csgModel = Selection.gameObjects[i].GetComponent<CSGModel>();

                if (csgModel == null)
                {
                    // Something other than a CSG Model has been selected
                    onlyCSGModelsSelected = false;
                }

                if (primitiveBrush != null)
                {
                    csgModel = primitiveBrush.GetCSGModel() as CSGModel;

                    if (csgModel != null)
                    {
                        if (!foundModels.Contains(csgModel))
                        {
                            foundModels.Add(csgModel);
                            selectedBrushes[csgModel] = new List<UnityEngine.Object>();
                        }

                        selectedBrushes[csgModel].Add(primitiveBrush.gameObject);
                    }
                }

                if (csgModel != null) // If a CSG Model is selected, or a brush has told us about its parent CSG Model
                {
                    anyCSGObjectsSelected = true;

                    if (!foundModels.Contains(csgModel))
                    {
                        foundModels.Add(csgModel);
                        selectedBrushes[csgModel] = new List<UnityEngine.Object>();
                    }
                }
                else // None found, try recursing up to find a CSG Model
                {
                    CSGModel[] parentCSGModels = Selection.gameObjects[i].GetComponentsInParent<CSGModel>(true);
                    if (parentCSGModels.Length > 0)
                    {
                        csgModel = parentCSGModels[0];

                        if (Selection.gameObjects[i].GetComponent<MeshFilter>() != null
                            || Selection.gameObjects[i].GetComponent<MeshCollider>() != null)
                        {
                            anyNonCSGSelected = true;
                        }
                        else
                        {
                            anyCSGObjectsSelected = true;

                            if (!foundModels.Contains(csgModel))
                            {
                                foundModels.Add(csgModel);
                                selectedBrushes[csgModel] = new List<UnityEngine.Object>();
                            }
                        }
                    }
                    else
                    {
                        anyNonCSGSelected = true;
                    }
                }
            }

            if (anyCSGObjectsSelected)
            {
                CSGModel activeModel = null;
                if (foundModels.Count == 1)
                {
                    if (!foundModels[0].EditMode)
                    {
                        foundModels[0].EditMode = true;
                    }
                    activeModel = foundModels[0];
                }
                else
                {
                    bool anyActive = false;

                    for (int i = 0; i < foundModels.Count; i++)
                    {
                        if (foundModels[i].EditMode)
                        {
                            anyActive = true;
                            activeModel = foundModels[i];
                            break;
                        }
                    }

                    if (!anyActive)
                    {
                        foundModels[0].EditMode = true;
                        activeModel = foundModels[0];
                    }
                }

                if (anyNonCSGSelected && activeModel != null)
                {
                    deferredSelection = selectedBrushes[activeModel].ToArray();
                }

                if (this != null && this.EditMode) // Current model can be destroyed
                {
                    if (onlyCSGModelsSelected)
                    {
                        // Only CSG model has been selected, so show special UI
                        SetOverrideMode(OverrideMode.TransformModel);
                    }
                    else
                    {
                        if (CurrentSettings.OverrideMode == OverrideMode.TransformModel)
                        {
                            SetOverrideMode(OverrideMode.None);
                        }
                    }
                }
            }
            else if (anyNonCSGSelected)
            {
                EditMode = false;
            }

            if (EditMode)
            {
                // Walk backwards until we find the last selected brush
                for (int i = Selection.gameObjects.Length - 1; i >= 0; i--)
                {
                    Brush brush = Selection.gameObjects[i].GetComponent<Brush>();

                    if (brush != null)
                    {
                        lastSelectedBrush = brush;
                        break;
                    }
                }
            }
        }

#endif

		public void SetLastSelectedBrush(Brush brush)
        {
            lastSelectedBrush = brush;
        }

        private void OnHierarchyItemGUI(int instanceID, Rect drawRect)
        {
            if (Event.current.type == EventType.ExecuteCommand)
            {
                if (Event.current.commandName == "Duplicate")
                {
                    if (EditorHelper.DuplicateSelection())
                    {
                        Event.current.Use();
                    }
                }
            }

            GameObject gameObject = EditorUtility.InstanceIDToObject(instanceID) as GameObject;

            if (Event.current.type == EventType.DragPerform)
            {
                if (drawRect.Contains(Event.current.mousePosition))
                {
                    if (gameObject != null)
                    {
                        OnDragDrop(gameObject);
                    }
                }
            }

            if (gameObject != null)
            {
                BrushBase brushBase = gameObject.GetComponent<BrushBase>();
                if (brushBase != null)
                {
                    drawRect.xMax -= 2;
                    drawRect.xMin = drawRect.xMax - 16;
                    drawRect.height = 16;

                    Material iconMaterial = null;
                    // Read only brushes have a greyscale icon
                    if (brushBase is PrimitiveBrush)
                    {
                        if (((PrimitiveBrush)brushBase).IsReadOnly)
                        {
                            iconMaterial = SabreCSGResources.GetGreyscaleUIMaterial();
                        }
                    }
                    if (gameObject.GetComponent<GroupBrush>())
                    {
                        Graphics.DrawTexture(drawRect, SabreCSGResources.GroupIconTexture, iconMaterial);
                    }
                    else if (brushBase.Mode == CSGMode.Volume)
                    {
                        Graphics.DrawTexture(drawRect, SabreCSGResources.VolumeIconTexture, iconMaterial);
                    }
                    else if (!brushBase.IsVisible)
                    {
                        Graphics.DrawTexture(drawRect, SabreCSGResources.CollisionIconTexture, iconMaterial);
                    }
                    else if (brushBase.IsNoCSG)
                    {
                        Graphics.DrawTexture(drawRect, SabreCSGResources.NoCSGIconTexture, iconMaterial);
                    }
                    else if (brushBase.Mode == CSGMode.Add)
                    {
                        Graphics.DrawTexture(drawRect, SabreCSGResources.AddIconTexture, iconMaterial);
                    }
                    else
                    {
                        Graphics.DrawTexture(drawRect, SabreCSGResources.SubtractIconTexture, iconMaterial);
                    }
                }
                else if(gameObject.HasComponent<CSGModel>())
                {
                    drawRect.xMax -= 2;
                    drawRect.xMin = drawRect.xMax - 16;
                    drawRect.height = 16;

                    Graphics.DrawTexture(drawRect, SabreCSGResources.SabreCSG16IconTexture);
                }

                if (EditMode)
                {
                    Event e = Event.current;

                    if (e.type == EventType.KeyDown || e.type == EventType.KeyUp)
                    {
                        OnGenericKeyAction(null, e);
                    }
                }
            }
        }

        private int frameIndex;

        private void OnEditorUpdate()
        {
            if (deferredSelection != null)
            {
                Selection.objects = deferredSelection;
                deferredSelection = null;
                SceneView.RepaintAll();
                EditorApplication.RepaintHierarchyWindow();
            }

            if (EditMode)
            {
                frameIndex++;
                if (frameIndex > 1000)
                {
                    frameIndex -= 1000;
                }

                if (AutoRebuild && gameObject.activeInHierarchy && this.enabled)
                {
                    //					if(frameIndex % 30 == 0)
                    {
                        Build(false, false);
                    }
                }

                if (CurrentSettings.OverrideFlyCamera)
                {
                    LinearFPSCam.OnUpdate();
                }
            }

            if (!Application.isPlaying)
            {
                bool buildOccurred = CSGFactory.Tick();
                if (buildOccurred)
                {
                    OnBuildComplete();
                }
            }
        }

        /// <summary>
        /// Used to determined whether we are subscribed to the "During scene GUI" event.
        /// Recompilations will reset this variable to false and we can rebind.
        /// </summary>
        [NonSerialized]
        private bool isSubscribedToDuringSceneGui = false;

        protected override void Update()
        {
            if (editMode && !anyCSGModelsInEditMode)
            {
                anyCSGModelsInEditMode = true;
                UpdateAllBrushesVisibility();
            }

            base.Update();

            // Make sure the events we need to listen for are all bound (recompilation removes listeners, so it is
            // necessary to rebind dynamically)
            if (!isSubscribedToDuringSceneGui)
            {
                // Then resubscribe and repaint
                isSubscribedToDuringSceneGui = true;
#if UNITY_2019_1_OR_NEWER
                SceneView.duringSceneGui -= OnSceneGUI;
                SceneView.duringSceneGui += OnSceneGUI;
#else
                SceneView.onSceneGUIDelegate -= OnSceneGUI;
                SceneView.onSceneGUIDelegate += OnSceneGUI;
#endif
                SceneView.RepaintAll();
            }

#if !(UNITY_5_0 || UNITY_5_1)
            if (!EditorHelper.HasDelegate(Selection.selectionChanged, (Action)OnSelectionChanged))
            {
                Selection.selectionChanged += OnSelectionChanged;
                // When we come out of play mode we lose this event handler and are no longer in edit mode.
                // We call OnSelectionChanged() manually to make sure we go back into edit mode, if a brush is still selected.
                OnSelectionChanged();
                // Make sure all brushes are visible again after a C# recompilation.
                if (anyCSGModelsInEditMode)
                {
                    editModeModel = GetActiveCSGModel();
                    UpdateAllBrushesVisibility(); 
                }
            }
#endif

            if (!EditorHelper.HasDelegate(EditorApplication.hierarchyWindowItemOnGUI, (EditorApplication.HierarchyWindowItemCallback)OnHierarchyItemGUI))
            {
                EditorApplication.hierarchyWindowItemOnGUI += OnHierarchyItemGUI;
            }

            if (EditMode)
            {
                if (!EditorHelper.HasDelegate(EditorApplication.projectWindowItemOnGUI, (EditorApplication.ProjectWindowItemCallback)OnProjectItemGUI))
                {
                    EditorApplication.projectWindowItemOnGUI += OnProjectItemGUI;
                }

                if (!EditorHelper.HasDelegate(EditorApplication.update, (EditorApplication.CallbackFunction)OnEditorUpdate))
                {
                    EditorApplication.update += OnEditorUpdate;
                }

                if (!EditorHelper.HasDelegate(Undo.undoRedoPerformed, (Undo.UndoRedoCallback)OnUndoRedoPerformed))
                {
                    Undo.undoRedoPerformed += OnUndoRedoPerformed;
                }

                // Track whether all the brushes have been destroyed
                bool anyBrushes = false;

                if (brushes != null)
                {
                    for (int i = 0; i < brushes.Count; i++)
                    {
                        if (brushes[i] != null)
                        {
                            anyBrushes = true;
                            break;
                        }
                    }
                }

                Toolbar.WarningMessage = "";
                Brush firstBrush = GetComponentInChildren<Brush>();
                if (firstBrush != null)
                {
                    if (firstBrush.Mode == CSGMode.Subtract)
                    {
                        Toolbar.WarningMessage = "First brush must be additive";
                    }
                    //				anyBrushes = true;
                }

                // All the brushes have been destroyed so add a default cube brush
                if (!Application.isPlaying && !anyBrushes)
                {
                    // Create the default brush
                    //GameObject newBrushObject = CreateBrush(PrimitiveBrushType.Cube, new Vector3(0,1,0));
                    //// Set the selection to the new object
                    //Selection.activeGameObject = newBrushObject;
                }
            }
        }

        private void OnDestroy()
        {
            EditorApplication.update -= OnEditorUpdate;
            EditorApplication.hierarchyWindowItemOnGUI -= OnHierarchyItemGUI;
            EditorApplication.projectWindowItemOnGUI -= OnProjectItemGUI;
#if UNITY_2019_1_OR_NEWER
            SceneView.duringSceneGui -= OnSceneGUI;
#else
            SceneView.onSceneGUIDelegate -= OnSceneGUI;
#endif
            Undo.undoRedoPerformed -= OnUndoRedoPerformed;

            GridManager.UpdateGrid();
        }

        public void RebindToOnSceneGUI()
        {
            // Unbind the delegate, then rebind to ensure our method gets called last
#if UNITY_2019_1_OR_NEWER
            SceneView.duringSceneGui -= OnSceneGUI;
            SceneView.duringSceneGui += OnSceneGUI;
#else
            SceneView.onSceneGUIDelegate -= OnSceneGUI;
            SceneView.onSceneGUIDelegate += OnSceneGUI;
#endif
        }

        public void ExportOBJ(bool limitToSelection)
        {
            List<Polygon> polygonsToExport = new List<Polygon>();

            if (limitToSelection)
            {
                for (int i = 0; i < Selection.gameObjects.Length; i++)
                {
                    PrimitiveBrush brush = Selection.gameObjects[i].GetComponent<PrimitiveBrush>();
                    if (brush != null) // If there's actually a brush attached to the game object
                    {
                        polygonsToExport.AddRange(brush.BrushCache.BuiltVisualPolygons.DeepCopy());
                    }
                }
            }
            else
            {
                if (buildContext.VisualPolygons != null)
                {
                    polygonsToExport = buildContext.VisualPolygons.DeepCopy();
                }
            }

            if (polygonsToExport.Count > 0)
            {
                string path = EditorUtility.SaveFilePanel("Save Geometry As OBJ", "Assets", this.name + ".obj", "obj");
                if (!string.IsNullOrEmpty(path))
                {
                    OBJFactory.ExportToFile(path, transform, polygonsToExport, GetDefaultMaterial());
                    AssetDatabase.Refresh();
                }
            }
            else
            {
                // Nothing to export
                if (limitToSelection)
                {
                    EditorUtility.DisplayDialog("Nothing to export", "No brushes selected corresponding to built geometry", "OK");
                }
                else
                {
                    EditorUtility.DisplayDialog("Nothing to export", "No built geometry found to export", "OK");
                }
            }
        }

        public static string GetSabreCSGPath()
        {
            // Find all the scripts with CSGModel in their name
            string[] guids = AssetDatabase.FindAssets("CSGModel t:Script");

            foreach (string guid in guids)
            {
                // Find the path of the file
                string path = AssetDatabase.GUIDToAssetPath(guid);

                string suffix = "Scripts/CSGModel.cs";
                // If it is the target file, i.e. CSGModel.cs not CSGModelInspector
                if (path.EndsWith(suffix))
                {
                    // Remove the suffix, to get for example Assets/SabreCSG
                    path = path.Remove(path.Length - suffix.Length, suffix.Length);

                    return path;
                }
            }

            // None matched
            return string.Empty;
        }

        /// <summary>
        /// Marks the Build Context associated with this CSG Model as changed
        /// </summary>
        public void SetContextDirty()
        {
            EditorHelper.SetDirty(buildContextBehaviour);
        }

        public void UndoRecordContext(string name)
        {
            Undo.RecordObject(buildContextBehaviour, name);
        }

        private Tool GetActiveTool()
        {
            if (CurrentSettings.OverrideMode != OverrideMode.None)
            {
                return overrideTools[CurrentSettings.OverrideMode];
            }
            else
            {
                return tools[CurrentSettings.CurrentMode];
            }
        }

        private void UpdateActiveTool()
        {
            Tool lastTool = activeTool;

            activeTool = GetActiveTool();

            if (activeTool != lastTool)
            {
                if (lastTool != null)
                {
                    lastTool.Deactivated();
                }
                activeTool.ResetTool();
            }
            UpdateAllBrushesVisibility();
        }

        public Tool GetTool(MainMode mode)
        {
            return tools[mode];
        }

        public void SetCurrentMode(MainMode newMode)
        {
            if (CurrentSettings.OverrideMode == OverrideMode.TransformModel)
            {
                // If we're leaving TransformModel
                bool onlyModelsSelected = true;
                for (int i = 0; i < Selection.gameObjects.Length; i++)
                {
                    if (Selection.gameObjects[i].GetComponent<CSGModel>() == null)
                    {
                        onlyModelsSelected = false;
                        break;
                    }
                }
                // Deselect if only CSG Models are selected
                if (onlyModelsSelected)
                {
                    Selection.activeGameObject = null;
                }
            }
            CurrentSettings.OverrideMode = OverrideMode.None;

            CurrentSettings.CurrentMode = newMode;

            UpdateActiveTool();
        }

        public void SetOverrideMode(OverrideMode newMode)
        {
            CurrentSettings.OverrideMode = newMode;

            UpdateActiveTool();
        }

        public void ExitOverrideMode()
        {
            CurrentSettings.OverrideMode = OverrideMode.None;

            UpdateActiveTool();
        }

        public bool EditMode
        {
            get
            {
                return this.editMode;
            }
            set
            {
                // Has edit mode changed
                if (editMode != value)
                {
                    editMode = value;

                    editModeModel = this;

                    CSGModel[] csgModels = Resources.FindObjectsOfTypeAll<CSGModel>();

                    if (value == true) // Edit mode enabled
                    {
                        // If there are any other CSG Models in the scene, disabling their editing
                        if (csgModels.Length > 1)
                        {
                            for (int i = 0; i < csgModels.Length; i++)
                            {
                                if (csgModels[i] != this)
                                {
                                    csgModels[i].EditMode = false;
                                }
                            }
                        }

                        // This CSG Model is now in edit mode, so we know for sure one is
                        anyCSGModelsInEditMode = true;

                        // Bind listeners
                        EditorApplication.update += OnEditorUpdate;

                        // Force the scene views to repaint (shows our own UI)
                        SceneView.RepaintAll();

                        // Update the projected grid material to match the scale of this CSG Model
                        CurrentSettings.ChangePosSnapDistance(1.0f);

                        //						if(Event.current != null)
                        //						{
                        //							Event.current.Use();
                        //						}

                        //                        SceneView.onSceneGUIDelegate += OnSceneGUI;
                    }
                    else // Edit mode disabled
                    {
                        // Unbind listeners
                        EditorApplication.update -= OnEditorUpdate;

                        //                        SceneView.onSceneGUIDelegate -= OnSceneGUI;

                        // Force the scene views to repaint (hides our own UI)
                        SceneView.RepaintAll();
                        //                        HandleUtility.Repaint();

                        // This CSG Model is no longer in edit mode, so find out if any are
                        anyCSGModelsInEditMode = false;
                        if (csgModels.Length > 1)
                        {
                            for (int i = 0; i < csgModels.Length; i++)
                            {
                                if (csgModels[i] != this
                                    && csgModels[i].EditMode)
                                {
                                    editModeModel = csgModels[i];
                                    anyCSGModelsInEditMode = true;
                                }
                            }
                        }
                    }

                    UpdateAllBrushesVisibility();

                    GridManager.UpdateGrid();
                }
            }
        }

        private void OnProjectItemGUI(string guid, Rect selectionRect)
        {
            //										Debug.Log(Event.current.type.ToString());
            /*
			if (Event.current.type == EventType.MouseDrag)
			{
				if(selectionRect.Contains(Event.current.mousePosition))
				{
					//					Debug.Log(Event.current.type.ToString());
					string path = AssetDatabase.GUIDToAssetPath (guid);
					if(!string.IsNullOrEmpty(path))
					{
						DragAndDrop.PrepareStartDrag();
						DragAndDrop.paths = new string[] { path };

						DragAndDrop.StartDrag ("Dragging material");

						// Make sure no one else uses this event
						Event.current.Use();
					}
				}
			}
			*/
        }

        private void OnDragDrop(GameObject gameObject)
        {
            //			PrimitiveBrush brush = gameObject.GetComponent<PrimitiveBrush>();
            //
            //			if(brush != null)
            //			{
            //				if(DragAndDrop.objectReferences.Length == 1)
            //				{
            //					if(DragAndDrop.objectReferences[0] is Material)
            //					{
            //						brush.Material = (Material)DragAndDrop.objectReferences[0];
            //						DragAndDrop.AcceptDrag();
            //						Event.current.Use();
            //					}
            //				}
            //			}
        }

        private void OnUndoRedoPerformed()
        {
            // An undo or redo operation may restore a brush, so make sure we track all
            brushes = new List<Brush>(transform.GetComponentsInChildren<Brush>(false));

            BrushBase[] brushBases = transform.GetComponentsInChildren<BrushBase>(false);

            // Tell each brush base that an undo/redo has been performed so it can make sure the render mesh is updated
            for (int i = 0; i < brushBases.Length; i++)
            {
                if (brushBases[i] != null)
                {
                    brushBases[i].OnUndoRedoPerformed();
                }
            }

            activeTool.OnUndoRedoPerformed();

            // If the user undos or redos a face change then a shared mesh may be updated.
            // Unity won't automatically refresh the mesh filters that use a shared mesh, so we need to force refresh
            // all of them so they fetch the latest revision of the mesh
            Transform meshGroup = GetMeshGroupTransform();

            if (meshGroup != null)
            {
                MeshFilter[] meshFilters = meshGroup.GetComponentsInChildren<MeshFilter>();
                for (int i = 0; i < meshFilters.Length; i++)
                {
                    meshFilters[i].ForceRefreshSharedMesh();
                }
            }

            EditorApplication.RepaintHierarchyWindow();
        }

        public override bool AreBrushesVisible
        {
            get
            {
                if (!Application.isPlaying)
                {
                    return CurrentSettings.BrushesVisible
                        && anyCSGModelsInEditMode
                        && editModeModel != null
                        && (editModeModel.activeTool == null || editModeModel.activeTool.BrushesHandleDrawing);
                }
                return base.AreBrushesVisible;
            }
        }

        public static void UpdateAllBrushesVisibility()
        {
            CSGModel[] csgModels = Resources.FindObjectsOfTypeAll<CSGModel>();
            for (int i = 0; i < csgModels.Length; i++)
            {
                Transform meshGroup = csgModels[i].transform.Find("MeshGroup");

                if (meshGroup != null)
                {
                    meshGroup.gameObject.SetActive(!CurrentSettings.MeshHidden);
                }

                List<Brush> brushes = csgModels[i].brushes;
                for (int j = 0; j < brushes.Count; j++)
                {
                    if (brushes[j] != null)
                    {
                        brushes[j].UpdateVisibility();
                    }
                }
            }
        }

        public static void RebuildAllVolumes()
        {
            CSGModel[] csgModels = Resources.FindObjectsOfTypeAll<CSGModel>();
            for (int i = 0; i < csgModels.Length; i++)
            {
                List<Brush> brushes = csgModels[i].brushes;
                for (int j = 0; j < brushes.Count; j++)
                {
                    if (brushes[j] != null)
                    {
                        brushes[j].RebuildVolume();
                    }
                }
            }
        }

        public override Material GetDefaultFallbackMaterial()
        {
            if (!Application.isPlaying)
            {
                // To allow users to move the SabreCSG folder, we must base the material loading on the asset path
                return AssetDatabase.LoadMainAssetAtPath(GetSabreCSGPath() + "Resources/" + DEFAULT_FALLBACK_MATERIAL_PATH + ".mat") as Material;
            }
            else
            {
                return base.GetDefaultFallbackMaterial();
            }
        }

        public override void OnBuildProgressChanged(float progress)
        {
            base.OnBuildProgressChanged(progress);

            EditorUtility.DisplayProgressBar("Building", "Building geometry from brushes", progress);
        }

        private void BreakMeshTest(Mesh mesh)
        {
            Vector3[] vertices = new Vector3[mesh.triangles.Length];
            Vector3[] normals = new Vector3[mesh.triangles.Length];
            Color32[] colors32 = new Color32[mesh.triangles.Length];
            Vector2[] uvs = new Vector2[mesh.triangles.Length];
            Vector4[] tangents = new Vector4[mesh.triangles.Length];
            int[] triangles = new int[mesh.triangles.Length];
            for (int i = 0; i < mesh.triangles.Length; i++)
            {
                int oldIndex = mesh.triangles[i];
                vertices[i] = mesh.vertices[oldIndex];
                normals[i] = mesh.normals[oldIndex];
                colors32[i] = mesh.colors32[oldIndex];
                uvs[i] = mesh.uv[oldIndex];
                tangents[i] = mesh.tangents[oldIndex];
                triangles[i] = i;
            }
            mesh.vertices = vertices;
            mesh.normals = normals;
            mesh.colors32 = colors32;
            mesh.uv = uvs;
            mesh.tangents = tangents;
            mesh.triangles = triangles;
        }

        public override void OnFinalizeVisualMesh(GameObject newGameObject, Mesh mesh)
        {
            base.OnFinalizeVisualMesh(newGameObject, mesh);

            if (buildSettings.GenerateLightmapUVs)
            {
                //				BreakMeshTest(mesh);

                // Create a copy of the mesh, which we can then unwrap
                Mesh meshCopy = new Mesh();

                meshCopy.vertices = mesh.vertices;
                meshCopy.normals = mesh.normals;
                meshCopy.colors32 = mesh.colors32;
                meshCopy.uv = mesh.uv;
                meshCopy.tangents = mesh.tangents;
                meshCopy.triangles = mesh.triangles;

                //				Vector2[] perTriangleUVs = UnityEditor.Unwrapping.GeneratePerTriangleUV(meshCopy);

                UnityEditor.UnwrapParam unwrapParameters = new UnityEditor.UnwrapParam()
                {
                    angleError = buildSettings.UnwrapAngleError,
                    areaError = buildSettings.UnwrapAreaError,
                    hardAngle = buildSettings.UnwrapHardAngle,
                    packMargin = buildSettings.UnwrapPackMargin,
                };

                // Unwrap the mesh copy, note that this also changes the vertex buffer, which is why we do it on a copy
                UnityEditor.Unwrapping.GenerateSecondaryUVSet(meshCopy, unwrapParameters);

                // Now transfer the unwrapped UVs to the original mesh, since the two vertex counts differ
                Vector2[] uv2 = new Vector2[mesh.vertices.Length];

                if (meshCopy.triangles.Length == mesh.triangles.Length)
                {
                    //					VisualDebug.ClearAll();
                    //					for (int i = 0; i < meshCopy.triangles.Length/3; i++)
                    //					{
                    //						int index1 = meshCopy.triangles[i*3 + 0];
                    //						int index2 = meshCopy.triangles[i*3 + 1];
                    //						int index3 = meshCopy.triangles[i*3 + 2];
                    ////						if(index1 == index2 || index2 == index3 || index1 == index3)
                    ////						{
                    ////							Debug.LogError("Degen found");
                    ////						}
                    //						VisualDebug.AddLinePolygon(new Vector3[]{
                    //							meshCopy.uv2[index1],
                    //							meshCopy.uv2[index2],
                    //							meshCopy.uv2[index3],
                    //						}, Color.white);
                    //					}

                    int triangleCount = mesh.triangles.Length;
                    for (int i = 0; i < triangleCount; i++)
                    {
                        int originalIndex = mesh.triangles[i];
                        int copyIndex = meshCopy.triangles[i];

                        //						if(mesh.vertices[originalIndex] != meshCopy.vertices[copyIndex])
                        //						{
                        //							Debug.LogError("Vertex mismatch found");
                        //						}
                        //
                        //						if(uv2[originalIndex] != Vector2.zero
                        //							&& uv2[originalIndex] != meshCopy.uv2[copyIndex])
                        //						{
                        //							Debug.Log("Overwriting " + uv2[originalIndex] + " with " + meshCopy.uv2[copyIndex]);
                        //						}
                        uv2[originalIndex] = meshCopy.uv2[copyIndex];
                    }

                    mesh.uv2 = uv2;

                    //					for (int i = 0; i < mesh.triangles.Length/3; i++)
                    //					{
                    //						int index1 = mesh.triangles[i*3 + 0];
                    //						int index2 = mesh.triangles[i*3 + 1];
                    //						int index3 = mesh.triangles[i*3 + 2];
                    ////						if(index1 == index2 || index2 == index3 || index1 == index3)
                    ////						{
                    ////							Debug.LogError("Degen found");
                    ////						}
                    //						VisualDebug.AddLinePolygon(new Vector3[]{
                    //							mesh.uv2[index1] + Vector2.right,
                    //							mesh.uv2[index2] + Vector2.right,
                    //							mesh.uv2[index3] + Vector2.right,
                    //						}, Color.blue);
                    //					}
                    //					for (int i = 0; i < uv2.Length/3; i++)
                    //					{
                    //						VisualDebug.AddLinePolygon(new Vector3[]{
                    //							uv2[i*3 + 0] + Vector2.right,
                    //							uv2[i*3 + 1] + Vector2.right,
                    //							uv2[i*3 + 2] + Vector2.right,
                    //						}, Color.blue);
                    //					}
                }
                else
                {
                    Debug.LogError("Unwrapped mesh triangle count mismatches source mesh");
                }

                //				GameObject.DestroyImmediate(meshCopy);
            }

            // Apply the model layer, tag and appropriate static flags
            ApplyModelAttributes(newGameObject, true);
        }

        public override void OnFinalizeCollisionMesh(GameObject newGameObject, Mesh mesh)
        {
            base.OnFinalizeCollisionMesh(newGameObject, mesh);

            // Apply the model layer, tag and appropriate static flags
            ApplyModelAttributes(newGameObject, false);
        }

        public Mesh GetMeshForMaterial(Material sourceMaterial, int fitVertices = 0)
        {
            if (materialMeshDictionary.Contains(sourceMaterial))
            {
                List<MaterialMeshDictionary.MeshObjectMapping> mappings = materialMeshDictionary[sourceMaterial];

                Mesh lastMesh = mappings[mappings.Count - 1].Mesh;

                if (lastMesh.vertices.Length + fitVertices < MESH_VERTEX_LIMIT)
                {
                    return lastMesh;
                }
            }

            Mesh mesh = new Mesh();

            materialMeshDictionary.Add(sourceMaterial, mesh, null);

            Material materialToApply = sourceMaterial;
            if (sourceMaterial == null)
            {
                materialToApply = GetDefaultMaterial();
            }

            GameObject newGameObject = CSGFactory.CreateMaterialMesh(this.transform, materialToApply, mesh);

            // Apply the model layer, tag and appropriate static flags
            ApplyModelAttributes(newGameObject, false);

            return mesh;
        }

        public Mesh GetMeshForCollision(int fitVertices = 0)
        {
            Mesh lastMesh = collisionMeshDictionary[collisionMeshDictionary.Count - 1];
            if (lastMesh.vertices.Length + fitVertices < MESH_VERTEX_LIMIT)
            {
                return lastMesh;
            }

            Mesh mesh = new Mesh();
            collisionMeshDictionary.Add(mesh);
            GameObject newGameObject = CSGFactory.CreateCollisionMesh(this.transform, mesh);

            // Apply the model layer, tag and appropriate static flags
            ApplyModelAttributes(newGameObject, false);

            return mesh;
        }

        private void ApplyModelAttributes(GameObject newGameObject, bool isVisual)
        {
            // Inherit any static flags from the CSG model
            UnityEditor.StaticEditorFlags rootStaticFlags = UnityEditor.GameObjectUtility.GetStaticEditorFlags(this.gameObject);
            UnityEditor.GameObjectUtility.SetStaticEditorFlags(newGameObject, rootStaticFlags);

            // Inherit the layer and tag from the CSG model
            newGameObject.layer = this.gameObject.layer;
            newGameObject.tag = this.gameObject.tag;

            // If the mesh is lightmap UV'd then make sure the object is lightmap static
            if (buildSettings.GenerateLightmapUVs)
            {
                UnityEditor.StaticEditorFlags staticFlags = UnityEditor.GameObjectUtility.GetStaticEditorFlags(newGameObject);
#if UNITY_2019_1_OR_NEWER
                staticFlags |= UnityEditor.StaticEditorFlags.ContributeGI;
#else
                staticFlags |= UnityEditor.StaticEditorFlags.LightmapStatic;
#endif
                UnityEditor.GameObjectUtility.SetStaticEditorFlags(newGameObject, staticFlags);
            }
        }

        public static CSGModel GetActiveCSGModel()
        {
            CSGModel[] csgModels = FindObjectsOfType<CSGModel>();

            // Return the first csg model that is currently being edited
            for (int i = 0; i < csgModels.Length; i++)
            {
                if (csgModels[i].EditMode)
                {
                    return csgModels[i];
                }
            }

            // None found
            return null;
        }

        private static void CleanupForBuild(Transform csgModelTransform)
        {
            Transform meshGroup = csgModelTransform.Find("MeshGroup");
            if (meshGroup != null)
            {
                // If the CSG Model is disabled we also disable the mesh group.
                meshGroup.gameObject.SetActive(csgModelTransform.gameObject.activeSelf);
                // Reanchor the meshes to the parent of the CSG Model
                meshGroup.SetParent(csgModelTransform.parent, true);
            }

            // find all built volumes:
            Transform[] volumes = csgModelTransform.FindChildren(Constants.GameObjectVolumeComponentIdentifier);
            for (int i = 0; i < volumes.Length; i++)
            {
                // make sure they are visible and editable again.
                volumes[i].gameObject.hideFlags = HideFlags.None;
                // give them a more recognizable name.
                volumes[i].name = volumes[i].parent.name.Replace(" Brush ", " Volume ");
                if (meshGroup != null)
                    // Reanchor the volumes to the mesh group.
                    volumes[i].SetParent(meshGroup, true);
                else
                    // Reanchor the volumes to the parent of the CSG Model
                    volumes[i].SetParent(csgModelTransform.parent, true);
            }

            // Remove the CSG Model and its brushes
            DestroyImmediate(csgModelTransform.gameObject);
        }

        [PostProcessScene(1)]
        public static void OnPostProcessScene()
        {
            CSGModel[] csgModels = Resources.FindObjectsOfTypeAll<CSGModel>();
            for (int i = 0; i < csgModels.Length; i++)
            {
                CleanupForBuild(csgModels[i].transform);
            }
        }

#if UNITY_EDITOR && (UNITY_5_0 || UNITY_5_1)
		void OnDrawGizmosSelected()
		{
			// Ensure Edit Mode is on
			EditMode = true;
            editModeModel = this;
		}
#endif
#endif
            }
    }

#endif
