/// <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++]; } } }
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(); } }
/// <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]; } } }