Example #1
0
        void UpdateTracking()
        {
            CSGModelBase parentCSGModel = GetCSGModel();

            // Make sure the CSG Model knows about this brush. If they duplicated a brush in the hierarchy then this
            // allows us to make sure the CSG Model knows about it
            if (parentCSGModel != null)
            {
                bool newBrush = parentCSGModel.TrackBrush(this);

                if (newBrush)
                {
                    MeshFilter meshFilter = gameObject.AddOrGetComponent <MeshFilter>();

                    meshFilter.sharedMesh = new Mesh();
                    brushCache            = new BrushCache();
                    EnsureWellFormed();
                    RecalculateBrushCache();
                }
                Invalidate(false);
                tracked = true;
            }
            else
            {
                tracked = false;
            }
        }
Example #2
0
        internal static Dictionary <int, List <Polygon> > OptimizeCollision(BrushCache mainCache)
        {
            // Get the polygons grouped by ID
            Dictionary <int, List <Polygon> > groupedPolygons = mainCache.GetGroupedBuiltCollisionPolygons();
            List <Polygon> allPolygons = new List <Polygon>();

            foreach (KeyValuePair <int, List <Polygon> > row in groupedPolygons)
            {
                // Determine the polygon set that is optimal
                List <Polygon> newPolygons = Optimizer.CalculateConvexHulls(row.Value);
                // If the polygon set has actually changed
                if (newPolygons != row.Value)
                {
                    // Replace the grouped polygons with the optimal set
                    row.Value.Clear();
                    row.Value.AddRange(newPolygons);
                }
                // Add the new polygons to the total list
                allPolygons.AddRange(newPolygons);
            }

            // Set the built polygons for this cache from the newly calculated optimal polygons
            mainCache.SetVisualBuiltPolygons(allPolygons);

            return(groupedPolygons);
        }
Example #3
0
        private static void SplitBy(LinkedList <BrushChunk> brushChunks, BrushCache splitter)
        {
            Polygon[] polygons = splitter.Polygons;
            for (int polygonIndex = 0; polygonIndex < polygons.Length; polygonIndex++)
            {
                Plane splitPlane = polygons[polygonIndex].Plane;

                for (LinkedListNode <BrushChunk> current = brushChunks.First; current != null; current = current.Next)
                {
#if !NO_EARLYOUT
                    if (!current.Value.GetBounds().IntersectsApproximate(splitter.Bounds))
                    {
                        continue;
                    }
#endif

                    BrushChunk chunkIn;
                    BrushChunk chunkOut;

                    if (current.Value.SplitByPlane(splitPlane, out chunkIn, out chunkOut))
                    {
                        // TODO: If chunkIn is fully inside polygons, delete it
                        current.Value = chunkOut;
                        current       = brushChunks.AddAfter(current, chunkIn);
                    }
                }
            }
        }
Example #4
0
        private static void ExtractSubtractionPolygons(BrushCache brushCacheSource, LinkedList <BrushChunk> brushChunks, BrushCache removee, bool isCollisionPass)
        {
            Polygon[] polygons = removee.Polygons;
            for (LinkedListNode <BrushChunk> current = brushChunks.First; current != null; current = current.Next)
            {
#if !NO_EARLYOUT
                if (!current.Value.GetBounds().IntersectsApproximate(removee.Bounds))
                {
                    continue;
                }
#endif
                List <Polygon> currentPolygons = current.Value.Polygons;
                for (int j = 0; j < currentPolygons.Count; j++)
                {
                    Polygon previousPolygon = currentPolygons[j];
                    if (previousPolygon.ExcludeFromFinal)
                    {
                        for (int k = 0; k < polygons.Length; k++)
                        {
                            if (GeometryHelper.PolygonContainsPolygon(polygons[k], previousPolygon))
                            {
                                previousPolygon = previousPolygon.DeepCopy();
                                // Transfer attributes from the source polygon to the chunk polygon
                                previousPolygon.UniqueIndex          = polygons[k].UniqueIndex;
                                previousPolygon.Material             = polygons[k].Material;
                                previousPolygon.ExcludeFromFinal     = false;
                                previousPolygon.UserExcludeFromFinal = polygons[k].UserExcludeFromFinal;

                                Vertex[] vertices = previousPolygon.Vertices;
                                // TODO: Optimise the Triangulate code
                                // Triangulate the polygon so that we can determine a normal from the surrounding three vertices
                                Polygon[] triangles = PolygonFactory.Triangulate(polygons[k]);

                                for (int l = 0; l < vertices.Length; l++)
                                {
                                    Vertex interpolatedVertex = CalculateInterpolated(triangles, vertices[l].Position);
                                    vertices[l].Normal = -interpolatedVertex.Normal;                                     // Flip normal as face is flipped
                                    vertices[l].Color  = interpolatedVertex.Color;
                                    vertices[l].UV     = interpolatedVertex.UV;
                                }

                                currentPolygons[j] = previousPolygon;


                                if (isCollisionPass)
                                {
                                    BrushCache.NotifyOfStolenCollisionPolygon(removee, brushCacheSource, currentPolygons[j]);
                                }
                                else
                                {
                                    BrushCache.NotifyOfStolenVisualPolygon(removee, brushCacheSource, currentPolygons[j]);
                                }
                            }
                        }
                    }
                }
            }
        }
