Ejemplo n.º 1
0
        /// <summary>
        /// Recompute the pivot of a transform containing bricks.
        /// </summary>
        /// <param name="parent">The transform to recompute the pivot for</param>
        /// <param name="pivotType">The pivot type</param>
        /// <param name="alignRotation">Whether we also want to align rotation relative to bricks</param>
        /// <param name="undoBehavior">Whether or not to register undo for this action</param>
        public static void RecomputePivot(Transform parent, Model.Pivot pivotType = Model.Pivot.BottomCenter, bool alignRotation = true, UndoBehavior undoBehavior = UndoBehavior.withUndo)
        {
            if (pivotType == Model.Pivot.Original)
            {
                return;
            }

            var   bricks           = parent.GetComponentsInChildren <Brick>();
            Brick referenceBrick   = null;
            var   closestAngleToUp = 100000.0f;

            foreach (var brick in bricks)
            {
                var newAngle = Vector3.Angle(brick.transform.up, parent.up);
                if (newAngle < closestAngleToUp)
                {
                    closestAngleToUp = newAngle;
                    referenceBrick   = brick;
                }
            }
            if (!referenceBrick)
            {
                return;
            }

            var       oldRotations   = new List <Quaternion>();
            var       oldPositions   = new List <Vector3>();
            Matrix4x4 transformation = referenceBrick.transform.localToWorldMatrix.inverse;
            var       bounds         = BrickBuildingUtility.ComputeBounds(bricks, transformation);
            var       pivot          = bounds.center;

            if (pivotType == Model.Pivot.BottomCenter)
            {
                pivot += Vector3.down * bounds.extents.y;
            }

            pivot = referenceBrick.transform.TransformPoint(pivot);

            if (Vector3.Distance(parent.position, pivot) < 0.00001f)
            {
                return;
            }

            if (undoBehavior == UndoBehavior.withUndo)
            {
                var collectedTransforms = new List <Transform>();
                collectedTransforms.Add(parent);
                foreach (Transform child in parent)
                {
                    collectedTransforms.Add(child);
                }
                Undo.RegisterCompleteObjectUndo(collectedTransforms.ToArray(), "Recording groups before moving model group");
            }

            var difference = parent.position - pivot;

            parent.position = pivot;

            foreach (Transform child in parent)
            {
                child.transform.position += difference;

                if (alignRotation)
                {
                    oldRotations.Add(child.transform.rotation);
                    oldPositions.Add(child.transform.position);
                }
            }

            if (alignRotation)
            {
                var closest = MathUtils.FindClosestAxis(parent, referenceBrick.transform.up, out MathUtils.VectorDirection direction);
                var rot     = Quaternion.FromToRotation(closest, referenceBrick.transform.up);

                var forward = referenceBrick.transform.forward;
                var right   = referenceBrick.transform.right;

                var oldRot = parent.rotation;
                parent.rotation = rot * parent.rotation;

                var m = Matrix4x4.TRS(parent.position, Quaternion.identity, Vector3.one);
                m.SetColumn(0, forward);
                switch (direction)
                {
                case MathUtils.VectorDirection.up:
                    m.SetColumn(1, parent.up);
                    break;

                case MathUtils.VectorDirection.right:
                    m.SetColumn(1, parent.right);
                    break;

                case MathUtils.VectorDirection.forward:
                    m.SetColumn(1, parent.forward);
                    break;

                case MathUtils.VectorDirection.down:
                    m.SetColumn(1, -parent.up);
                    break;

                case MathUtils.VectorDirection.left:
                    m.SetColumn(1, -parent.right);
                    break;

                case MathUtils.VectorDirection.back:
                    m.SetColumn(1, -parent.forward);
                    break;
                }
                m.SetColumn(2, right);

                var related = MathUtils.GetRelatedAxes(parent, direction);
                rot = MathUtils.AlignRotation(new Vector3[] { related.Item1, related.Item2 }, m) * rot;
                rot.ToAngleAxis(out float angle, out Vector3 axis);
                parent.rotation = oldRot;
                parent.RotateAround(parent.position, axis, angle);

                var i = 0;
                foreach (Transform child in parent)
                {
                    child.transform.rotation = oldRotations[i];
                    child.transform.position = oldPositions[i++];
                }
            }
        }
