Beispiel #1
0
        public static void ReimportModelGroup(LXFMLDoc lxfml, ModelGroup group, ModelGroupImportSettings importSettings, bool detectConnectivity = false)
        {
            // Assign the new group import settings to the group.
            group.importSettings = importSettings;

            // We assume that the group can be found, so reimport it.
            if (group.processed)
            {
                // Remove all processed meshes.
                var renderers = group.GetComponentsInChildren <MeshRenderer>();
                foreach (var renderer in renderers)
                {
                    // FIXME Destroy the mesh? Prevents undo..
                    var filter = renderer.GetComponent <MeshFilter>();
                    //Undo.DestroyObjectImmediate(filter.sharedMesh);

                    if (renderer.GetComponent <ModelGroup>() == null)
                    {
                        // Destroy submesh game objects entirely.
                        Undo.DestroyObjectImmediate(renderer.gameObject);
                    }
                    else
                    {
                        // Destroy mesh related components on group game object.
                        Object.DestroyImmediate(filter);
                        Object.DestroyImmediate(renderer);
                    }
                }
            }

            // FIXME Check if bricks are referenced.
            // FIXME Check if bricks have custom components attached.

            // Remove group bricks.
            var existingBricks = group.GetComponentsInChildren <Brick>();

            foreach (var brick in existingBricks)
            {
                Undo.DestroyObjectImmediate(brick.gameObject);
            }

            var groupLightMapped = group.importSettings.isStatic && group.importSettings.lightmapped;

            SetStaticAndGIParams(group.gameObject, group.importSettings.isStatic, groupLightMapped);

            // Move group to origo to ensure that bricks are instantiated in the correct positions.
            var originalGroupLocalPosition = group.transform.localPosition;
            var originalGroupLocalRotation = group.transform.localRotation;
            var originalGroupLocalScale    = group.transform.localScale;
            var originalGroupParent        = group.transform.parent;
            var originalGroupSiblingIndex  = group.transform.GetSiblingIndex();

            group.transform.SetParent(null);
            group.transform.localPosition = Vector3.zero;
            group.transform.localRotation = Quaternion.identity;
            group.transform.localScale    = Vector3.one;

            // Create dictionary with just this group.
            var modelGroupImportSettingsDictionary = new DictionaryIntToModelGroupImportSettings();

            modelGroupImportSettingsDictionary.Add(group.number, group.importSettings);

            // Instantiate group bricks.
            var resultBricks = new Dictionary <int, Brick>(lxfml.bricks.Count);

            InstantiateModelBricks(lxfml, modelGroupImportSettingsDictionary, ref resultBricks, group.number);

            // Assign bricks to group.
            foreach (var brick in resultBricks.Values)
            {
                brick.transform.SetParent(group.transform);
            }
            ModelGroupUtility.RecomputePivot(group, false, ModelGroupUtility.UndoBehavior.withoutUndo);

            // Move group back to original location.
            group.transform.SetParent(originalGroupParent);
            group.transform.SetSiblingIndex(originalGroupSiblingIndex);
            group.transform.localPosition = originalGroupLocalPosition;
            group.transform.localRotation = originalGroupLocalRotation;
            group.transform.localScale    = originalGroupLocalScale;

            /*if (group.processed)
             * {
             *  // Process the group again.
             *  // FIXME Is this even a good idea?
             *  if (group.type == GroupType.Environment || group.type == GroupType.Static)
             *  {
             *      Vector2Int vertCount = Vector2Int.zero;
             *      Vector2Int triCount = Vector2Int.zero;
             *      Vector2Int meshCount = Vector2Int.zero;
             *      Vector2Int colliderCount = Vector2Int.zero;
             *      ModelProcessor.ProcessModelGroup(group, ref vertCount, ref triCount, ref meshCount, ref colliderCount);
             *
             *      Debug.Log($"Process result (before/after):\nVerts {vertCount.x}/{vertCount.y}, tris {triCount.x}/{triCount.y}, meshes {meshCount.x}/{meshCount.y}, colliders {colliderCount.x}/{colliderCount.y}");
             *  }
             * }*/

            if (detectConnectivity && group.importSettings.connectivity)
            {
                var sceneBricks = new HashSet <Brick>(StageUtility.GetCurrentStageHandle().FindComponentsOfType <Brick>());
                DetectConnectivity(new HashSet <Brick>(resultBricks.Values), sceneBricks);
            }

            EditorUtility.ClearProgressBar();
        }
        public static void ProcessModelGroup(ModelGroup group, ref Vector2Int vertCount, ref Vector2Int triCount, ref Vector2Int meshCount, ref Vector2Int boxColliderCount)
        {
            bool collapseMesh = true;
            bool collapseCols = group.importSettings.colliders;

            // Keep track of how many of the removable meshes are left for each part.
            var partMeshCount = new Dictionary <Part, int>();
            // Keep track of how many of the removable surface meshes (shell, colourChangeSurface) are left for each non-legacy part.
            var partSurfaceMeshCount = new Dictionary <Part, int>();

            // TODO: Move orphaned colliders to group root

            var progress = 0;

            if (collapseMesh)
            {
                //    Debug.Log("Collapsing Mesh for "+group.name);

                // Optimization settings
                bool doSort = group.optimizations.HasFlag(ModelGroup.Optimizations.SortFrontToBack);
                bool canRemoveTubesAndPins        = group.optimizations.HasFlag(ModelGroup.Optimizations.RemoveTubesAndPins);
                bool canRemoveKnobs               = group.optimizations.HasFlag(ModelGroup.Optimizations.RemoveKnobs);
                bool canRemoveCompletelyInvisible = group.optimizations.HasFlag(ModelGroup.Optimizations.RemoveInvisible);
                bool cullBackfaces = group.optimizations.HasFlag(ModelGroup.Optimizations.BackfaceCulling);

                bool randomizeNormals = group.randomizeNormals;

                // Split into different meshes depending on whether they have knobs or are transparent.
                // 0 = No knobs = Fast track to avoid normal mapping when it isn't required
                // 1 = Knobs = Slow track needs normal mapping
                // 2 = Transparent No Knobs = Fast track to avoid normal mapping when it isn't required.
                // 3 = Transparent Knobs = Slow track since we will not render transparent bricks during visibility tests.

                MeshHelper[] meshHelpers = new MeshHelper[] { new MeshHelper(), new MeshHelper(), new MeshHelper(), new MeshHelper() };

                Matrix4x4 groupMatrix    = group.transform.localToWorldMatrix;
                Matrix4x4 groupMatrixInv = groupMatrix.inverse;

                // Collect all parts.
                var parts = group.GetComponentsInChildren <Part>();

                // Collect relevant part mesh renderers along with mesh type information and a randomized rotation for each part.
                List <PartMeshRenderer> mrs = new List <PartMeshRenderer>();

                foreach (var part in parts)
                {
                    if (progress++ % 200 == 0)
                    {
                        EditorUtility.DisplayProgressBar("Processing", "Collecting part renderers.", ((float)progress / parts.Length) * 0.05f);
                    }

                    partMeshCount[part] = 0;

                    if (part.legacy)
                    {
                        // Legacy parts only have unstructured meshes.
                        foreach (var renderer in part.GetComponentsInChildren <MeshRenderer>(true))
                        {
                            var partMeshRenderer = new PartMeshRenderer()
                            {
                                part       = part,
                                rendererer = renderer,
                                type       = MeshType.Legacy
                            };
                            mrs.Add(partMeshRenderer);
                            partMeshCount[part]++;
                        }
                    }
                    else
                    {
                        partSurfaceMeshCount[part] = 0;

                        foreach (var renderer in part.GetComponentsInChildren <MeshRenderer>(true))
                        {
                            var parentName = renderer.transform.parent.name;

                            // First check if it is a decoration surface since we will not include them in the processing.
                            if (parentName == "DecorationSurfaces")
                            {
                                continue;
                            }

                            var partMeshRenderer = new PartMeshRenderer()
                            {
                                part       = part,
                                rendererer = renderer.GetComponent <MeshRenderer>()
                            };
                            if (parentName == "Knobs") // Is it a knob?
                            {
                                partMeshRenderer.type = MeshType.Knob;
                            }
                            else if (parentName == "Tubes") // Is it a tube?
                            {
                                partMeshRenderer.type = MeshType.Tube;
                            }
                            else if (parentName == "ColourChangeSurfaces") // Is it a colour change surface?
                            {
                                partMeshRenderer.type = MeshType.ColourChange;
                                partSurfaceMeshCount[part]++;
                            }
                            else // It must be the shell.
                            {
                                partMeshRenderer.type = MeshType.Shell;
                                partSurfaceMeshCount[part]++;
                            }

                            mrs.Add(partMeshRenderer);
                            partMeshCount[part]++;
                        }
                    }
                }

                Vector3 camPos = Vector3.zero;
                Vector3 camDir = Vector3.zero;

                if (group.views.Count == 0 && Camera.main)
                {
                    camPos = Camera.main.transform.position;
                    camDir = Camera.main.transform.forward;
                }
                else if (group.views.Count > 0)
                {
                    camPos = group.views[0].position;
                    camDir = group.views[0].rotation * Vector3.forward;
                }
                else
                {
                    doSort = false;
                    Debug.LogError("No views specified for front-to-back geometry sorting. Disabling!");
                }

                if (doSort)
                {
                    Vector3 sortDir;
                    if (group.views.Count == 0)
                    {
                        sortDir = camDir;
                    }
                    else
                    {
                        sortDir = group.views[0].rotation * Vector3.forward;
                    }

                    mrs.Sort(delegate(PartMeshRenderer a, PartMeshRenderer b)
                    {
                        float dA = Vector3.Dot(a.rendererer.bounds.center, sortDir);
                        float dB = Vector3.Dot(b.rendererer.bounds.center, sortDir);
                        return(dA.CompareTo(dB));
                    });
                }


                // Multiple passes
                // - Collect all meshes
                // - Remove original meshfilters and renderers
                // - Determine which Optimization level can be used (for legacy meshes only) or if mesh can be discarded completely
                // - Remove backfaces
                // - Build combined mesh

                // Collect meshes
                List <MeshInstance> instances = new List <MeshInstance>();

                for (int i = 0; i < mrs.Count; ++i)
                {
                    if (i % 200 == 0)
                    {
                        EditorUtility.DisplayProgressBar("Processing", "Collecting part meshes.", 0.05f + (float)i / mrs.Count * 0.05f);
                    }

                    MeshFilter mf = mrs[i].rendererer.GetComponent <MeshFilter>();
                    if (mf)
                    {
                        if (mrs[i].rendererer.enabled && mrs[i].rendererer.gameObject.activeInHierarchy)
                        {
                            Mesh source = mf.sharedMesh;
                            if (source)
                            {
                                meshCount.x++;
                                triCount.x  += (int)source.GetIndexCount(0) / 3;
                                vertCount.x += source.vertexCount;

                                Material material = mrs[i].rendererer.sharedMaterial;

                                MeshInstance c = new MeshInstance();
                                c.mesh        = source;
                                c.matrix      = groupMatrixInv * mf.transform.localToWorldMatrix;
                                c.worldMatrix = mf.transform.localToWorldMatrix;
                                c.material    = material;
                                c.transparent = material && material.color.a < 1.0;
                                c.part        = mrs[i].part;
                                c.type        = mrs[i].type;
                                c.up          = mf.transform.up;
                                c.bounds      = mrs[i].rendererer.bounds;
                                instances.Add(c);
                            }
                        }
                        Object.DestroyImmediate(mf);
                    }
                    Object.DestroyImmediate(mrs[i].rendererer);
                }

                List <Vector3>   viewPositions      = new List <Vector3>();
                List <Vector3>   viewDirections     = new List <Vector3>();
                List <Matrix4x4> viewMatrices       = new List <Matrix4x4>();
                List <Matrix4x4> projectionMatrices = new List <Matrix4x4>();
                List <Plane[]>   viewFrustums       = new List <Plane[]>();

                if (group.views.Count == 0 && Camera.main)
                {
                    viewMatrices.Add(Camera.main.worldToCameraMatrix);

                    if (Camera.main.orthographic)
                    {
                        viewDirections.Add(camDir);
                    }
                    else
                    {
                        viewPositions.Add(camPos);
                    }

                    viewFrustums.Add(GeometryUtility.CalculateFrustumPlanes(Camera.main));

                    projectionMatrices.Add(Camera.main.projectionMatrix);
                }
                else if (group.views.Count > 0)
                {
                    for (int i = 0; i < group.views.Count; ++i)
                    {
                        var view       = group.views[i];
                        var viewMatrix = Matrix4x4.TRS(view.position, view.rotation, Vector3.one).inverse;

                        // Invert Z for metal/openGL
                        if (SystemInfo.usesReversedZBuffer)
                        {
                            viewMatrix.SetRow(2, -viewMatrix.GetRow(2));
                        }

                        viewMatrices.Add(viewMatrix);

                        Matrix4x4 projectionMatrix;
                        Plane[]   frustumPlanes;
                        if (view.perspective)
                        {
                            projectionMatrix = Matrix4x4.Perspective(
                                view.fov,
                                view.aspect,
                                view.minRange,
                                view.maxRange
                                );

                            viewPositions.Add(view.position);

                            frustumPlanes = MathUtils.GetFrustumPlanesPerspective(view.position, view.rotation, view.fov, view.aspect, view.minRange, view.maxRange);
                        }
                        else
                        {
                            projectionMatrix = Matrix4x4.Ortho(
                                -view.size * view.aspect,
                                view.size * view.aspect,
                                -view.size,
                                view.size,
                                view.minRange,
                                view.maxRange
                                );

                            viewDirections.Add(view.rotation * Vector3.forward);

                            frustumPlanes = MathUtils.GetFrustumPlanesOrtho(view.position, view.rotation, view.size, view.aspect, view.minRange, view.maxRange);
                        }

                        projectionMatrices.Add(projectionMatrix);

                        viewFrustums.Add(frustumPlanes);
                    }
                }
                else
                {
                    if (canRemoveTubesAndPins || canRemoveKnobs || canRemoveCompletelyInvisible || cullBackfaces)
                    {
                        Debug.LogError("No views specified for backface culling and geometry removal. Disabling!");
                        canRemoveTubesAndPins        = false;
                        canRemoveKnobs               = false;
                        canRemoveCompletelyInvisible = false;
                        cullBackfaces = false;
                    }
                }

                if (instances.Count > 16777215)
                {
                    Debug.LogError($"Group {group.groupName} contains too many meshes. Some meshes will not be optimized correctly. Please split the group into multiple groups.");
                }

                AnalyzeMeshes(viewMatrices, projectionMatrices, viewPositions, viewDirections, viewFrustums, instances, partMeshCount, partSurfaceMeshCount, canRemoveTubesAndPins, canRemoveKnobs, canRemoveCompletelyInvisible, group.importSettings.lod);

                // Combine instances to a single mesh
                for (int i = 0; i < instances.Count; ++i)
                {
                    if (i % 200 == 0)
                    {
                        EditorUtility.DisplayProgressBar("Processing", "Combining part meshes.", 0.2f + (float)i / instances.Count * 0.05f);
                    }

                    Mesh source = instances[i].mesh;
                    if (source == null)
                    {
                        continue;
                    }

                    Matrix4x4 matrix   = instances[i].matrix;
                    Material  material = instances[i].material;
                    int       subMesh  = instances[i].transparent ? (instances[i].type == MeshType.Knob ? 3 : 2) : instances[i].pixelKnobCount >= knobNormalMapThreshold ? 1 : 0;

                    MeshHelper mh = new MeshHelper(source);
                    mh.Transform(matrix);

                    // Cull backfaces
                    if (cullBackfaces)
                    {
                        for (int t = 0; t < mh.triangles.Count; t += 3)
                        {
                            int     v0     = mh.triangles[t];
                            int     v1     = mh.triangles[t + 1];
                            int     v2     = mh.triangles[t + 2];
                            Vector3 triCen = groupMatrix.MultiplyPoint((mh.vertices[v0] + mh.vertices[v1] + mh.vertices[v2]) / 3);
                            Vector3 triNor = groupMatrix.MultiplyVector(mh.normals[v0] + mh.normals[v1] + mh.normals[v2]);//.normalized;

                            bool anyInView = false;
                            for (int v = 0; v < viewPositions.Count; ++v)
                            {
                                anyInView |= (Vector3.Dot(viewPositions[v] - triCen, triNor) >= 0);
                            }
                            for (int v = 0; v < viewDirections.Count; ++v)
                            {
                                anyInView |= (Vector3.Dot(viewDirections[v], triNor) <= 0);
                            }
                            if (!anyInView)
                            {
                                mh.triangles[t]     = -1;
                                mh.triangles[t + 1] = -1;
                                mh.triangles[t + 2] = -1;
                            }
                        }
                        mh.triangles.RemoveAll((obj) => obj < 0);

                        mh.RemoveUnusedVertices();
                    }

                    // culled completely?
                    if (mh.vertices.Count == 0)
                    {
                        partMeshCount[instances[i].part]--;
                        continue;
                    }

                    meshCount.y++;
                    triCount.y  += mh.triangles.Count / 3;
                    vertCount.y += mh.vertices.Count;

                    // Store color in vertices
                    mh.SetColor(material);

                    // Randomize normals
                    if (randomizeNormals)
                    {
                        mh.AddNormalNoise();
                    }

                    meshHelpers[subMesh].Combine(mh);

                    mh = null;
                }

                EditorUtility.DisplayProgressBar("Processing", "Building new meshes.", 0.3f);

                for (int i = 0; i < meshHelpers.Length; ++i)
                {
                    if (meshHelpers[i].vertices.Count > 0)
                    {
                        GameObject target = group.gameObject;
                        if (i > 0)
                        {
                            target = new GameObject(group.name + "_subMesh" + i);
                            Undo.RegisterCreatedObjectUndo(target, "Create sub mesh");
                            target.transform.SetParent(group.transform, false);
                        }

                        MeshFilter   mf = Undo.AddComponent <MeshFilter>(target);
                        MeshRenderer mr = Undo.AddComponent <MeshRenderer>(target);

                        Mesh m = new Mesh();
                        meshHelpers[i].ToMesh(m, group.importSettings.lightmapped);

                        // Need tangents?
                        if (i > 0)
                        {
                            m.RecalculateTangents();
                        }

                        // Make static.
                        target.isStatic = true;
                        if (group.importSettings.lightmapped)
                        {
                            mr.receiveGI = ReceiveGI.Lightmaps;
                        }
                        else
                        {
                            mr.receiveGI = ReceiveGI.LightProbes;
                        }

                        mf.sharedMesh = m;
                        switch (i)
                        {
                        case 0:
                        {
                            PartUtility.StoreOptimizedMesh(m, group.parentName + "_" + group.groupName + "_Optimized.asset");
                            mr.sharedMaterial = AssetDatabase.LoadAssetAtPath <Material>("Packages/com.unity.lego.modelimporter/Materials/LEGO_VertexColor.mat");
                            break;
                        }

                        case 1:
                        {
                            PartUtility.StoreOptimizedMesh(m, group.parentName + "_" + group.groupName + "_Optimized_NormalMap.asset");
                            mr.sharedMaterial = AssetDatabase.LoadAssetAtPath <Material>("Packages/com.unity.lego.modelimporter/Materials/LEGO_VertexColor_NormalMap.mat");
                            break;
                        }

                        case 2:
                        {
                            PartUtility.StoreOptimizedMesh(m, group.parentName + "_" + group.groupName + "_Optimized_Transparent.asset");
                            mr.sharedMaterial = AssetDatabase.LoadAssetAtPath <Material>("Packages/com.unity.lego.modelimporter/Materials/LEGO_VertexColor_Transparent.mat");
                            break;
                        }

                        case 3:
                        {
                            PartUtility.StoreOptimizedMesh(m, group.parentName + "_" + group.groupName + "_Optimized_Transparent_NormalMap.asset");
                            mr.sharedMaterial = AssetDatabase.LoadAssetAtPath <Material>("Packages/com.unity.lego.modelimporter/Materials/LEGO_VertexColor_Transparent_NormalMap.mat");
                            break;
                        }
                        }
                    }
                }
            }

            // Remove decoration surfaces for non-legacy parts that have no surface meshes left.
            progress = 0;
            foreach (var entry in partSurfaceMeshCount)
            {
                if (progress++ % 200 == 0)
                {
                    EditorUtility.DisplayProgressBar("Processing", "Removing unneeded decorations.", 0.3f + (float)progress / partSurfaceMeshCount.Count * 0.25f);
                }

                if (!entry.Key.legacy && entry.Value == 0)
                {
                    var decorationSurfaces = entry.Key.transform.Find("DecorationSurfaces");
                    if (decorationSurfaces)
                    {
                        Undo.DestroyObjectImmediate(decorationSurfaces.gameObject);
                    }
                }
            }

            // Remove parts that have no meshes left.
            progress = 0;
            foreach (var entry in partMeshCount)
            {
                if (progress++ % 200 == 0)
                {
                    EditorUtility.DisplayProgressBar("Processing", "Removing empty parts.", 0.55f + (float)progress / partMeshCount.Count * 0.25f);
                }

                if (entry.Value == 0)
                {
                    entry.Key.brick.parts.Remove(entry.Key);

                    // If no parts are left, remove the brick.
                    if (entry.Key.brick.parts.Count == 0)
                    {
                        Undo.DestroyObjectImmediate(entry.Key.brick.gameObject);
                    }
                    else
                    {
                        Undo.DestroyObjectImmediate(entry.Key.gameObject);
                    }
                }
            }

            // Collapse remaining box colliders.
            if (collapseCols)
            {
                // FIXME Move to proper constant.
                var colliderSizeBias = 0.02f;
                var epsilon          = 0.001f;

                var allColliders = group.GetComponentsInChildren <BoxCollider>();
                // Filter out colliders that are part of connectivity features.
                var partColliders = allColliders.Where((c) => c.gameObject.layer != LayerMask.NameToLayer(Connection.connectivityReceptorLayerName) && c.gameObject.layer != LayerMask.NameToLayer(Connection.connectivityConnectorLayerName)).ToArray();
                boxColliderCount.x = partColliders.Length;
                boxColliderCount.y = partColliders.Length;
                bool[] colDeleted       = new bool[partColliders.Length];
                var    collapseHappened = true;
                var    iterationCount   = 0;
                while (collapseHappened)
                {
                    collapseHappened = false;
                    iterationCount++;
                    for (var i = 0; i < partColliders.Length; ++i)
                    {
                        if (i % 200 == 0)
                        {
                            EditorUtility.DisplayProgressBar("Processing", "Collapsing colliders - iteration " + iterationCount + ".", 0.8f + (float)i / partColliders.Length * 0.05f);
                        }

                        if (colDeleted[i])
                        {
                            continue;
                        }

                        for (var j = i + 1; j < partColliders.Length; ++j)
                        {
                            if (colDeleted[j])
                            {
                                continue;
                            }

                            var colliderA = partColliders[i];
                            var colliderB = partColliders[j];

                            // Check that spaces match up.
                            var colliderBRotationInALocalSpace = Quaternion.Inverse(colliderA.transform.rotation) * colliderB.transform.rotation;
                            var euler = colliderBRotationInALocalSpace.eulerAngles;
                            if (Mathf.Abs(Mathf.Round(euler.x / 90.0f) - euler.x / 90.0f) < epsilon && Mathf.Abs(Mathf.Round(euler.y / 90.0f) - euler.y / 90.0f) < epsilon && Mathf.Abs(Mathf.Round(euler.z / 90.0f) - euler.z / 90.0f) < epsilon)
                            {
                                // Check that centers match up.
                                var colliderBCenterInColliderALocalSpace = colliderA.transform.InverseTransformPoint(colliderB.transform.TransformPoint(colliderB.center));

                                var centerDiff            = colliderBCenterInColliderALocalSpace - colliderA.center;
                                var axisToMatch           = centerDiff.MajorAxis();
                                var centerProjectedToAxis = centerDiff.SnapToMajorAxis() * centerDiff.magnitude;
                                if ((centerDiff - centerProjectedToAxis).sqrMagnitude < 0.01f)
                                {
                                    //Debug.Log(GetGameObjectPath(colliderA.gameObject) + colliderA.transform.GetSiblingIndex() + "\n" + GetGameObjectPath(colliderB.gameObject) + colliderB.transform.GetSiblingIndex());
                                    //Debug.Log("Matched centers on " + axisToMatch);
                                    // Check that size match up.
                                    var colliderBSizeInColliderALocalSpace = colliderA.transform.InverseTransformVector(colliderB.transform.TransformVector(colliderB.size)).Abs();
                                    //Debug.Log("Collider b size in collider a local space: " + colliderBSizeInColliderALocalSpace);

                                    var otherAxis1 = (axisToMatch + 1) % 3;
                                    var otherAxis2 = (axisToMatch + 2) % 3;
                                    if (Mathf.Abs(centerDiff.magnitude - (colliderA.size[axisToMatch] + colliderBSizeInColliderALocalSpace[axisToMatch] + 2.0f * colliderSizeBias) * 0.5f) < epsilon)
                                    {
                                        //Debug.Log("Matched on axis length");
                                        if (Mathf.Abs(colliderA.size[otherAxis1] - colliderBSizeInColliderALocalSpace[otherAxis1]) < epsilon && Mathf.Abs(colliderA.size[otherAxis2] - colliderBSizeInColliderALocalSpace[otherAxis2]) < epsilon)
                                        {
                                            //Debug.Log("Matched other axes");
                                            // Merge collider B into collider A.
                                            colliderA.center += centerDiff.normalized * (colliderBSizeInColliderALocalSpace[axisToMatch] + colliderSizeBias) * 0.5f;
                                            var newSize = colliderA.size;
                                            newSize[axisToMatch] += colliderBSizeInColliderALocalSpace[axisToMatch] + colliderSizeBias;
                                            colliderA.size        = newSize;

                                            // Update part and destroy collider game object. Empty parent Colliders game objects will be removed during part clean-up.
                                            var part = colliderB.GetComponentInParent <Part>();
                                            part.colliders.Remove(colliderB);
                                            Undo.DestroyObjectImmediate(colliderB.gameObject);

                                            colDeleted[j] = true;

                                            boxColliderCount.y--;

                                            // Note that a change was made, and run over colliders again when done.
                                            collapseHappened = true;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            if (collapseMesh || collapseCols)
            {
                // Collect all remaining parts and clean them up.
                progress = 0;
                var parts = group.GetComponentsInChildren <Part>();
                foreach (var part in parts)
                {
                    if (progress++ % 200 == 0)
                    {
                        EditorUtility.DisplayProgressBar("Processing", "Cleaning up remaining parts.", 0.85f + (float)progress / parts.Length * 0.15f);
                    }
                    CleanupPartGeometryTransforms(part);
                }
            }

            EditorUtility.ClearProgressBar();
        }
        public static void ReimportModelGroup(LXFMLDoc lxfml, ModelGroup group, ModelGroupImportSettings importSettings, bool detectConnectivity = false)
        {
            // Assign the new group import settings to the group.
            group.importSettings = importSettings;

            // We assume that the group can be found, so reimport it.
            if (group.processed)
            {
                // Remove all processed meshes.
                var renderers = group.GetComponentsInChildren <MeshRenderer>();
                foreach (var renderer in renderers)
                {
                    // FIXME Destroy the mesh? Prevents undo..
                    var filter = renderer.GetComponent <MeshFilter>();
                    //Undo.DestroyObjectImmediate(filter.sharedMesh);

                    if (renderer.GetComponent <ModelGroup>() == null)
                    {
                        // Destroy submesh game objects entirely.
                        Undo.DestroyObjectImmediate(renderer.gameObject);
                    }
                    else
                    {
                        // Destroy mesh related components on group game object.
                        Object.DestroyImmediate(filter);
                        Object.DestroyImmediate(renderer);
                    }
                }
            }

            // FIXME Check if bricks are referenced.
            // FIXME Check if bricks have custom components attached.

            // Remove group bricks.
            var existingBricks = group.GetComponentsInChildren <Brick>();

            foreach (var brick in existingBricks)
            {
                Undo.DestroyObjectImmediate(brick.gameObject);
            }

            var groupLightMapped = group.importSettings.isStatic && group.importSettings.lightmapped;

            SetStaticAndGIParams(group.gameObject, group.importSettings.isStatic, groupLightMapped);

            // Move group to origo to ensure that bricks are instantiated in the correct positions.
            var originalGroupParent       = group.transform.parent;
            var originalGroupSiblingIndex = group.transform.GetSiblingIndex();

            group.transform.parent        = null;
            group.transform.localPosition = Vector3.zero;
            group.transform.localRotation = Quaternion.identity;
            group.transform.localScale    = Vector3.one;

            // Create dictionary with just this group.
            var modelGroupImportSettingsDictionary = new DictionaryIntToModelGroupImportSettings();

            modelGroupImportSettingsDictionary.Add(group.number, group.importSettings);

            // Instantiate group bricks.
            var resultBricks = new Dictionary <int, Brick>(lxfml.bricks.Count);

            InstantiateModelBricks(lxfml, modelGroupImportSettingsDictionary, ref resultBricks, group.number);

            // Assign bricks to group.
            foreach (var brick in resultBricks.Values)
            {
                brick.transform.SetParent(group.transform);
            }

            // Set parent of group back to original.
            group.transform.parent = originalGroupParent;
            group.transform.SetSiblingIndex(originalGroupSiblingIndex);

            if (detectConnectivity && group.importSettings.connectivity)
            {
                var sceneBricks = new HashSet <Brick>(StageUtility.GetCurrentStageHandle().FindComponentsOfType <Brick>());
                DetectConnectivity(new HashSet <Brick>(resultBricks.Values), sceneBricks);
            }

            EditorUtility.ClearProgressBar();
        }