Example #5
0
        public override void RecachePolygons(bool markUnbuilt)
        {
            if (brushCache == null)
            {
                brushCache = new BrushCache();
            }
            Polygon[] cachedTransformedPolygons = GenerateTransformedPolygons();
            Bounds    cachedTransformedBounds   = GetBoundsTransformed();

            brushCache.Set(mode, cachedTransformedPolygons, cachedTransformedBounds, markUnbuilt);
        }
Example #6
0
        internal static void ReclaimStolenCollisionPolygons(BrushCache mainCache)
        {
            List <KeyValuePair <Polygon, BrushCache> > stolenCollisionPolygons = mainCache.stolenCollisionPolygons;

            for (int i = 0; i < stolenCollisionPolygons.Count; i++)
            {
                // TODO: Does this actually remove all of them?
                bool removed = stolenCollisionPolygons[i].Value.builtCollisionPolygons.Remove(stolenCollisionPolygons[i].Key);
                if (removed)
                {
                    mainCache.builtCollisionPolygons.Add(stolenCollisionPolygons[i].Key);
                }

                stolenCollisionPolygons.RemoveAt(i);
                i--;
            }
        }
Example #7
0
 internal static void NotifyOfStolenCollisionPolygon(BrushCache mainCache, BrushCache brushCacheSource, Polygon newPolygon)
 {
     mainCache.stolenCollisionPolygons.Add(new KeyValuePair <Polygon, BrushCache>(newPolygon, brushCacheSource));
 }
Example #8
0
        private static void SplitAndRemove(LinkedList <BrushChunk> brushChunks, BrushCache removee, int[] polygonsRemoved)
        {
            Polygon[] polygons    = removee.Polygons;
            Plane[]   splitPlanes = removee.SplitPlanes;

            for (int polygonIndex = 0; polygonIndex < polygons.Length; polygonIndex++)
            {
                Plane splitPlane = splitPlanes[polygonIndex];

                for (LinkedListNode <BrushChunk> current = brushChunks.First; current != null;)
                {
#if !NO_EARLYOUT
                    if (!current.Value.GetBounds().IntersectsApproximate(removee.Bounds))
                    {
                        current = current.Next;
                        continue;
                    }
#endif

                    BrushChunk chunkIn;
                    BrushChunk chunkOut;

                    if (current.Value.SplitByPlane(splitPlane, out chunkIn, out chunkOut))
                    {
                        // TODO: If chunkIn is fully inside polygons, delete it
                        current.Value = chunkOut;

                        if (!GeometryHelper.PolyhedronContainsPolyhedron(polygons, chunkIn.Polygons))
                        {
                            current = brushChunks.AddAfter(current, chunkIn);
                        }
                        else
                        {
                            for (int i = 0; i < chunkIn.Polygons.Count; i++)
                            {
                                if (chunkIn.Polygons[i].UniqueIndex != -1)
                                {
                                    int relativeIndex = chunkIn.Polygons[i].UniqueIndex - firstPolygonUID;
                                    MarkPolygonRemoved(relativeIndex, polygonsRemoved);
                                }
                            }
                        }

                        // Next iteration
                        current = current.Next;
                    }
                    else
                    {
                        LinkedListNode <BrushChunk> next = current.Next;
                        BrushChunk chunk = current.Value;
                        if (GeometryHelper.PolyhedronContainsPolyhedron(polygons, chunk.Polygons))
                        {
                            for (int i = 0; i < chunk.Polygons.Count; i++)
                            {
                                if (chunk.Polygons[i].UniqueIndex != -1)
                                {
                                    int relativeIndex = chunk.Polygons[i].UniqueIndex - firstPolygonUID;
                                    MarkPolygonRemoved(relativeIndex, polygonsRemoved);
                                }
                            }

                            brushChunks.Remove(current);
                        }
                        // Next iteration
                        current = next;
                    }
                }
            }
        }