Ejemplo n.º 2
0
        private void OnGUI()
        {
            // Find max label width.
            var maxLabelWidth = 110.0f;

            for (int i = 0; i < lxfml.groups.Length; i++)
            {
                var size = EditorStyles.boldLabel.CalcSize(new GUIContent(lxfml.groups[i].name));
                maxLabelWidth = Mathf.Max(maxLabelWidth, size.x);
            }

            minSize = new Vector2(leftMargin + maxLabelWidth + 360, 226);
            maxSize = new Vector2(leftMargin + maxLabelWidth + 360, 2000);

            scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, false);

            GUI.Box(new Rect(20, 20, 100, 100), logoTexture);

            CreateHeaderUI(new Vector2(leftMargin + maxLabelWidth + 20.0f, 100.0f), "Colliders", "Add colliders to bricks.");
            CreateHeaderUI(new Vector2(leftMargin + maxLabelWidth + 50.0f, 100.0f), "Connectivity", "Add connectivity to bricks. Connectivity requires colliders.");

            CreateHeaderUI(new Vector2(leftMargin + maxLabelWidth + 100.0f, 100.0f), "Static", "Make bricks static.");
            CreateHeaderUI(new Vector2(leftMargin + maxLabelWidth + 130.0f, 100.0f), "Lightmapped", "Add lightmap UVs to bricks. Bricks must be static to be lightmapped.");

            CreateHeaderUI(new Vector2(leftMargin + maxLabelWidth + 180.0f, 100.0f), "Randomize Rotation", "Slightly rotate bricks to improve realism.");
            CreateHeaderUI(new Vector2(leftMargin + maxLabelWidth + 230.0f, 100.0f), "Prefer Legacy", "Prefer legacy geometry over new geometry.");
            CreateHeaderUI(new Vector2(leftMargin + maxLabelWidth + 280.0f, 100.0f), "LOD", "LOD 0 includes chamfered edges.\nLOD 1 does not.\nLOD 2 simplifies knobs.");

            // Reserve the space for the GUILayout scroll view.
            GUILayout.Space(135.0f);
            var nextY = 135.0f;

            var showAllBoolsUI = model == null && group == null && lxfml.groups.Length > 1; // When importing a new model, just check if there is more than one lxfml group.

            showAllBoolsUI |= model != null && importSettings.Count > 1;                    // When reimporting an entire model, check if the existing model has more than one group.
            if (showAllBoolsUI)
            {
                CreateAllBoolsUI(new Vector2(leftMargin + maxLabelWidth + 15.0f, nextY), importSettings, "colliders", "connectivity", null);
                CreateAllBoolsUI(new Vector2(leftMargin + maxLabelWidth + 45.0f, nextY), importSettings, "connectivity", null, "colliders");

                CreateAllBoolsUI(new Vector2(leftMargin + maxLabelWidth + 95.0f, nextY), importSettings, "isStatic", "lightmapped", null);
                CreateAllBoolsUI(new Vector2(leftMargin + maxLabelWidth + 125.0f, nextY), importSettings, "lightmapped", null, "isStatic");

                CreateAllBoolsUI(new Vector2(leftMargin + maxLabelWidth + 175.0f, nextY), importSettings, "randomizeRotation");
                CreateAllBoolsUI(new Vector2(leftMargin + maxLabelWidth + 225.0f, nextY), importSettings, "preferLegacy");
                CreateAllLODsUI(new Vector2(leftMargin + maxLabelWidth + 275.0f, nextY), importSettings);

                // Reserve the space for the GUILayout scroll view.
                GUILayout.Space(25.0f);
                nextY += 25.0f;
            }

            var collidersOrConnectivityWhilePreferringLegacy = false;

            for (int i = 0; i < lxfml.groups.Length; i++)
            {
                var showGroup = group == null && model == null;              // When importing a new model, show all groups.
                showGroup |= model != null && importSettings.ContainsKey(i); // When reimporting an entire model, only show groups already in the existing model.
                showGroup |= group != null && i == group.number;             // When reimporting a model group, only show that group.
                if (showGroup)
                {
                    GUI.Label(new Rect(leftMargin, nextY, maxLabelWidth, 20.0f), lxfml.groups[i].name);

                    CreateBoolUI(new Vector2(leftMargin + maxLabelWidth + 15.0f, nextY), importSettings, "colliders", i, "connectivity", null);
                    CreateBoolUI(new Vector2(leftMargin + maxLabelWidth + 45.0f, nextY), importSettings, "connectivity", i, null, "colliders");

                    CreateBoolUI(new Vector2(leftMargin + maxLabelWidth + 95.0f, nextY), importSettings, "isStatic", i, "lightmapped", null);
                    CreateBoolUI(new Vector2(leftMargin + maxLabelWidth + 125.0f, nextY), importSettings, "lightmapped", i, null, "isStatic");

                    CreateBoolUI(new Vector2(leftMargin + maxLabelWidth + 175.0f, nextY), importSettings, "randomizeRotation", i);
                    CreateBoolUI(new Vector2(leftMargin + maxLabelWidth + 225.0f, nextY), importSettings, "preferLegacy", i);
                    CreateLODUI(new Vector2(leftMargin + maxLabelWidth + 275.0f, nextY), importSettings, i);

                    if ((importSettings[i].colliders || importSettings[i].connectivity) && importSettings[i].preferLegacy)
                    {
                        collidersOrConnectivityWhilePreferringLegacy = true;
                    }

                    // Reserve the space for the GUILayout scroll view.
                    GUILayout.Space(20.0f);
                    nextY += 20.0f;
                }
            }

            if (collidersOrConnectivityWhilePreferringLegacy)
            {
                EditorGUI.HelpBox(new Rect(leftMargin, nextY, position.width - leftMargin - 20.0f, 38.0f), "Legacy parts might not contain colliders or connectivity information.", MessageType.Warning);
                // Reserve the space for the GUILayout scroll view.
                GUILayout.Space(42.0f);
                nextY += 42.0f;
            }

            // Reserve the space for the GUILayout scroll view.
            GUILayout.Space(5.0f);
            nextY += 5.0f;

            // Only show pivot option when not reimporting group.
            if (group == null)
            {
                GUI.Label(new Rect(leftMargin, nextY, maxLabelWidth, 20.0f), "Pivot");
                pivot = (Model.Pivot)EditorGUI.EnumPopup(new Rect(leftMargin + maxLabelWidth + 15.0f, nextY, 126.0f, 16.0f), pivot);

                // Reserve the space for the GUILayout scroll view.
                GUILayout.Space(25.0f);
                nextY += 25.0f;
            }

            // Create the right import/reimport button and handle the import/reimport based on the three cases:
            // - Reimport model
            // - Reimport model group
            // - Import model
            bool importPressed;

            if (model)
            {
                // ----------------------
                // Reimport entire model.
                // ----------------------
                importPressed = GUI.Button(new Rect(leftMargin, nextY, maxLabelWidth + 15.0f + 126.0f, 32.0f), "Reimport Model");

                if (importPressed)
                {
                    // Register undo.
                    Undo.RegisterFullObjectHierarchyUndo(model.gameObject, "Reimport");
                    var oldPivot = model.pivot;
                    model.pivot = pivot;

                    // Update the path if it is new.
                    if (filePath != null)
                    {
                        model.absoluteFilePath = filePath;
                        model.relativeFilePath = PathUtils.GetRelativePath(Directory.GetCurrentDirectory(), filePath);
                    }

                    ModelImporter.ReimportModel(lxfml, model, oldPivot, importSettings);

                    var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
                    if (prefabStage != null)
                    {
                        EditorSceneManager.MarkSceneDirty(prefabStage.scene);
                    }
                    SceneBrickBuilder.MarkSceneDirty();
                }
            }
            else if (group)
            {
                // ---------------------
                // Reimport model group.
                // ---------------------
                importPressed = GUI.Button(new Rect(leftMargin, nextY, maxLabelWidth + 15.0f + 126.0f, 32.0f), "Reimport Model Group");

                if (importPressed)
                {
                    // Register undo.
                    Undo.RegisterFullObjectHierarchyUndo(group.gameObject, "Reimport");

                    ModelImporter.ReimportModelGroup(lxfml, group, importSettings[group.number], true);

                    var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
                    if (prefabStage != null)
                    {
                        EditorSceneManager.MarkSceneDirty(prefabStage.scene);
                    }
                    SceneBrickBuilder.MarkSceneDirty();
                }
            }
            else
            {
                // ----------------------
                // Import model.
                // ----------------------
                importPressed = GUI.Button(new Rect(leftMargin, nextY, maxLabelWidth + 15.0f + 126.0f, 32.0f), "Import Model");

                if (importPressed)
                {
                    // Check for connectivity updates.
                    ConnectivityVersionChecker.CheckForUpdates();

                    model = ModelImporter.InstantiateModel(lxfml, filePath, pivot, importSettings).GetComponent <Model>();
                    var camera = SceneView.lastActiveSceneView.camera;
                    if (camera)
                    {
                        var cameraRay = new Ray(camera.transform.position, camera.transform.forward);

                        var bricksInModel = model.GetComponentsInChildren <Brick>();
                        var bricks        = new HashSet <Brick>(bricksInModel);
                        var sourceBrick   = bricks.First();

                        BrickBuildingUtility.AlignBricks(sourceBrick, bricks, BrickBuildingUtility.ComputeBounds(bricks), sourceBrick.transform.position, Vector3.zero, cameraRay,
                                                         new Plane(Vector3.up, Vector3.zero), 100.0f, out Vector3 offset, out Vector3 alignedOffset, out _, out _);

                        var offsetPosition = model.transform.position + alignedOffset;

                        model.transform.position = offsetPosition;

                        Selection.activeGameObject = model.gameObject;
                        Physics.SyncTransforms();
                    }
                    SceneBrickBuilder.SyncAndUpdateBrickCollision(true);
                }
            }

            // Reserve the space for the GUILayout scroll view.
            GUILayout.Space(36.0f);
            nextY += 36.0f;

            // List tracked errors.
            foreach (var trackedError in trackedErrors)
            {
                EditorGUI.HelpBox(new Rect(leftMargin, nextY, maxLabelWidth + 340.0f, 38.0f), trackedError.Key, MessageType.Warning);
                // Reserve the space for the GUILayout scroll view.
                GUILayout.Space(42.0f);
                nextY += 42.0f;

                foreach (var id in trackedError.Value)
                {
                    GUI.Label(new Rect(leftMargin, nextY, maxLabelWidth + 340.0f, 16.0f), id);
                    // Reserve the space for the GUILayout scroll view.
                    GUILayout.Space(20.0f);
                    nextY += 20.0f;
                }
            }

            GUILayout.EndScrollView();

            if (importPressed)
            {
                this.Close();
            }
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Recompute the model and model group hierarchy.
        /// Some rules:
        /// 1. A brick will always be in a model group, which will always be in a model.
        /// 2. Empty models and model groups are destroyed.
        /// 3. Bricks can not contain bricks, model groups can not contain model groups and models can not contain models. So they are flattened.
        /// </summary>
        /// <param name="bricks">The bricks that we need to check the hierarchy for</param>
        /// <param name="alignRotation">Whether or not we also align rotation when we recompute pivot</param>
        /// <param name="undoBehavior">Whether or not to register undo for the recomputation of hierarchy</param>
        public static void RecomputeHierarchy(IEnumerable <Brick> bricks, bool alignRotation = true, UndoBehavior undoBehavior = UndoBehavior.withUndo)
        {
            // Group numbers are numbers in parentheses
            // So group 1 with three bricks is shown as (1, 1, 1)
            // In case the group is part of a prefab it is suffixed with a p like (1p, 1p)
            // In case one of the bricks in a group is an override it is noted with a + as in (1p, 1p+)

            // Cases for splitting:
            // (1, 1) -> (1) (1)
            // (1, 1) (2) -> (1) (1, 2) -> (1) (2, 2)

            // Cases for unpacking:
            // (1p) (2p) -> (1, 2p) -> (2p+, 2p)
            // (1p, 1p+) (2p) -> (1, 1, 2p) -> (2p+, 2p+, 2p)
            // (1p, 1p) -> (1p) (1p) -> (1) (1)

            // Only set parent:
            // (1) (2) -> (1, 2) -> (2, 2)
            // (1p, 1p+) (2p) -> (1p) (1p+, 2p) -> (1p) (2p+, 2p)
            // (1p, 1p+) (2) -> (1p) (1p+, 2) -> (1p) (2, 2)

            if (PrefabStageUtility.GetCurrentPrefabStage() != null)
            {
                var rootObject = PrefabStageUtility.GetCurrentPrefabStage().prefabContentsRoot;
                var brick      = rootObject.GetComponent <Brick>();
                if (brick)
                {
                    return;
                }
            }

            // First we flatten models
            var modelsToCheck = new HashSet <Model>();
            var groupsToCheck = new HashSet <ModelGroup>();
            var bricksToCheck = new HashSet <Brick>();

            foreach (var brick in bricks)
            {
                var bricksInParent = brick.GetComponentsInParent <Brick>(true);
                if (bricksInParent.Length > 1)
                {
                    bricksToCheck.Add(brick);
                }
                var modelsInParent = brick.GetComponentsInParent <Model>(true);
                if (modelsInParent.Length > 1)
                {
                    modelsToCheck.UnionWith(modelsInParent);
                }
                var groupsInParent = brick.GetComponentsInParent <ModelGroup>(true);
                if (groupsInParent.Length > 1)
                {
                    groupsToCheck.UnionWith(groupsInParent);
                }
            }

            foreach (var model in modelsToCheck)
            {
                var modelsInModel = model.GetComponentsInChildren <Model>(true);
                foreach (var inModel in modelsInModel)
                {
                    if (inModel == model)
                    {
                        continue;
                    }

                    var groupsInModel = inModel.GetComponentsInChildren <ModelGroup>(true);
                    foreach (var group in groupsInModel)
                    {
                        SetParent(group.transform, model.transform, undoBehavior);
                    }
                }
            }

            // Now flatten groups
            foreach (var group in groupsToCheck)
            {
                var groupsInGroup = group.GetComponentsInChildren <ModelGroup>(true);
                foreach (var inGroup in groupsInGroup)
                {
                    if (inGroup == group)
                    {
                        continue;
                    }

                    var bricksInGroup = inGroup.GetComponentsInChildren <Brick>(true);
                    foreach (var brick in bricksInGroup)
                    {
                        SetParent(brick.transform, group.transform, undoBehavior);
                    }
                }
            }

            // Now flatten bricks
            foreach (var brick in bricksToCheck)
            {
                var group = GetGroupInParent(brick.transform);
                if (group)
                {
                    SetParent(brick.transform, group.transform, undoBehavior);
                }
            }

            var connectedClusters = new List <HashSet <Brick> >();

            // Collect all connected brick lists
            foreach (var brick in bricks)
            {
                if (connectedClusters.Any(x => x.Contains(brick)))
                {
                    continue;
                }

                if (!brick.HasConnectivity())
                {
                    var group = GetGroupInParent(brick.transform);
                    if (!group)
                    {
                        connectedClusters.Add(new HashSet <Brick> {
                            brick
                        });
                    }
                    else
                    {
                        var model = GetModelInParent(brick.transform);
                        if (!model)
                        {
                            var bricksInGroup = group.GetComponentsInChildren <Brick>(true);
                            var connected     = new HashSet <Brick>();
                            connected.Add(brick);
                            connectedClusters.Add(connected);
                        }
                    }
                }
                else
                {
                    var connected = brick.GetConnectedBricks();
                    connected.Add(brick);
                    connectedClusters.Add(connected);
                }
            }

            // Now find all groups for each cluster
            var groupsPerCluster = new List <(HashSet <Brick>, HashSet <ModelGroup>, HashSet <Brick>)>();

            foreach (var cluster in connectedClusters)
            {
                if (cluster.Count == 0)
                {
                    continue;
                }

                var bricksNotInGroup = new HashSet <Brick>();
                var groups           = new HashSet <ModelGroup>();
                foreach (var brick in cluster)
                {
                    var group = GetGroupInParent(brick.transform);
                    if (group)
                    {
                        groups.Add(group);
                    }
                    else
                    {
                        bricksNotInGroup.Add(brick);
                    }
                }
                groupsPerCluster.Add((cluster, groups, bricksNotInGroup));
            }

            // Sorting makes sure we merge before we split. Merging will make it easier to see what we need to split later.
            groupsPerCluster = groupsPerCluster.OrderByDescending(x => x.Item2.Count).ToList();

            // Check through each of these groups in the cluster
            foreach (var groupPerCluster in groupsPerCluster)
            {
                var cluster    = groupPerCluster.Item1;
                var groups     = groupPerCluster.Item2;
                var notInGroup = groupPerCluster.Item3;

                // If the cluster has more than one group, we need to merge them
                if (groups.Count > 1)
                {
                    // Merge some groups
                    ModelGroup largestGroup     = null;
                    int        largestGroupSize = 0;
                    foreach (var group in groups)
                    {
                        var bricksInGroup   = group.GetComponentsInChildren <Brick>(true);
                        var bricksInCluster = bricksInGroup.Count(x => cluster.Contains(x));
                        if (bricksInCluster >= largestGroupSize)
                        {
                            largestGroup     = group;
                            largestGroupSize = bricksInCluster;
                        }
                    }

                    foreach (var brick in cluster)
                    {
                        if (brick.transform.parent == largestGroup.transform)
                        {
                            continue;
                        }

                        if (IsPartOfPrefab(brick))
                        {
                            UnpackPrefab(brick, undoBehavior);
                        }
                        SetParent(brick.transform, largestGroup.transform, undoBehavior);
                    }

                    RecomputePivot(largestGroup, alignRotation, undoBehavior);
                    var modelGO = largestGroup.transform.parent;
                    var model   = modelGO.GetComponent <Model>();
                    if (model)
                    {
                        RecomputePivot(model, alignRotation, undoBehavior);
                    }
                }
                else if (groups.Count == 1) // In case the cluster only has one group, we need to check if the group contains bricks not in this cluster
                {
                    var group = groups.FirstOrDefault();
                    if (!group)
                    {
                        continue;
                    }

                    var bricksInGroup    = group.GetComponentsInChildren <Brick>(true);
                    var clustersForGroup = new HashSet <HashSet <Brick> >();

                    // If this group contains more than one cluster, split
                    foreach (var brick in bricksInGroup)
                    {
                        if (!clustersForGroup.Any(x => x.Contains(brick)))
                        {
                            if (brick.HasConnectivity())
                            {
                                var connected = brick.GetConnectedBricks();
                                connected.Add(brick);
                                clustersForGroup.Add(connected);
                            }
                        }
                    }

                    // Only split if we found multiple clusters in group
                    if (clustersForGroup.Count > 1)
                    {
                        // Get the model for the group
                        var model = GetModelInParent(group.transform);

                        // Find all prefabs we need to unpack by looking through the clusters in the group
                        foreach (var clusterInGroup in clustersForGroup)
                        {
                            // Look through each brick in the cluster
                            foreach (var brick in clusterInGroup)
                            {
                                // First check if there is a brick in this cluster that is part of a prefab and not an override
                                // If there is, then check if there is another cluster containing a prefab that is not an override
                                // In that case, we have to unpack, because we are changing the parents of gameobjects in a prefab
                                if (IsPartOfPrefab(brick))
                                {
                                    if (clustersForGroup.Any(clust => clust != clusterInGroup && clust.Any(obj => !PrefabUtility.IsAddedGameObjectOverride(obj.gameObject))))
                                    {
                                        UnpackPrefab(brick, undoBehavior);
                                        break;
                                    }
                                }
                            }
                        }

                        // Optimization: Check for largest group containing only a single cluster and keep that group intact
                        HashSet <Brick> largestGroupBricks   = null;
                        ModelGroup      largestGroup         = null;
                        int             largestGroupSize     = 0;
                        var             sharedParentClusters = new HashSet <ModelGroup>();

                        foreach (var clusterInGroup in clustersForGroup)
                        {
                            var parent     = clusterInGroup.First().transform.parent;
                            var modelGroup = parent.GetComponent <ModelGroup>();
                            if (!modelGroup)
                            {
                                continue;
                            }

                            var skip = false;
                            foreach (var brick in clusterInGroup)
                            {
                                if (brick.transform.parent != parent)
                                {
                                    skip = true;
                                    break;
                                }
                            }
                            if (skip)
                            {
                                continue;
                            }

                            if (largestGroupSize < clusterInGroup.Count())
                            {
                                largestGroupSize   = clusterInGroup.Count();
                                largestGroupBricks = clusterInGroup;
                                largestGroup       = modelGroup;
                            }
                        }

                        if (largestGroupBricks != null)
                        {
                            clustersForGroup.Remove(largestGroupBricks);
                        }

                        foreach (var clusterInGroup in clustersForGroup)
                        {
                            var newObject = new GameObject();
                            var newGroup  = newObject.AddComponent <ModelGroup>();

                            // Add LEGOModelGroupAsset component.
                            newObject.AddComponent <LEGOModelGroupAsset>();

                            if (undoBehavior == UndoBehavior.withUndo)
                            {
                                Undo.RegisterCreatedObjectUndo(newObject, "Created new group");
                            }

                            newGroup.transform.position = group.transform.position;

                            if (model)
                            {
                                SetParent(newGroup.transform, model.transform, undoBehavior);
                            }

                            newGroup.name             = group.groupName;
                            newGroup.groupName        = group.groupName;
                            newGroup.parentName       = group.parentName;
                            newGroup.optimizations    = group.optimizations;
                            newGroup.randomizeNormals = group.randomizeNormals;
                            foreach (var view in group.views)
                            {
                                newGroup.views.Add(new CullingCameraConfig()
                                {
                                    name        = view.name,
                                    perspective = view.perspective,
                                    position    = view.position,
                                    rotation    = view.rotation,
                                    fov         = view.fov,
                                    size        = view.size,
                                    minRange    = view.minRange,
                                    maxRange    = view.maxRange,
                                    aspect      = view.aspect
                                });
                            }
                            newGroup.autoGenerated = true;

                            foreach (var brick in clusterInGroup)
                            {
                                SetParent(brick.transform, newGroup.transform, undoBehavior);
                            }
                            RecomputePivot(newGroup, alignRotation, undoBehavior);
                        }

                        if (model)
                        {
                            RecomputePivot(model, alignRotation, undoBehavior);
                        }
                    }
                    else if (notInGroup.Count > 0)
                    {
                        foreach (var brick in notInGroup)
                        {
                            if (IsPartOfPrefab(brick))
                            {
                                UnpackPrefab(brick, undoBehavior);
                            }
                            SetParent(brick.transform, group.transform, undoBehavior);
                        }
                    }
                    else
                    {
                        RecomputePivot(group, alignRotation, undoBehavior);

                        var   modelGO        = group.transform.parent;
                        Model model          = null;
                        bool  createNewModel = (PrefabStageUtility.GetCurrentPrefabStage() != null && modelGO && !modelGO.GetComponent <Model>()) ||
                                               (PrefabStageUtility.GetCurrentPrefabStage() == null && (!modelGO || !modelGO.GetComponent <Model>()));
                        if (createNewModel)
                        {
                            model = CreateNewDefaultModel(group.name, undoBehavior);
                            if (modelGO != null)
                            {
                                if (PrefabStageUtility.GetCurrentPrefabStage() == null && PrefabUtility.IsPartOfAnyPrefab(modelGO))
                                {
                                    UnpackPrefab(modelGO.gameObject, undoBehavior);
                                }
                                SetParent(model.transform, modelGO.transform, undoBehavior);
                            }
                            SetParent(group.transform, model.transform, undoBehavior);
                            EditorGUIUtility.PingObject(group.gameObject);
                        }
                        else
                        {
                            model = modelGO.GetComponent <Model>();
                        }

                        if (model)
                        {
                            RecomputePivot(model, alignRotation, undoBehavior);
                        }
                    }
                }
                else // No groups.
                {
                    var   name  = cluster.FirstOrDefault()?.name;
                    Model model = null;

                    if (PrefabStageUtility.GetCurrentPrefabStage() != null)
                    {
                        var rootObject = PrefabStageUtility.GetCurrentPrefabStage().prefabContentsRoot;
                        model = rootObject.GetComponent <Model>();
                        if (!model)
                        {
                            model = CreateNewDefaultModel(name);
                            SetParent(model.transform, rootObject.transform, undoBehavior);
                        }
                    }
                    else
                    {
                        model = CreateNewDefaultModel(name);
                    }

                    ModelGroup newGroup = CreateNewDefaultModelGroup(name);

                    SetParent(newGroup.transform, model.transform, undoBehavior);
                    var bounds = BrickBuildingUtility.ComputeBounds(cluster, Matrix4x4.identity);
                    model.transform.position = new Vector3(bounds.center.x, bounds.min.y, bounds.center.z);

                    Transform originalParent = null;
                    foreach (var brick in cluster)
                    {
                        if (!originalParent)
                        {
                            originalParent = brick.transform.parent;
                        }
                        if (brick.transform.parent != originalParent)
                        {
                            originalParent = null;
                            break;
                        }
                    }

                    if (originalParent)
                    {
                        SetParent(model.transform, originalParent, undoBehavior);
                    }

                    foreach (var brick in cluster)
                    {
                        SetParent(brick.transform, newGroup.transform, undoBehavior);
                        EditorGUIUtility.PingObject(brick.gameObject);
                    }
                }
            }

            var modelsInScene = StageUtility.GetCurrentStageHandle().FindComponentsOfType <Model>();

            foreach (var model in modelsInScene)
            {
                var children = model.GetComponentsInChildren <Brick>(true);
                if (children.Length == 0)
                {
                    if (undoBehavior == UndoBehavior.withUndo)
                    {
                        Undo.DestroyObjectImmediate(model.gameObject);
                    }
                    else
                    {
                        Object.DestroyImmediate(model.gameObject);
                    }
                }
            }

            var groupsInScene = StageUtility.GetCurrentStageHandle().FindComponentsOfType <ModelGroup>();

            foreach (var group in groupsInScene)
            {
                var children = group.GetComponentsInChildren <Brick>(true);
                if (children.Length == 0)
                {
                    if (undoBehavior == UndoBehavior.withUndo)
                    {
                        Undo.DestroyObjectImmediate(group.gameObject);
                    }
                    else
                    {
                        Object.DestroyImmediate(group.gameObject);
                    }
                }
            }
        }
        public static void RecomputePivot(Transform parent, Model.Pivot pivotType = Model.Pivot.BottomCenter, bool alignRotation = true, UndoBehavior undoBehavior = UndoBehavior.withUndo)
        {
            if (pivotType == Model.Pivot.Original)
            {
                return;
            }

            var bricks         = parent.GetComponentsInChildren <Brick>();
            var referenceBrick = bricks.FirstOrDefault();

            if (!referenceBrick)
            {
                return;
            }

            var       oldRotations   = new List <Quaternion>();
            var       oldPositions   = new List <Vector3>();
            Matrix4x4 transformation = referenceBrick.transform.localToWorldMatrix.inverse;
            var       bounds         = BrickBuildingUtility.ComputeBounds(bricks, transformation);
            var       pivot          = bounds.center;

            if (pivotType == Model.Pivot.BottomCenter)
            {
                pivot += Vector3.down * bounds.extents.y;
            }

            pivot = referenceBrick.transform.TransformPoint(pivot);

            if (Vector3.Distance(parent.position, pivot) < float.Epsilon)
            {
                return;
            }

            if (undoBehavior == UndoBehavior.withUndo)
            {
                var collectedTransforms = new List <Transform>();
                collectedTransforms.Add(parent);
                for (var i = 0; i < parent.childCount; i++)
                {
                    var child = parent.GetChild(i);
                    collectedTransforms.Add(child);
                }
                Undo.RegisterCompleteObjectUndo(collectedTransforms.ToArray(), "Recording groups before moving model group");
            }

            var difference = parent.position - pivot;

            parent.position = pivot;

            for (var i = 0; i < parent.childCount; i++)
            {
                var child = parent.GetChild(i);
                child.transform.position += difference;

                if (alignRotation)
                {
                    oldRotations.Add(child.transform.rotation);
                    oldPositions.Add(child.transform.position);
                }
            }

            if (alignRotation)
            {
                var rot = Quaternion.FromToRotation(parent.up, referenceBrick.transform.up);

                var forward = referenceBrick.transform.forward;
                var right   = referenceBrick.transform.right;

                var oldRot = parent.rotation;
                parent.rotation = rot * parent.rotation;

                var m = Matrix4x4.TRS(parent.position, Quaternion.identity, Vector3.one);
                m.SetColumn(0, forward);
                m.SetColumn(1, parent.up);
                m.SetColumn(2, right);

                rot = MathUtils.AlignRotation(new Vector3[] { parent.right, parent.forward }, m) * rot;
                rot.ToAngleAxis(out float angle, out Vector3 axis);
                parent.rotation = oldRot;
                parent.RotateAround(parent.position, axis, angle);

                for (var i = 0; i < parent.childCount; i++)
                {
                    var child = parent.GetChild(i);
                    child.transform.rotation = oldRotations[i];
                    child.transform.position = oldPositions[i];
                }
            }
        }