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; } }
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); }
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); } } } }
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]); } } } } } } }
public override void RecachePolygons(bool markUnbuilt) { if (brushCache == null) { brushCache = new BrushCache(); } Polygon[] cachedTransformedPolygons = GenerateTransformedPolygons(); Bounds cachedTransformedBounds = GetBoundsTransformed(); brushCache.Set(mode, cachedTransformedPolygons, cachedTransformedBounds, markUnbuilt); }
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--; } }
internal static void NotifyOfStolenCollisionPolygon(BrushCache mainCache, BrushCache brushCacheSource, Polygon newPolygon) { mainCache.stolenCollisionPolygons.Add(new KeyValuePair <Polygon, BrushCache>(newPolygon, brushCacheSource)); }
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; } } } }
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--; } } } }
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]); } } } } } }
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); } } }
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); }
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; } }