Example #9
0
        private static void RestoreInteriorPolygons(LinkedList <BrushChunk> brushChunks, BrushCache removee, List <Polygon> excludedPolygons, int[] polygonsRemoved)
        {
            // TODO: If a polygon is in a subtract last rather than an add it shold not be removed
            Polygon[] polygons    = removee.Polygons;
            Vector3   brushCenter = removee.Bounds.center;

            for (int i = 0; i < excludedPolygons.Count; i++)
            {
                Vector3 polygonCenter = excludedPolygons[i].GetCenterPoint();

#if !NO_EARLYOUT
                if (!removee.Bounds.ContainsApproximate(polygonCenter))
                {
                    continue;
                }
#endif

                float distanceInside = GeometryHelper.PolyhedronContainsPointDistance(polygons, polygonCenter);
                if (distanceInside > 1E-05)
                {
                    int relativeIndex = excludedPolygons[i].UniqueIndex - firstPolygonUID;
                    MarkPolygonRestored(relativeIndex, polygonsRemoved);

                    // Well inside the brush, so restore it
                    excludedPolygons[i].ExcludeFromFinal = false;
                    excludedPolygons.Remove(excludedPolygons[i]);
                    i--;
                }
                else if (distanceInside >= -1e-5f)
                {
                    // Edge case, make sure the face is towards the brush
                    if (Vector3.Dot(brushCenter - polygonCenter, excludedPolygons[i].Plane.normal) > 0)
                    {
                        int relativeIndex = excludedPolygons[i].UniqueIndex - firstPolygonUID;
                        MarkPolygonRestored(relativeIndex, polygonsRemoved);

                        excludedPolygons[i].ExcludeFromFinal = false;
                        excludedPolygons.Remove(excludedPolygons[i]);
                        i--;
                    }
                }
            }
        }
Example #10
0
        private static void RemoveInteriorPolygons(LinkedList <BrushChunk> brushChunks, BrushCache removee, List <Polygon> excludedPolygons, int[] polygonsRemoved)
        {
            // TODO: If a polygon is in a subtract last rather than an add it shold not be removed
            Polygon[] polygons    = removee.Polygons;
            Vector3   brushCenter = removee.Bounds.center;

            for (LinkedListNode <BrushChunk> current = brushChunks.First; current != null; current = current.Next)
            {
#if !NO_EARLYOUT
                if (!current.Value.GetBounds().IntersectsApproximate(removee.Bounds))
                {
                    continue;
                }
#endif

                List <Polygon> chunkPolygons = current.Value.Polygons;
                for (int i = 0; i < chunkPolygons.Count; i++)
                {
                    if (chunkPolygons[i].ExcludeFromFinal == false)
                    {
                        Vector3 polygonCenter  = chunkPolygons[i].GetCenterPoint();
                        float   distanceInside = GeometryHelper.PolyhedronContainsPointDistance(polygons, polygonCenter);
                        if (distanceInside > 1E-05)
                        {
                            int relativeIndex = chunkPolygons[i].UniqueIndex - firstPolygonUID;
                            MarkPolygonRemoved(relativeIndex, polygonsRemoved);

                            // Well inside the brush, so remove it
                            chunkPolygons[i].ExcludeFromFinal = true;
                            excludedPolygons.Add(chunkPolygons[i]);
                        }
                        else if (distanceInside >= -1E-05)
                        {
                            // Edge case, make sure the face is towards the brush
                            if (Vector3.Dot(brushCenter - polygonCenter, chunkPolygons[i].Plane.normal) > 0)
                            {
                                int relativeIndex = chunkPolygons[i].UniqueIndex - firstPolygonUID;
                                MarkPolygonRemoved(relativeIndex, polygonsRemoved);

                                chunkPolygons[i].ExcludeFromFinal = true;
                                excludedPolygons.Add(chunkPolygons[i]);
                            }
                        }
                    }
                }
            }
        }
Example #11
0
        static int firstPolygonUID = 0;         // Not sure about this

        internal static void Build(BrushCache brushCache, int brushIndex, BrushCache[] allBrushCaches, bool isCollisionPass)
        {
            firstPolygonUID = brushCache.Polygons[0].UniqueIndex;
            if (brushCache.Mode == CSGMode.Add)
            {
                int[]          polygonsRemoved = new int[brushCache.Polygons.Length];
                List <Polygon> builtPolygons   = new List <Polygon>();

                // Grab the intersecting brushes so we only work with what we actually intersect
                List <BrushCache> intersectingBrushCaches = null;

                if (isCollisionPass)
                {
                    intersectingBrushCaches = brushCache.IntersectingCollisionBrushCaches;
                }
                else
                {
                    intersectingBrushCaches = brushCache.IntersectingVisualBrushCaches;
                }

                LinkedList <BrushChunk> brushChunks = new LinkedList <BrushChunk>();
                brushChunks.AddFirst(new BrushChunk(new List <Polygon>(brushCache.Polygons.DeepCopy())));

                for (int i = 0; i < intersectingBrushCaches.Count; i++)
                {
                    if (intersectingBrushCaches[i] == null)
                    {
                        continue;
                    }

                    int index = Array.IndexOf(allBrushCaches, intersectingBrushCaches[i]);
                    if (index <= brushIndex)                    // Earlier brush
                    {
                        // Split the chunks so that polygons can be removed as necessary
                        SplitBy(brushChunks, intersectingBrushCaches[i]);
                    }
                    else
                    {
                        // Split by the other brush and remove any chunks inside the later brush
                        SplitAndRemove(brushChunks, intersectingBrushCaches[i], polygonsRemoved);
                    }
                }

                List <Polygon> excludedPolygons = new List <Polygon>();
                for (int i = 0; i < intersectingBrushCaches.Count; i++)
                {
                    if (intersectingBrushCaches[i] == null)
                    {
                        continue;
                    }

                    int index = Array.IndexOf(allBrushCaches, intersectingBrushCaches[i]);
                    if (index <= brushIndex)                    // Earlier brush
                    {
                        if (intersectingBrushCaches[i].Mode == CSGMode.Add)
                        {
//							// Split the chunks so that polygons can be removed as necessary
//							SplitBy(brushChunks, intersectingBrushCaches[i]);
                            // Remove any polygons that are contained in an earlier addition
                            RemoveInteriorPolygons(brushChunks, intersectingBrushCaches[i], excludedPolygons, polygonsRemoved);
                        }
                        else
                        {
                            // Restore any polygons that were removed when inside an addition but are now inside a subsequent subtraction
                            RestoreInteriorPolygons(brushChunks, intersectingBrushCaches[i], excludedPolygons, polygonsRemoved);
                        }
                    }
                    else                     // Later brush
                    {
                        if (intersectingBrushCaches[i].Mode == CSGMode.Add)
                        {
                            // Remove any polygons that will be contrained by a later additive brush
                            RemoveInteriorPolygons(brushChunks, intersectingBrushCaches[i], excludedPolygons, polygonsRemoved);
                        }
                        else
                        {
                            // If the later brush is subtractive, extract any subtractive polygons and add to these chunks
                            ExtractSubtractionPolygons(brushCache, brushChunks, intersectingBrushCaches[i], isCollisionPass);
                        }
                    }
                }

                // Concat all the chunk polygons into one list
                for (LinkedListNode <BrushChunk> current = brushChunks.First; current != null; current = current.Next)
                {
                    builtPolygons.AddRange(current.Value.Polygons);
                }

                // TODO: Is it faster without RemoveAt?
                for (int i = 0; i < polygonsRemoved.Length; i++)
                {
                    if (polygonsRemoved[i] == 0)                    // Found a complete polygon
                    {
                        int uniqueIndex = firstPolygonUID + i;
                        // TODO: Remove existing polygons with UniqueID
                        for (int j = 0; j < builtPolygons.Count; j++)
                        {
                            if (builtPolygons[j].UniqueIndex == uniqueIndex)
                            {
                                builtPolygons.RemoveAt(j);
                                j--;
                            }
                        }
                        // Add in polygon from the source polygons
                        builtPolygons.Add(brushCache.Polygons[i].DeepCopy());
                    }
                }


                // Remove any temporary polygons
                List <Polygon> newBuiltPolygons = new List <Polygon>(builtPolygons.Count);
                for (int i = 0; i < builtPolygons.Count; i++)
                {
                    if (!builtPolygons[i].ExcludeFromFinal)
                    {
                        newBuiltPolygons.Add(builtPolygons[i]);
                    }
                }

                newBuiltPolygons.TrimExcess();

                builtPolygons = newBuiltPolygons;

                // Finally, provide the brush cache with the built polygons
                if (isCollisionPass)
                {
                    brushCache.SetCollisionBuiltPolygons(builtPolygons);
                }
                else
                {
                    brushCache.SetVisualBuiltPolygons(builtPolygons);
                }

//				for (int i = 0; i < brushChunks.Count; i++)
//				{
//					DebugExclude.DisplayChunk(DebugExclude.hackyHolder, brushChunks[i], brushIndex);
//				}
            }
            else             // Subtract
            {
                // Do nothing for subtractive brushes. This is handled by the additive brushes they interact with
                if (isCollisionPass)
                {
                    brushCache.SetCollisionBuiltPolygons(brushCache.BuiltCollisionPolygons);
                }
                else
                {
                    brushCache.SetVisualBuiltPolygons(brushCache.BuiltVisualPolygons);
                }
            }
        }
Example #12
0
        protected static List <Brush> CalculateIntersectingBrushes(Brush sourceBrush, List <Brush> brushes, bool isCollisionPass)
        {
            // If the brush is not CSG it can't intersect any brushes!
            if (sourceBrush.IsNoCSG || sourceBrush.Mode == CSGMode.Volume)
            {
                return(new List <Brush>());
            }
            // Return empty lists if the pass is not relevant
            if (isCollisionPass)
            {
                if (!sourceBrush.hasCollision)
                {
                    return(new List <Brush>());
                }
            }
            else
            {
                if (!sourceBrush.isVisible)
                {
                    return(new List <Brush>());
                }
            }

            BrushCache sourceCache = sourceBrush.BrushCache;

            List <Brush> intersectingBrushes = new List <Brush>();

            Bounds targetBounds = sourceCache.Bounds;

            Polygon[] targetPolygons = sourceCache.Polygons;

            // Find the index of this brush
            int thisIndex = -1;

            for (int i = 0; i < brushes.Count; i++)
            {
                if (brushes[i] != null)
                {
                    BrushCache brushCache = brushes[i].BrushCache;
                    if (brushCache == sourceCache)
                    {
                        thisIndex = i;
                        break;
                    }
                }
            }

            // Go through the brushes before this one
            for (int i = thisIndex - 1; i >= 0; i--)
            {
                if (!Brush.IsInvalidForBuild(brushes[i]))
                {
                    // Skip any brushes not suitable for the pass
                    if (brushes[i].isNoCSG || brushes[i].mode == CSGMode.Volume)
                    {
                        // NoCSG and volume brushes skip the CSG calcs
                        continue;
                    }
                    else if (isCollisionPass && !brushes[i].HasCollision)
                    {
                        continue;
                    }
                    else if (!isCollisionPass && !brushes[i].IsVisible)
                    {
                        continue;
                    }

                    BrushCache brushCache = brushes[i].BrushCache;
                    if (brushCache.Bounds.IntersectsApproximate(targetBounds))
                    {
                        if (GeometryHelper.PolyhedraIntersect(brushCache.Polygons, targetPolygons))
                        {
                            intersectingBrushes.Add(brushes[i]);

                            // If the brush is contained entirely by a previous subtraction then it's impossible
                            // to intersect with any brushes before that subtraction
                            if (brushCache.Mode == CSGMode.Subtract)
                            {
                                if (GeometryHelper.PolyhedronContainsPolyhedron(brushCache.Polygons, targetPolygons))
                                {
                                    break;
                                }
                            }
                        }
                    }
                }
            }

            intersectingBrushes.Reverse();

            List <BrushCache> activeSubtractions = new List <BrushCache>();

            // Go through the brushes after this one
            for (int i = thisIndex + 1; i < brushes.Count; i++)
            {
                if (!Brush.IsInvalidForBuild(brushes[i]))
                {
                    // Skip any brushes not suitable for the pass
                    if (brushes[i].isNoCSG || brushes[i].mode == CSGMode.Volume)
                    {
                        // NoCSG and volume brushes skip the CSG calcs
                        continue;
                    }
                    else if (isCollisionPass && !brushes[i].HasCollision)
                    {
                        // Collision pass and this brush has no collision so skip
                        continue;
                    }
                    else if (!isCollisionPass && !brushes[i].IsVisible)
                    {
                        // Visual pass and this brush isn't visible so skip
                        continue;
                    }

                    BrushCache brushCache = brushes[i].BrushCache;
                    if (brushCache.Bounds.IntersectsApproximate(targetBounds))
                    {
                        if (GeometryHelper.PolyhedraIntersect(brushCache.Polygons, targetPolygons))
                        {
                            bool containedByPreviousSubtraction = false;

                            for (int j = 0; j < activeSubtractions.Count; j++)
                            {
                                BrushCache subtractionCache = activeSubtractions[j];

                                if (subtractionCache.Bounds.IntersectsApproximate(brushCache.Bounds))
                                {
                                    if (GeometryHelper.PolyhedronContainsPolyhedron(subtractionCache.Polygons, brushCache.Polygons))
                                    {
                                        containedByPreviousSubtraction = true;
                                        break;
                                    }
                                }
                            }

                            if (!containedByPreviousSubtraction)
                            {
                                intersectingBrushes.Add(brushes[i]);

                                if (brushCache.Mode == CSGMode.Subtract)
                                {
                                    activeSubtractions.Add(brushCache);
                                }
                            }
                        }
                    }
                }
            }

            return(intersectingBrushes);
        }
Example #13
0
        internal static void CoreBuild(object state)
        {
            MeshGroupManager.OnFinalizeVisualMesh    = onFinalizeVisualMesh;
            MeshGroupManager.OnFinalizeCollisionMesh = onFinalizeCollisionMesh;

            if (forceRebuild)
            {
                buildContext.ClearAll();
            }

            buildStartTime = DateTime.Now;

            int brushesToBuild = 0;

            brushesBuilt = 0;

            BrushCache[] allBrushCaches = new BrushCache[brushes.Count];

            int totalPolygons = 0;

            for (int i = 0; i < allBrushCaches.Length; i++)
            {
                allBrushCaches[i] = brushes[i].BrushCache;
                totalPolygons    += allBrushCaches[i].Polygons.Length;
            }

            PolygonEntry[] oldVisualPolygonIndex    = buildContext.VisualPolygonIndex;
            PolygonEntry[] oldCollisionPolygonIndex = buildContext.CollisionPolygonIndex;

            PolygonEntry[] newVisualPolygonIndex    = new PolygonEntry[totalPolygons];
            PolygonEntry[] newCollisionPolygonIndex = new PolygonEntry[totalPolygons];

            int polygonUniqueID = 0;

            // Walk through each brush, assigning unique IDs to each polygon (since Unity serialisation will break
            // references on a recompile, reload etc)
            for (int i = 0; i < allBrushCaches.Length; i++)
            {
                brushes[i].AssignUniqueIDs(polygonUniqueID);

                // TODO: Find a way to remove this when Nova is removed
                polygonUniqueID += allBrushCaches[i].AssignUniqueIDs(polygonUniqueID,
                                                                     brushes[i].IsVisible,
                                                                     oldVisualPolygonIndex,
                                                                     newVisualPolygonIndex,
                                                                     brushes[i].HasCollision,
                                                                     oldCollisionPolygonIndex,
                                                                     newCollisionPolygonIndex);
            }

            buildContext.VisualPolygonIndex    = newVisualPolygonIndex;
            buildContext.CollisionPolygonIndex = newCollisionPolygonIndex;

            // Create a list of builders that need to be built
            bool[] shouldBuildVisible   = new bool[allBrushCaches.Length];
            bool[] shouldBuildCollision = new bool[allBrushCaches.Length];

            for (int brushIndex = 0; brushIndex < allBrushCaches.Length; brushIndex++)
            {
                if (!allBrushCaches[brushIndex].Built && brushes[brushIndex].IsVisible)
                {
                    shouldBuildVisible[brushIndex] = true;
                    brushesToBuild++;

                    // Mark all the intersecting brushes as needing to build
                    foreach (BrushCache brushCache in allBrushCaches[brushIndex].IntersectingVisualBrushCaches)
                    {
                        // TODO: This is slower than it needs to be
                        int otherIndex = Array.IndexOf(allBrushCaches, brushCache);
                        if (otherIndex != -1)
                        {
                            if (!shouldBuildVisible[otherIndex] && brushes[otherIndex].IsVisible)
                            {
                                shouldBuildVisible[otherIndex] = true;
                                brushesToBuild++;

                                if (brushes[otherIndex].Mode == CSGMode.Subtract)
                                {
                                    foreach (BrushCache brushCache2 in allBrushCaches[otherIndex].IntersectingVisualBrushCaches)
                                    {
                                        // TODO: This is slower than it needs to be
                                        int otherIndex2 = Array.IndexOf(allBrushCaches, brushCache2);
                                        if (otherIndex2 != -1)
                                        {
                                            if (!shouldBuildVisible[otherIndex2] && brushes[otherIndex2].IsVisible)
                                            {
                                                shouldBuildVisible[otherIndex2] = true;
                                                brushesToBuild++;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            for (int brushIndex = 0; brushIndex < allBrushCaches.Length; brushIndex++)
            {
                if (!allBrushCaches[brushIndex].Built && brushes[brushIndex].HasCollision)
                {
                    shouldBuildCollision[brushIndex] = true;
                    // TODO: This is slower than it needs to be
                    foreach (BrushCache brushCache in allBrushCaches[brushIndex].IntersectingCollisionBrushCaches)
                    {
                        int otherIndex = Array.IndexOf(allBrushCaches, brushCache);
                        if (otherIndex != -1)
                        {
                            if (!shouldBuildCollision[otherIndex] && brushes[otherIndex].HasCollision)
                            {
                                shouldBuildCollision[otherIndex] = true;
                                brushesToBuild++;

                                if (brushes[otherIndex].Mode == CSGMode.Subtract)
                                {
                                    foreach (BrushCache brushCache2 in allBrushCaches[otherIndex].IntersectingCollisionBrushCaches)
                                    {
                                        // TODO: This is slower than it needs to be
                                        int otherIndex2 = Array.IndexOf(allBrushCaches, brushCache2);
                                        if (otherIndex2 != -1)
                                        {
                                            if (!shouldBuildCollision[otherIndex2] && brushes[otherIndex2].HasCollision)
                                            {
                                                shouldBuildCollision[otherIndex2] = true;
                                                brushesToBuild++;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            for (int brushIndex = 0; brushIndex < allBrushCaches.Length; brushIndex++)
            {
                if (shouldBuildVisible[brushIndex] || shouldBuildCollision[brushIndex])
                {
                    allBrushCaches[brushIndex].ResetForBuild(newVisualPolygonIndex, newCollisionPolygonIndex);
                }
            }
            bool showProgressBar = false;

            if (DateTime.Now - buildStartTime > new TimeSpan(0, 0, 1))
            {
                showProgressBar = true;
                if (state == null && onProgressChange != null)                // Can only fire on main thread
                {
                    onProgressChange(0);
                }
            }

            // TODO: All this should be possible to parallelise.
            for (int brushIndex = 0; brushIndex < allBrushCaches.Length; brushIndex++)
            {
                if (shouldBuildVisible[brushIndex])
                {
                    // Intersecting builders can probably be calculated at edit time
                    BrushBuilder.Build(allBrushCaches[brushIndex], brushIndex, allBrushCaches, false);

                    brushesBuilt++;

                    // If we are not required to build collision (either for this brush, or at all) then we've built it!
                    if (!shouldBuildCollision[brushIndex] || !buildSettings.GenerateCollisionMeshes)
                    {
                        allBrushCaches[brushIndex].SetBuilt();
                    }

                    if (showProgressBar)
                    {
                        if (state == null && onProgressChange != null)                        // Can only fire on main thread
                        {
                            onProgressChange(brushesBuilt / (float)brushesToBuild);
                        }
                    }
                    else
                    {
                        if (DateTime.Now - buildStartTime > new TimeSpan(0, 0, 1))
                        {
                            showProgressBar = true;
                            if (state == null && onProgressChange != null)                            // Can only fire on main thread
                            {
                                onProgressChange(brushesBuilt / (float)brushesToBuild);
                            }
                        }
                    }
                }
            }

            for (int brushIndex = 0; brushIndex < allBrushCaches.Length; brushIndex++)
            {
                if (shouldBuildVisible[brushIndex])
                {
                    // Intersecting builders can probably be calculated at edit time
                    BrushCache.ReclaimStolenVisualPolygons(allBrushCaches[brushIndex]);
                }
            }

            if (buildSettings.GenerateCollisionMeshes)
            {
                for (int brushIndex = 0; brushIndex < allBrushCaches.Length; brushIndex++)
                {
                    if (shouldBuildCollision[brushIndex])
                    {
                        if (allBrushCaches[brushIndex].CollisionVisualEqual)
                        {
                        }
                        else
                        {
                            // Intersecting builders can probably be calculated at edit time
                            BrushBuilder.Build(allBrushCaches[brushIndex], brushIndex, allBrushCaches, true);

                            brushesBuilt++;

                            if (showProgressBar)
                            {
                                if (state == null && onProgressChange != null)                                // Can only fire on main thread
                                {
                                    onProgressChange(brushesBuilt / (float)brushesToBuild);
                                }
                            }
                            else
                            {
                                if (DateTime.Now - buildStartTime > new TimeSpan(0, 0, 1))
                                {
                                    showProgressBar = true;
                                    if (state == null && onProgressChange != null)                                    // Can only fire on main thread
                                    {
                                        onProgressChange(brushesBuilt / (float)brushesToBuild);
                                    }
                                }
                            }
                        }

                        allBrushCaches[brushIndex].SetBuilt();
                    }
                }

                for (int brushIndex = 0; brushIndex < allBrushCaches.Length; brushIndex++)
                {
                    if (shouldBuildCollision[brushIndex])
                    {
                        if (!allBrushCaches[brushIndex].CollisionVisualEqual)
                        {
                            // Intersecting builders can probably be calculated at edit time
                            BrushCache.ReclaimStolenCollisionPolygons(allBrushCaches[brushIndex]);
                        }
                    }
                }
            }

            time1 = DateTime.Now;

            // TODO: Can parallelise the vertex/index buffer generation, built putting them into mesh needs to be main thread
            // Triangulate the new polygons
            if (brushesBuilt > 0)
            {
                //VisualDebug.ClearAll();

                Dictionary <int, List <Polygon> > allGroupedPolygons = new Dictionary <int, List <Polygon> >();

                for (int brushIndex = 0; brushIndex < allBrushCaches.Length; brushIndex++)
                {
                    if (shouldBuildVisible[brushIndex])
                    {
                        // Grab the polygons grouped by ID, optimizing them if requested
                        Dictionary <int, List <Polygon> > groupedPolygons = null;
                        if (buildSettings.OptimizeGeometry)
                        {
                            // Return the optimal list of polygons (this call also updates BuiltVisualPolygons)
                            groupedPolygons = BrushCache.OptimizeVisual(allBrushCaches[brushIndex]);
                        }
                        else
                        {
                            groupedPolygons = allBrushCaches[brushIndex].GetGroupedBuiltVisualPolygons();
                        }

                        // Set the visual mapping from the built polygons (used by SurfaceEditor)
                        List <Polygon> polygons = allBrushCaches[brushIndex].BuiltVisualPolygons;
                        buildContext.SetVisualMapping(brushes[brushIndex], polygons);

                        // Add each set of polygons to the dictionary to be triangulated
                        foreach (KeyValuePair <int, List <Polygon> > row in groupedPolygons)
                        {
                            allGroupedPolygons.Add(row.Key, row.Value);
                        }
                    }
                }

                bool useIndividualVertices = buildSettings.GenerateLightmapUVs;                 // Generate individual vertices for unwrapped geometry

                MeshGroupManager.TriangulateNewPolygons(useIndividualVertices, allGroupedPolygons, buildContext.VisualPolygonIndex);

                if (buildSettings.GenerateCollisionMeshes)
                {
                    allGroupedPolygons.Clear();

                    for (int brushIndex = 0; brushIndex < allBrushCaches.Length; brushIndex++)
                    {
                        if (shouldBuildCollision[brushIndex])
                        {
                            if (allBrushCaches[brushIndex].CollisionVisualEqual)
                            {
                                // Intersection sets equal, so just copy visual triangulation
                                List <Polygon> builtVisualPolygons = allBrushCaches[brushIndex].BuiltVisualPolygons;
                                for (int i = 0; i < builtVisualPolygons.Count; i++)
                                {
                                    PolygonEntry visualPolygon = buildContext.VisualPolygonIndex[builtVisualPolygons[i].UniqueIndex];
                                    if (visualPolygon != null)
                                    {
                                        buildContext.CollisionPolygonIndex[builtVisualPolygons[i].UniqueIndex] = visualPolygon.DeepCopy();
                                    }
                                }
                            }
                            else
                            {
                                // Intersection sets are different, need to triangulate separately
                                Dictionary <int, List <Polygon> > groupedPolygons = null;
                                if (buildSettings.OptimizeGeometry)
                                {
                                    // Return the optimal list of polygons (this call also updates BuiltCollisionPolygons)
                                    groupedPolygons = BrushCache.OptimizeCollision(allBrushCaches[brushIndex]);
                                }
                                else
                                {
                                    groupedPolygons = allBrushCaches[brushIndex].GetGroupedBuiltCollisionPolygons();
                                }

                                // Add each set of polygons to the dictionary to be triangulated
                                foreach (KeyValuePair <int, List <Polygon> > row in groupedPolygons)
                                {
                                    allGroupedPolygons.Add(row.Key, row.Value);
                                }
                            }
                        }
                    }

                    useIndividualVertices = false;                     // Never use individual vertices for collision geometry
                    MeshGroupManager.TriangulateNewPolygons(useIndividualVertices, allGroupedPolygons, buildContext.CollisionPolygonIndex);
                }
            }

            // If multithreaded
            if (state != null)
            {
                // All done, tell the main thread to finish the build up
                readyForFinalize = true;
            }
        }