From 2e8f0d011d5042bffbbcc7d6d09a6fd45dd7cc39 Mon Sep 17 00:00:00 2001 From: vegasten Date: Tue, 3 Sep 2024 15:36:48 +0200 Subject: [PATCH 01/41] Start implementing --- .../IdProviders/SequentialIdGenerator.cs | 7 + .../HighlightSectorSplitter.cs | 313 ++++++++++++++++++ .../SectorSplitting/ISectorSplitter.cs | 2 +- .../SectorSplitting/InternalSector.cs | 3 +- .../SectorSplitting/SectorSplitterOctree.cs | 8 +- .../SectorSplitting/SectorSplitterSingle.cs | 2 +- .../SectorSplitting/SplittingUtils.cs | 34 ++ .../Operations/StidTagMapper/StidTagMapper.cs | 92 +++++ .../StidTagMapper/TagDataFromStid.cs | 33 ++ CadRevealComposer/Primitives/APrimitive.cs | 6 +- CadRevealComposer/SceneCreator.cs | 9 +- .../RvmStoreToCadRevealNodesConverter.cs | 125 +++++-- 12 files changed, 594 insertions(+), 40 deletions(-) create mode 100644 CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs create mode 100644 CadRevealComposer/Operations/StidTagMapper/StidTagMapper.cs create mode 100644 CadRevealComposer/Operations/StidTagMapper/TagDataFromStid.cs diff --git a/CadRevealComposer/IdProviders/SequentialIdGenerator.cs b/CadRevealComposer/IdProviders/SequentialIdGenerator.cs index 31a37979f..76018745f 100644 --- a/CadRevealComposer/IdProviders/SequentialIdGenerator.cs +++ b/CadRevealComposer/IdProviders/SequentialIdGenerator.cs @@ -9,6 +9,13 @@ public class SequentialIdGenerator private ulong _internalIdCounter = ulong.MaxValue; + protected SequentialIdGenerator() { } + + public SequentialIdGenerator(ulong startId) + { + _internalIdCounter = startId - 1; // It increments before selecting the id, hence -1 + } + public ulong GetNextId() { var candidate = Interlocked.Increment(ref _internalIdCounter); diff --git a/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs new file mode 100644 index 000000000..afea61030 --- /dev/null +++ b/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs @@ -0,0 +1,313 @@ +using System.Linq; + +namespace CadRevealComposer.Operations.SectorSplitting; + +using global::CadRevealComposer.IdProviders; +using global::CadRevealComposer.Primitives; +using global::CadRevealComposer.Utils; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Numerics; + +public class HighlightSectorSplitter : ISectorSplitter +{ + private const float MinDiagonalSizeAtDepth_1 = 7; // arbitrary value for min size at depth 1 + private const float MinDiagonalSizeAtDepth_2 = 4; // arbitrary value for min size at depth 2 + private const float MinDiagonalSizeAtDepth_3 = 1.5f; // arbitrary value for min size at depth 3 + private const long SectorEstimatedByteSizeBudget = 100_000; // bytes, Arbitrary value + private const float DoNotChopSectorsSmallerThanMetersInDiameter = 17.4f; // Arbitrary value + + public IEnumerable SplitIntoSectors(APrimitive[] allGeometries, ulong nextSectorId) + { + var sectorIdGenerator = new SequentialIdGenerator(nextSectorId); + + var rootSectorId = (uint)sectorIdGenerator.GetNextId(); + const string rootPath = "/0"; + yield return CreateRootSector(rootSectorId, rootPath, new BoundingBox(Vector3.Zero, Vector3.One)); + + var primitivesGroupedByDiscipline = allGeometries.GroupBy(x => x.Discipline); + + var sectors = new List(); + foreach (var disciplineGroup in primitivesGroupedByDiscipline) + { + var geometryGroups = disciplineGroup.GroupBy(x => x.TreeIndex); // Group by treeindex to avoid having one treeindex uneccessary many sectors + var nodes = SplittingUtils.ConvertPrimitiveGroupsToNodes(geometryGroups); + + // TODO: Currently ignoring outlierNodes + (Node[] regularNodes, Node[] outlierNodes) = nodes.SplitNodesIntoRegularAndOutlierNodes(); + + // var boundingBox = regularNodes.CalculateBoundingBox(); + //var startSplittingDepth = 3; //CalculateStartSplittingDepth(boundingBox); // Manually setting, because it always was 1 + //sectors.AddRange( + // SplitIntoSectorsRecursive(regularNodes, 1, rootPath, rootSectorId, sectorIdGenerator, startSplittingDepth) + //); + + sectors.AddRange(SplitIntoTreeIndexSectors(regularNodes, rootPath, rootSectorId, sectorIdGenerator)); + } + + foreach (var sector in sectors) + { + yield return sector; + } + } + + private IEnumerable SplitIntoTreeIndexSectors( + Node[] nodes, + string rootPath, + uint rootSectorId, + SequentialIdGenerator sectorIdGenerator + ) + { + var nodesUsed = 0; + + while (nodesUsed < nodes.Length) + { + var nodesByBudget = GetNodesByBudgetSimple(nodes, nodesUsed).ToArray(); + nodesUsed += nodesByBudget.Length; + + var sectorId = (uint)sectorIdGenerator.GetNextId(); + var subtreeBoundingBox = nodesByBudget.CalculateBoundingBox(); + + yield return CreateSector(nodesByBudget, sectorId, rootSectorId, rootPath, 1, subtreeBoundingBox); + } + } + + private IEnumerable SplitIntoSectorsRecursive( + Node[] nodes, + int recursiveDepth, + string parentPath, + uint? parentSectorId, + SequentialIdGenerator sectorIdGenerator, + int depthToStartSplittingGeometry + ) + { + if (nodes.Length == 0) + { + yield break; + } + + var actualDepth = Math.Max(1, recursiveDepth - depthToStartSplittingGeometry + 1); + + var subtreeBoundingBox = nodes.CalculateBoundingBox(); + + var mainVoxelNodes = Array.Empty(); + Node[] subVoxelNodes; + + if (recursiveDepth < depthToStartSplittingGeometry) + { + subVoxelNodes = nodes; + } + else + { + // fill main voxel according to budget + var additionalMainVoxelNodesByBudget = GetNodesByBudget( + nodes.ToArray(), + SectorEstimatedByteSizeBudget, + actualDepth + ) + .ToList(); + mainVoxelNodes = mainVoxelNodes.Concat(additionalMainVoxelNodesByBudget).ToArray(); + subVoxelNodes = nodes.Except(mainVoxelNodes).ToArray(); + } + + if (!subVoxelNodes.Any()) + { + var sectorId = (uint)sectorIdGenerator.GetNextId(); + + yield return CreateSector( + mainVoxelNodes, + sectorId, + parentSectorId, + parentPath, + actualDepth, + subtreeBoundingBox + ); + } + else + { + string parentPathForChildren = parentPath; + uint? parentSectorIdForChildren = parentSectorId; + + var geometries = mainVoxelNodes.SelectMany(n => n.Geometries).ToArray(); + + // Should we keep empty sectors???? yes no? + if (geometries.Any() || subVoxelNodes.Any()) + { + var sectorId = (uint)sectorIdGenerator.GetNextId(); + var path = $"{parentPath}/{sectorId}"; + + yield return CreateSector( + mainVoxelNodes, + sectorId, + parentSectorId, + parentPath, + actualDepth, + subtreeBoundingBox + ); + + parentPathForChildren = path; + parentSectorIdForChildren = sectorId; + } + + var sizeOfSubVoxelNodes = subVoxelNodes.Sum(x => x.EstimatedByteSize); + var subVoxelDiagonal = subVoxelNodes.CalculateBoundingBox().Diagonal; + + if ( + subVoxelDiagonal < DoNotChopSectorsSmallerThanMetersInDiameter + || sizeOfSubVoxelNodes < SectorEstimatedByteSizeBudget + ) + { + var sectors = SplitIntoSectorsRecursive( + subVoxelNodes, + recursiveDepth + 1, + parentPathForChildren, + parentSectorIdForChildren, + sectorIdGenerator, + depthToStartSplittingGeometry + ); + foreach (var sector in sectors) + { + yield return sector; + } + + yield break; + } + + var voxels = subVoxelNodes + .GroupBy(node => SplittingUtils.CalculateVoxelKeyForNode(node, subtreeBoundingBox)) + .OrderBy(x => x.Key) + .ToImmutableList(); + + foreach (var voxelGroup in voxels) + { + if (voxelGroup.Key == SplittingUtils.MainVoxel) + { + throw new Exception( + "Main voxel should not appear here. Main voxel should be processed separately." + ); + } + + var sectors = SplitIntoSectorsRecursive( + voxelGroup.ToArray(), + recursiveDepth + 1, + parentPathForChildren, + parentSectorIdForChildren, + sectorIdGenerator, + depthToStartSplittingGeometry + ); + foreach (var sector in sectors) + { + yield return sector; + } + } + } + } + + private static IEnumerable GetNodesByBudgetSimple(IReadOnlyList nodes, int indexToStart) + { + var byteSizeBudget = SectorEstimatedByteSizeBudget; + + for (int i = indexToStart; i < nodes.Count; i++) + { + if (byteSizeBudget < 0) + { + yield break; + } + + var node = nodes[i]; + byteSizeBudget -= node.EstimatedByteSize; + + yield return node; + } + } + + private static IEnumerable GetNodesByBudget(IReadOnlyList nodes, long byteSizeBudget, int actualDepth) + { + var selectedNodes = actualDepth switch + { + 1 => nodes.Where(x => x.Diagonal >= MinDiagonalSizeAtDepth_1).ToArray(), + 2 => nodes.Where(x => x.Diagonal >= MinDiagonalSizeAtDepth_2).ToArray(), + 3 => nodes.Where(x => x.Diagonal >= MinDiagonalSizeAtDepth_3).ToArray(), + _ => nodes.ToArray(), + }; + + var nodesInPrioritizedOrder = selectedNodes.OrderByDescending(x => x.Diagonal); + + var nodeArray = nodesInPrioritizedOrder.ToArray(); + var byteSizeBudgetLeft = byteSizeBudget; + for (int i = 0; i < nodeArray.Length; i++) + { + if ((byteSizeBudgetLeft < 0) && nodeArray.Length - i > 10) + { + yield break; + } + + var node = nodeArray[i]; + byteSizeBudgetLeft -= node.EstimatedByteSize; + + yield return node; + } + } + + private InternalSector CreateRootSector(uint sectorId, string path, BoundingBox subtreeBoundingBox) + { + return new InternalSector(sectorId, null, 0, path, 0, 0, Array.Empty(), subtreeBoundingBox, null); + } + + private InternalSector CreateSector( + Node[] nodes, + uint sectorId, + uint? parentSectorId, + string parentPath, + int depth, + BoundingBox subtreeBoundingBox + ) + { + var path = $"{parentPath}/{sectorId}"; + + var minDiagonal = nodes.Any() ? nodes.Min(n => n.Diagonal) : 0; + var maxDiagonal = nodes.Any() ? nodes.Max(n => n.Diagonal) : 0; + var geometries = nodes.SelectMany(n => n.Geometries).ToArray(); + var geometryBoundingBox = geometries.CalculateBoundingBox(); + + return new InternalSector( + sectorId, + parentSectorId, + depth, + path, + minDiagonal, + maxDiagonal, + geometries, + subtreeBoundingBox, + geometryBoundingBox + ); + } + + private static int CalculateStartSplittingDepth(BoundingBox boundingBox) + { + // If we start splitting too low in the octree, we might end up with way too many sectors + // If we start splitting too high, we might get some large sectors with a lot of data, which always will be prioritized + + var diagonalAtDepth = boundingBox.Diagonal; + int depth = 1; + // Todo: Arbitrary numbers in this method based on gut feeling. + // Assumes 3 levels of "LOD Splitting": + // 300x300 for Very large parts + // 150x150 for large parts + // 75x75 for > 1 meter parts + // 37,5 etc by budget + const float level1SectorsMaxDiagonal = 500; + while (diagonalAtDepth > level1SectorsMaxDiagonal) + { + diagonalAtDepth /= 2; + depth++; + } + + Console.WriteLine( + $"Diagonal was: {boundingBox.Diagonal:F2}m. Starting splitting at depth {depth}. Expecting a diagonal of maximum {diagonalAtDepth:F2}m" + ); + return depth; + } +} diff --git a/CadRevealComposer/Operations/SectorSplitting/ISectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/ISectorSplitter.cs index 941da1676..8097f7aab 100644 --- a/CadRevealComposer/Operations/SectorSplitting/ISectorSplitter.cs +++ b/CadRevealComposer/Operations/SectorSplitting/ISectorSplitter.cs @@ -5,5 +5,5 @@ public interface ISectorSplitter { - public IEnumerable SplitIntoSectors(APrimitive[] allGeometries); + public IEnumerable SplitIntoSectors(APrimitive[] allGeometries, ulong nextSectorId); } diff --git a/CadRevealComposer/Operations/SectorSplitting/InternalSector.cs b/CadRevealComposer/Operations/SectorSplitting/InternalSector.cs index 4fee2d617..943a19007 100644 --- a/CadRevealComposer/Operations/SectorSplitting/InternalSector.cs +++ b/CadRevealComposer/Operations/SectorSplitting/InternalSector.cs @@ -11,5 +11,6 @@ public record InternalSector( float MaxNodeDiagonal, APrimitive[] Geometries, BoundingBox SubtreeBoundingBox, - BoundingBox? GeometryBoundingBox + BoundingBox? GeometryBoundingBox, + bool IsHighlightSector = false ); diff --git a/CadRevealComposer/Operations/SectorSplitting/SectorSplitterOctree.cs b/CadRevealComposer/Operations/SectorSplitting/SectorSplitterOctree.cs index 55f80a2c4..97cf3279f 100644 --- a/CadRevealComposer/Operations/SectorSplitting/SectorSplitterOctree.cs +++ b/CadRevealComposer/Operations/SectorSplitting/SectorSplitterOctree.cs @@ -1,11 +1,11 @@ namespace CadRevealComposer.Operations.SectorSplitting; +using IdProviders; +using Primitives; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using IdProviders; -using Primitives; using Utils; public class SectorSplitterOctree : ISectorSplitter @@ -24,9 +24,9 @@ public class SectorSplitterOctree : ISectorSplitter private readonly TooFewInstancesHandler _tooFewInstancesHandler = new(); private readonly TooFewPrimitivesHandler _tooFewPrimitivesHandler = new(); - public IEnumerable SplitIntoSectors(APrimitive[] allGeometries) + public IEnumerable SplitIntoSectors(APrimitive[] allGeometries, ulong nextSectorId) { - var sectorIdGenerator = new SequentialIdGenerator(); + var sectorIdGenerator = new SequentialIdGenerator(nextSectorId); var allNodes = SplittingUtils.ConvertPrimitivesToNodes(allGeometries); (Node[] regularNodes, Node[] outlierNodes) = allNodes.SplitNodesIntoRegularAndOutlierNodes(); diff --git a/CadRevealComposer/Operations/SectorSplitting/SectorSplitterSingle.cs b/CadRevealComposer/Operations/SectorSplitting/SectorSplitterSingle.cs index 0005903ca..f8818cb4f 100644 --- a/CadRevealComposer/Operations/SectorSplitting/SectorSplitterSingle.cs +++ b/CadRevealComposer/Operations/SectorSplitting/SectorSplitterSingle.cs @@ -8,7 +8,7 @@ public class SectorSplitterSingle : ISectorSplitter { - public IEnumerable SplitIntoSectors(APrimitive[] allGeometries) + public IEnumerable SplitIntoSectors(APrimitive[] allGeometries, ulong nextSectorId) { yield return CreateRootSector(0, allGeometries); } diff --git a/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs b/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs index e0cf276e7..c3404ae94 100644 --- a/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs +++ b/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs @@ -221,4 +221,38 @@ float outlierGroupingDistance return listOfGroups; } + + public static Node[] ConvertPrimitiveGroupsToNodes(IEnumerable> geometryGroups) + { + float sizeCutoff = 0.1f; + + return geometryGroups + .Select(g => + { + var allGeometries = g.Select(x => x).ToArray(); + var orderedBySizeDescending = allGeometries.OrderByDescending(x => x.AxisAlignedBoundingBox.Diagonal); + APrimitive[] geometries; + if (orderedBySizeDescending.First().AxisAlignedBoundingBox.Diagonal < sizeCutoff) + { + geometries = allGeometries; + } + else + { + geometries = allGeometries.Where(x => x.AxisAlignedBoundingBox.Diagonal > sizeCutoff).ToArray(); + } + var boundingBox = geometries.CalculateBoundingBox(); + if (boundingBox == null) + { + throw new Exception("Unexpected error, the bounding box should not have been null."); + } + return new Node( + g.Key, + geometries, + geometries.Sum(DrawCallEstimator.EstimateByteSize), + EstimatedTriangleCount: DrawCallEstimator.Estimate(geometries).EstimatedTriangleCount, + boundingBox + ); + }) + .ToArray(); + } } diff --git a/CadRevealComposer/Operations/StidTagMapper/StidTagMapper.cs b/CadRevealComposer/Operations/StidTagMapper/StidTagMapper.cs new file mode 100644 index 000000000..0fdf6bf90 --- /dev/null +++ b/CadRevealComposer/Operations/StidTagMapper/StidTagMapper.cs @@ -0,0 +1,92 @@ +namespace CadRevealComposer.Operations; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Newtonsoft.Json; + +public static class StidTagMapper +{ + public static CadRevealNode[] FilterNodesWithTag(CadRevealNode[] nodes) + { + // string filename = "troa_tags.json"; + string path = @"\\ws1611\AppResources\TrollA\Tags_temp2024-08-08\troa_tags.json"; + + var tagDataFromStid = ParseFromJson(path); + return FilterNodesByStidTags(nodes, tagDataFromStid); + } + + private static TagDataFromStid[] ParseFromJson(string path) + { + var json = File.ReadAllText(path); + + return JsonConvert.DeserializeObject(json)!; + } + + private static CadRevealNode[] FilterNodesByStidTags( + IReadOnlyList revealNodes, + TagDataFromStid[] tagDataFromStid + ) + { + var acceptedNodes = new List(); + + var tagLookup = tagDataFromStid.ToDictionary(x => x.TagNo.Trim(), x => x, StringComparer.OrdinalIgnoreCase); + + var lineTags = tagDataFromStid.Where(x => x.TagCategory == 6).ToArray(); + + foreach (CadRevealNode revealNode in revealNodes) + { + if (revealNode.Attributes.TryGetValue("Tag", out var pdmsTag)) + { + if (tagLookup.TryGetValue(pdmsTag, out var stidTag)) + { + acceptedNodes.Add(revealNode); + continue; + } + } + + if (tagLookup.TryGetValue(revealNode.Name.Trim(['/']).Trim(), out var value)) + { + acceptedNodes.Add(revealNode); + continue; + } + + if (revealNode.Attributes.TryGetValue("Type", out var type) && type == "PIPE") + { + var baseLineTag = revealNode.Name.Split("_")[0].TrimStart('/').Trim(); + var matchingLineTags = lineTags + .Where(x => x.TagNo.Contains(baseLineTag, StringComparison.OrdinalIgnoreCase)) + .ToArray(); + + if (pdmsTag != null) + { + var pdmsTagWithoutStars = pdmsTag!.Trim('*'); + var pdmsTagMatchingLineTags = lineTags + .Where(x => x.TagNo.Contains(pdmsTagWithoutStars, StringComparison.OrdinalIgnoreCase)) + .ToArray(); + if (pdmsTagMatchingLineTags.Any()) + { + if (pdmsTagMatchingLineTags.Length == 1) + { + acceptedNodes.Add(revealNode); + continue; + } + else + { + acceptedNodes.Add(revealNode); + continue; + } + } + } + if (matchingLineTags.Any()) + { + acceptedNodes.Add(revealNode); + continue; + } + } + } + + return acceptedNodes.ToArray(); + } +} diff --git a/CadRevealComposer/Operations/StidTagMapper/TagDataFromStid.cs b/CadRevealComposer/Operations/StidTagMapper/TagDataFromStid.cs new file mode 100644 index 000000000..92597796d --- /dev/null +++ b/CadRevealComposer/Operations/StidTagMapper/TagDataFromStid.cs @@ -0,0 +1,33 @@ +namespace CadRevealComposer.Operations; + +using Newtonsoft.Json; + +[JsonObject] +public class TagDataFromStid +{ + public string TagNo { get; set; } + public string Description { get; set; } + public string TagStatus { get; set; } + public int TagCategory { get; set; } + public string TagCategoryDescription { get; set; } + public string TagType { get; set; } + public string UpdatedDate { get; set; } + public string LocationCode { get; set; } + public string DisciplineCode { get; set; } + public string ContrCode { get; set; } + public string System { get; set; } + public string ProjectCode { get; set; } + public string PoNo { get; set; } + public string PlantNo { get; set; } + public float? XCoordinate { get; set; } + public float? YCoordinate { get; set; } + public float? ZCoordinate { get; set; } + public AdditionalFields[] AdditionalFields { get; set; } +} + +[JsonObject] +public class AdditionalFields +{ + public string Type { get; set; } + public string Value { get; set; } +} diff --git a/CadRevealComposer/Primitives/APrimitive.cs b/CadRevealComposer/Primitives/APrimitive.cs index 3b0d771d7..a0a391324 100644 --- a/CadRevealComposer/Primitives/APrimitive.cs +++ b/CadRevealComposer/Primitives/APrimitive.cs @@ -157,4 +157,8 @@ public abstract record APrimitive( [property: ProtoMember(1)] ulong TreeIndex, [property: ProtoMember(2)] Color Color, [property: ProtoMember(3)] BoundingBox AxisAlignedBoundingBox -); +) +{ + public int Priority { get; init; } = 0; + public string? Discipline { get; init; } = null; +} diff --git a/CadRevealComposer/SceneCreator.cs b/CadRevealComposer/SceneCreator.cs index afd55d09a..66a0c0b54 100644 --- a/CadRevealComposer/SceneCreator.cs +++ b/CadRevealComposer/SceneCreator.cs @@ -154,7 +154,14 @@ private static SectorInfo SerializeSector(InternalSector p, string outputDirecto { var (estimatedTriangleCount, estimatedDrawCalls) = DrawCallEstimator.Estimate(p.Geometries); - var sectorFilename = p.Geometries.Any() ? $"sector_{p.SectorId}.glb" : null; + string? sectorFilename = !p.IsHighlightSector + ? p.Geometries.Any() + ? $"sector_{p.SectorId}.glb" + : null + : p.Geometries.Any() + ? $"highlight_sector_{p.SectorId}.glb" + : null; + var sectorInfo = new SectorInfo( SectorId: p.SectorId, ParentSectorId: p.ParentSectorId, diff --git a/CadRevealRvmProvider/RvmStoreToCadRevealNodesConverter.cs b/CadRevealRvmProvider/RvmStoreToCadRevealNodesConverter.cs index 087e0e631..1974f1daa 100644 --- a/CadRevealRvmProvider/RvmStoreToCadRevealNodesConverter.cs +++ b/CadRevealRvmProvider/RvmStoreToCadRevealNodesConverter.cs @@ -17,16 +17,17 @@ NodeNameFiltering nodeNameFiltering ) { var failedPrimitiveConversionsLogObject = new FailedPrimitivesLogObject(); - var cadRevealRootNodes = rvmStore - .RvmFiles.SelectMany(f => f.Model.Children) - .Select(root => - CollectGeometryNodesRecursive( - root, - parent: null, - treeIndexGenerator, - nodeNameFiltering, - failedPrimitiveConversionsLogObject - ) + var cadRevealRootNodes = rvmStore.RvmFiles + .SelectMany(f => f.Model.Children) + .Select( + root => + CollectGeometryNodesRecursive( + root, + parent: null, + treeIndexGenerator, + nodeNameFiltering, + failedPrimitiveConversionsLogObject + ) ) .WhereNotNull() .ToArray(); @@ -41,9 +42,69 @@ NodeNameFiltering nodeNameFiltering Trace.Assert(subBoundingBox != null, "Root node has no bounding box. Are there any meshes in the input?"); var allNodes = cadRevealRootNodes.SelectMany(CadRevealNode.GetAllNodesFlat).ToArray(); + + SetPriorityForHighlightSplitting(allNodes); + return allNodes; } + private static void SetPriorityForHighlightSplitting(CadRevealNode[] nodes) + { + var disciplineFilteredNodes = FilterAndSetDiscipline(nodes).ToArray(); + + // TODO + // Are we going to use the custom STID mapper, or should we wait for a more official solution? + // Notes: Uncommenting code below requires that a relevant file exists on build server + var tagMappingAndDisciplineFilteredNodes = disciplineFilteredNodes; // StidTagMapper.FilterNodesWithTag(disciplineFilteredNodes); + + var tagAndDisciplineFilteredNodes = FilterByIfTagExists(tagMappingAndDisciplineFilteredNodes).ToArray(); + SetPriortyOnNodesAndChildren(tagAndDisciplineFilteredNodes); + } + + private static IEnumerable FilterAndSetDiscipline(CadRevealNode[] nodes) + { + foreach (var node in nodes) + { + var discipline = node.Attributes.GetValueOrNull("Discipline"); + + if (discipline != null && discipline != "STRU") + { + var children = CadRevealNode.GetAllNodesFlat(node); + foreach (var child in children) + { + child.Geometries = child.Geometries.Select(g => g with { Discipline = discipline }).ToArray(); + } + + yield return node; + } + } + } + + private static IEnumerable FilterByIfTagExists(CadRevealNode[] nodes) + { + foreach (var node in nodes) + { + var tag = node.Attributes.GetValueOrNull("Tag"); + + if (tag != null) + { + yield return node; + } + } + } + + private static void SetPriortyOnNodesAndChildren(CadRevealNode[] nodes) + { + foreach (var node in nodes) + { + var allChildren = CadRevealNode.GetAllNodesFlat(node); + foreach (var child in allChildren) + { + child.Geometries = child.Geometries.Select(g => g with { Priority = 1 }).ToArray(); + } + } + } + private static CadRevealNode? CollectGeometryNodesRecursive( RvmNode root, CadRevealNode? parent, @@ -69,8 +130,8 @@ FailedPrimitivesLogObject failedPrimitivesConversionLogObject if (root.Children.OfType().Any() && root.Children.OfType().Any()) { - childrenCadNodes = root - .Children.Select(child => + childrenCadNodes = root.Children + .Select(child => { switch (child) { @@ -102,16 +163,17 @@ FailedPrimitivesLogObject failedPrimitivesConversionLogObject } else { - childrenCadNodes = root - .Children.OfType() - .Select(n => - CollectGeometryNodesRecursive( - n, - newNode, - treeIndexGenerator, - nodeNameFiltering, - failedPrimitivesConversionLogObject - ) + childrenCadNodes = root.Children + .OfType() + .Select( + n => + CollectGeometryNodesRecursive( + n, + newNode, + treeIndexGenerator, + nodeNameFiltering, + failedPrimitivesConversionLogObject + ) ) .WhereNotNull() .ToArray(); @@ -119,20 +181,21 @@ FailedPrimitivesLogObject failedPrimitivesConversionLogObject } newNode.Geometries = rvmGeometries - .SelectMany(primitive => - RvmPrimitiveToAPrimitive.FromRvmPrimitive( - newNode.TreeIndex, - primitive, - root, - failedPrimitivesConversionLogObject - ) + .SelectMany( + primitive => + RvmPrimitiveToAPrimitive.FromRvmPrimitive( + newNode.TreeIndex, + primitive, + root, + failedPrimitivesConversionLogObject + ) ) .ToArray(); newNode.Children = childrenCadNodes; - var primitiveBoundingBoxes = root - .Children.OfType() + var primitiveBoundingBoxes = root.Children + .OfType() .Select(x => x.CalculateAxisAlignedBoundingBox()?.ToCadRevealBoundingBox()) .WhereNotNull() .ToArray(); From f6f13e9f52ad5481299e8abebef456e25a4e7893 Mon Sep 17 00:00:00 2001 From: vegasten Date: Wed, 4 Sep 2024 13:03:19 +0200 Subject: [PATCH 02/41] Create highlight sectors --- CadRevealComposer/CadRevealComposerRunner.cs | 2 +- .../HighlightSectorSplitter.cs | 8 +- .../HighlightSplittingUtils.cs | 101 ++++++++++++++ .../SectorSplitting/SplittingUtils.cs | 34 ----- .../StidTagMapper/TagDataFromStid.cs | 34 ++--- CadRevealComposer/SceneCreator.cs | 2 +- .../RvmStoreToCadRevealNodesConverter.cs | 125 +++++------------- 7 files changed, 157 insertions(+), 149 deletions(-) create mode 100644 CadRevealComposer/Operations/SectorSplitting/HighlightSplittingUtils.cs diff --git a/CadRevealComposer/CadRevealComposerRunner.cs b/CadRevealComposer/CadRevealComposerRunner.cs index e00fc6bab..58f520101 100644 --- a/CadRevealComposer/CadRevealComposerRunner.cs +++ b/CadRevealComposer/CadRevealComposerRunner.cs @@ -158,7 +158,7 @@ ComposerParameters composerParameters splitter = new SectorSplitterOctree(); } - var sectors = splitter.SplitIntoSectors(allPrimitives).OrderBy(x => x.SectorId).ToArray(); + var sectors = splitter.SplitIntoSectors(allPrimitives, 0).OrderBy(x => x.SectorId).ToArray(); Console.WriteLine($"Split into {sectors.Length} sectors in {stopwatch.Elapsed}"); diff --git a/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs index afea61030..d3ff4fcca 100644 --- a/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs +++ b/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs @@ -2,14 +2,14 @@ namespace CadRevealComposer.Operations.SectorSplitting; -using global::CadRevealComposer.IdProviders; -using global::CadRevealComposer.Primitives; -using global::CadRevealComposer.Utils; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Numerics; +using global::CadRevealComposer.IdProviders; +using global::CadRevealComposer.Primitives; +using global::CadRevealComposer.Utils; public class HighlightSectorSplitter : ISectorSplitter { @@ -33,7 +33,7 @@ public IEnumerable SplitIntoSectors(APrimitive[] allGeometries, foreach (var disciplineGroup in primitivesGroupedByDiscipline) { var geometryGroups = disciplineGroup.GroupBy(x => x.TreeIndex); // Group by treeindex to avoid having one treeindex uneccessary many sectors - var nodes = SplittingUtils.ConvertPrimitiveGroupsToNodes(geometryGroups); + var nodes = HighlightSplittingUtils.ConvertPrimitiveGroupsToNodes(geometryGroups); // TODO: Currently ignoring outlierNodes (Node[] regularNodes, Node[] outlierNodes) = nodes.SplitNodesIntoRegularAndOutlierNodes(); diff --git a/CadRevealComposer/Operations/SectorSplitting/HighlightSplittingUtils.cs b/CadRevealComposer/Operations/SectorSplitting/HighlightSplittingUtils.cs new file mode 100644 index 000000000..fcebf8756 --- /dev/null +++ b/CadRevealComposer/Operations/SectorSplitting/HighlightSplittingUtils.cs @@ -0,0 +1,101 @@ +namespace CadRevealComposer.Operations.SectorSplitting; + +using System; +using System.Collections.Generic; +using System.Linq; +using CadRevealComposer.Primitives; +using CadRevealComposer.Utils; + +public static class HighlightSplittingUtils +{ + public static void SetPriorityForHighlightSplitting(CadRevealNode[] nodes) + { + var disciplineFilteredNodes = FilterAndSetDiscipline(nodes).ToArray(); + + // TODO + // Are we going to use the custom STID mapper, or should we wait for a more official solution? + // Notes: Uncommenting code below requires that a relevant file exists on build server + var tagMappingAndDisciplineFilteredNodes = disciplineFilteredNodes; // StidTagMapper.FilterNodesWithTag(disciplineFilteredNodes); + + var tagAndDisciplineFilteredNodes = FilterByIfTagExists(tagMappingAndDisciplineFilteredNodes).ToArray(); + SetPriortyOnNodesAndChildren(tagAndDisciplineFilteredNodes); + } + + private static IEnumerable FilterAndSetDiscipline(CadRevealNode[] nodes) + { + foreach (var node in nodes) + { + var discipline = node.Attributes.GetValueOrNull("Discipline"); + + if (discipline != null && discipline != "STRU") + { + var children = CadRevealNode.GetAllNodesFlat(node); + foreach (var child in children) + { + child.Geometries = child.Geometries.Select(g => g with { Discipline = discipline }).ToArray(); + } + + yield return node; + } + } + } + + private static IEnumerable FilterByIfTagExists(CadRevealNode[] nodes) + { + foreach (var node in nodes) + { + var tag = node.Attributes.GetValueOrNull("Tag"); + + if (tag != null) + { + yield return node; + } + } + } + + private static void SetPriortyOnNodesAndChildren(CadRevealNode[] nodes) + { + foreach (var node in nodes) + { + var allChildren = CadRevealNode.GetAllNodesFlat(node); + foreach (var child in allChildren) + { + child.Geometries = child.Geometries.Select(g => g with { Priority = 1 }).ToArray(); + } + } + } + + public static Node[] ConvertPrimitiveGroupsToNodes(IEnumerable> geometryGroups) + { + float sizeCutoff = 0.1f; + + return geometryGroups + .Select(g => + { + var allGeometries = g.Select(x => x).ToArray(); + var orderedBySizeDescending = allGeometries.OrderByDescending(x => x.AxisAlignedBoundingBox.Diagonal); + APrimitive[] geometries; + if (orderedBySizeDescending.First().AxisAlignedBoundingBox.Diagonal < sizeCutoff) + { + geometries = allGeometries; + } + else + { + geometries = allGeometries.Where(x => x.AxisAlignedBoundingBox.Diagonal > sizeCutoff).ToArray(); + } + var boundingBox = geometries.CalculateBoundingBox(); + if (boundingBox == null) + { + throw new Exception("Unexpected error, the bounding box should not have been null."); + } + return new Node( + g.Key, + geometries, + geometries.Sum(DrawCallEstimator.EstimateByteSize), + EstimatedTriangleCount: DrawCallEstimator.Estimate(geometries).EstimatedTriangleCount, + boundingBox + ); + }) + .ToArray(); + } +} diff --git a/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs b/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs index c3404ae94..e0cf276e7 100644 --- a/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs +++ b/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs @@ -221,38 +221,4 @@ float outlierGroupingDistance return listOfGroups; } - - public static Node[] ConvertPrimitiveGroupsToNodes(IEnumerable> geometryGroups) - { - float sizeCutoff = 0.1f; - - return geometryGroups - .Select(g => - { - var allGeometries = g.Select(x => x).ToArray(); - var orderedBySizeDescending = allGeometries.OrderByDescending(x => x.AxisAlignedBoundingBox.Diagonal); - APrimitive[] geometries; - if (orderedBySizeDescending.First().AxisAlignedBoundingBox.Diagonal < sizeCutoff) - { - geometries = allGeometries; - } - else - { - geometries = allGeometries.Where(x => x.AxisAlignedBoundingBox.Diagonal > sizeCutoff).ToArray(); - } - var boundingBox = geometries.CalculateBoundingBox(); - if (boundingBox == null) - { - throw new Exception("Unexpected error, the bounding box should not have been null."); - } - return new Node( - g.Key, - geometries, - geometries.Sum(DrawCallEstimator.EstimateByteSize), - EstimatedTriangleCount: DrawCallEstimator.Estimate(geometries).EstimatedTriangleCount, - boundingBox - ); - }) - .ToArray(); - } } diff --git a/CadRevealComposer/Operations/StidTagMapper/TagDataFromStid.cs b/CadRevealComposer/Operations/StidTagMapper/TagDataFromStid.cs index 92597796d..084555135 100644 --- a/CadRevealComposer/Operations/StidTagMapper/TagDataFromStid.cs +++ b/CadRevealComposer/Operations/StidTagMapper/TagDataFromStid.cs @@ -5,29 +5,29 @@ [JsonObject] public class TagDataFromStid { - public string TagNo { get; set; } - public string Description { get; set; } - public string TagStatus { get; set; } - public int TagCategory { get; set; } - public string TagCategoryDescription { get; set; } - public string TagType { get; set; } - public string UpdatedDate { get; set; } - public string LocationCode { get; set; } - public string DisciplineCode { get; set; } - public string ContrCode { get; set; } - public string System { get; set; } - public string ProjectCode { get; set; } - public string PoNo { get; set; } - public string PlantNo { get; set; } + public required string TagNo { get; set; } + public required string Description { get; set; } + public required string TagStatus { get; set; } + public required int TagCategory { get; set; } + public required string TagCategoryDescription { get; set; } + public required string? TagType { get; set; } + public required string UpdatedDate { get; set; } + public required string LocationCode { get; set; } + public required string DisciplineCode { get; set; } + public required string ContrCode { get; set; } + public required string System { get; set; } + public required string ProjectCode { get; set; } + public required string PoNo { get; set; } + public required string PlantNo { get; set; } public float? XCoordinate { get; set; } public float? YCoordinate { get; set; } public float? ZCoordinate { get; set; } - public AdditionalFields[] AdditionalFields { get; set; } + public required AdditionalFields[] AdditionalFields { get; set; } } [JsonObject] public class AdditionalFields { - public string Type { get; set; } - public string Value { get; set; } + public required string Type { get; set; } + public required string Value { get; set; } } diff --git a/CadRevealComposer/SceneCreator.cs b/CadRevealComposer/SceneCreator.cs index 66a0c0b54..0c2bcc062 100644 --- a/CadRevealComposer/SceneCreator.cs +++ b/CadRevealComposer/SceneCreator.cs @@ -138,7 +138,7 @@ Sector FromSector(SectorInfo sector) JsonUtils.JsonSerializeToFile(scene, scenePath, writeIndented: EnvUtil.IsDebugBuild); // We don't want indentation in prod, it doubles the size. Format in an editor if needed. } - public static void ExportSectorGeometries( + private static void ExportSectorGeometries( IReadOnlyList geometries, string sectorFilename, string? outputDirectory diff --git a/CadRevealRvmProvider/RvmStoreToCadRevealNodesConverter.cs b/CadRevealRvmProvider/RvmStoreToCadRevealNodesConverter.cs index 1974f1daa..e998e6c2a 100644 --- a/CadRevealRvmProvider/RvmStoreToCadRevealNodesConverter.cs +++ b/CadRevealRvmProvider/RvmStoreToCadRevealNodesConverter.cs @@ -4,6 +4,7 @@ namespace CadRevealRvmProvider.Converters; using CadRevealComposer; using CadRevealComposer.IdProviders; using CadRevealComposer.Operations; +using CadRevealComposer.Operations.SectorSplitting; using CadRevealComposer.Utils; using RvmSharp.Containers; using RvmSharp.Primitives; @@ -17,17 +18,16 @@ NodeNameFiltering nodeNameFiltering ) { var failedPrimitiveConversionsLogObject = new FailedPrimitivesLogObject(); - var cadRevealRootNodes = rvmStore.RvmFiles - .SelectMany(f => f.Model.Children) - .Select( - root => - CollectGeometryNodesRecursive( - root, - parent: null, - treeIndexGenerator, - nodeNameFiltering, - failedPrimitiveConversionsLogObject - ) + var cadRevealRootNodes = rvmStore + .RvmFiles.SelectMany(f => f.Model.Children) + .Select(root => + CollectGeometryNodesRecursive( + root, + parent: null, + treeIndexGenerator, + nodeNameFiltering, + failedPrimitiveConversionsLogObject + ) ) .WhereNotNull() .ToArray(); @@ -43,68 +43,11 @@ NodeNameFiltering nodeNameFiltering var allNodes = cadRevealRootNodes.SelectMany(CadRevealNode.GetAllNodesFlat).ToArray(); - SetPriorityForHighlightSplitting(allNodes); + HighlightSplittingUtils.SetPriorityForHighlightSplitting(allNodes); return allNodes; } - private static void SetPriorityForHighlightSplitting(CadRevealNode[] nodes) - { - var disciplineFilteredNodes = FilterAndSetDiscipline(nodes).ToArray(); - - // TODO - // Are we going to use the custom STID mapper, or should we wait for a more official solution? - // Notes: Uncommenting code below requires that a relevant file exists on build server - var tagMappingAndDisciplineFilteredNodes = disciplineFilteredNodes; // StidTagMapper.FilterNodesWithTag(disciplineFilteredNodes); - - var tagAndDisciplineFilteredNodes = FilterByIfTagExists(tagMappingAndDisciplineFilteredNodes).ToArray(); - SetPriortyOnNodesAndChildren(tagAndDisciplineFilteredNodes); - } - - private static IEnumerable FilterAndSetDiscipline(CadRevealNode[] nodes) - { - foreach (var node in nodes) - { - var discipline = node.Attributes.GetValueOrNull("Discipline"); - - if (discipline != null && discipline != "STRU") - { - var children = CadRevealNode.GetAllNodesFlat(node); - foreach (var child in children) - { - child.Geometries = child.Geometries.Select(g => g with { Discipline = discipline }).ToArray(); - } - - yield return node; - } - } - } - - private static IEnumerable FilterByIfTagExists(CadRevealNode[] nodes) - { - foreach (var node in nodes) - { - var tag = node.Attributes.GetValueOrNull("Tag"); - - if (tag != null) - { - yield return node; - } - } - } - - private static void SetPriortyOnNodesAndChildren(CadRevealNode[] nodes) - { - foreach (var node in nodes) - { - var allChildren = CadRevealNode.GetAllNodesFlat(node); - foreach (var child in allChildren) - { - child.Geometries = child.Geometries.Select(g => g with { Priority = 1 }).ToArray(); - } - } - } - private static CadRevealNode? CollectGeometryNodesRecursive( RvmNode root, CadRevealNode? parent, @@ -130,8 +73,8 @@ FailedPrimitivesLogObject failedPrimitivesConversionLogObject if (root.Children.OfType().Any() && root.Children.OfType().Any()) { - childrenCadNodes = root.Children - .Select(child => + childrenCadNodes = root + .Children.Select(child => { switch (child) { @@ -163,17 +106,16 @@ FailedPrimitivesLogObject failedPrimitivesConversionLogObject } else { - childrenCadNodes = root.Children - .OfType() - .Select( - n => - CollectGeometryNodesRecursive( - n, - newNode, - treeIndexGenerator, - nodeNameFiltering, - failedPrimitivesConversionLogObject - ) + childrenCadNodes = root + .Children.OfType() + .Select(n => + CollectGeometryNodesRecursive( + n, + newNode, + treeIndexGenerator, + nodeNameFiltering, + failedPrimitivesConversionLogObject + ) ) .WhereNotNull() .ToArray(); @@ -181,21 +123,20 @@ FailedPrimitivesLogObject failedPrimitivesConversionLogObject } newNode.Geometries = rvmGeometries - .SelectMany( - primitive => - RvmPrimitiveToAPrimitive.FromRvmPrimitive( - newNode.TreeIndex, - primitive, - root, - failedPrimitivesConversionLogObject - ) + .SelectMany(primitive => + RvmPrimitiveToAPrimitive.FromRvmPrimitive( + newNode.TreeIndex, + primitive, + root, + failedPrimitivesConversionLogObject + ) ) .ToArray(); newNode.Children = childrenCadNodes; - var primitiveBoundingBoxes = root.Children - .OfType() + var primitiveBoundingBoxes = root + .Children.OfType() .Select(x => x.CalculateAxisAlignedBoundingBox()?.ToCadRevealBoundingBox()) .WhereNotNull() .ToArray(); From 1bedb6491f18fdbd98d329dc9532e51b9df29b04 Mon Sep 17 00:00:00 2001 From: vegasten Date: Wed, 4 Sep 2024 13:51:36 +0200 Subject: [PATCH 03/41] Actually create highlight sectors --- CadRevealComposer/CadRevealComposerRunner.cs | 34 +++++++++++- .../HighlightSectorSplitter.cs | 52 +++++-------------- 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/CadRevealComposer/CadRevealComposerRunner.cs b/CadRevealComposer/CadRevealComposerRunner.cs index 58f520101..96807724e 100644 --- a/CadRevealComposer/CadRevealComposerRunner.cs +++ b/CadRevealComposer/CadRevealComposerRunner.cs @@ -158,12 +158,44 @@ ComposerParameters composerParameters splitter = new SectorSplitterOctree(); } + var highlightSplitter = new HighlightSectorSplitter(); + var sectors = splitter.SplitIntoSectors(allPrimitives, 0).OrderBy(x => x.SectorId).ToArray(); + var prioritizedForHiglighting = allPrimitives.Where(x => x.Priority == 1).ToArray(); + + var nextSectorId = sectors.Last().SectorId + 1; + var highlightSectors = highlightSplitter + .SplitIntoSectors(prioritizedForHiglighting, nextSectorId) + .OrderBy(x => x.SectorId) + .ToArray(); + + // Remove redundant root and point to the original root + var originalRootId = sectors.First().SectorId; + var redundantRootId = highlightSectors.First().SectorId; + highlightSectors = highlightSectors + .Skip(1) + .Select(sector => + { + return sector.ParentSectorId == redundantRootId + ? (sector with { ParentSectorId = originalRootId }) + : sector; + }) + .ToArray(); + Console.WriteLine($"Split into {sectors.Length} sectors in {stopwatch.Elapsed}"); + var allSectors = sectors.Concat(highlightSectors).ToArray(); + stopwatch.Restart(); - SceneCreator.CreateSceneFile(allPrimitives, outputDirectory, modelParameters, maxTreeIndex, stopwatch, sectors); + SceneCreator.CreateSceneFile( + allPrimitives, + outputDirectory, + modelParameters, + maxTreeIndex, + stopwatch, + allSectors + ); Console.WriteLine($"Wrote scene file in {stopwatch.Elapsed}"); stopwatch.Restart(); } diff --git a/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs index d3ff4fcca..aef4c4858 100644 --- a/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs +++ b/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs @@ -1,15 +1,13 @@ -using System.Linq; - -namespace CadRevealComposer.Operations.SectorSplitting; +namespace CadRevealComposer.Operations.SectorSplitting; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Numerics; -using global::CadRevealComposer.IdProviders; -using global::CadRevealComposer.Primitives; -using global::CadRevealComposer.Utils; +using CadRevealComposer.IdProviders; +using CadRevealComposer.Primitives; +using CadRevealComposer.Utils; public class HighlightSectorSplitter : ISectorSplitter { @@ -35,21 +33,21 @@ public IEnumerable SplitIntoSectors(APrimitive[] allGeometries, var geometryGroups = disciplineGroup.GroupBy(x => x.TreeIndex); // Group by treeindex to avoid having one treeindex uneccessary many sectors var nodes = HighlightSplittingUtils.ConvertPrimitiveGroupsToNodes(geometryGroups); - // TODO: Currently ignoring outlierNodes + // Ignore outlier nodes + // TODO: Decide if this is the right thing to do (Node[] regularNodes, Node[] outlierNodes) = nodes.SplitNodesIntoRegularAndOutlierNodes(); - // var boundingBox = regularNodes.CalculateBoundingBox(); - //var startSplittingDepth = 3; //CalculateStartSplittingDepth(boundingBox); // Manually setting, because it always was 1 - //sectors.AddRange( - // SplitIntoSectorsRecursive(regularNodes, 1, rootPath, rootSectorId, sectorIdGenerator, startSplittingDepth) - //); - sectors.AddRange(SplitIntoTreeIndexSectors(regularNodes, rootPath, rootSectorId, sectorIdGenerator)); } foreach (var sector in sectors) { - yield return sector; + // TODO Is there a better way to mark as highlightsector + + yield return sector with + { + IsHighlightSector = true + }; } } @@ -284,30 +282,4 @@ BoundingBox subtreeBoundingBox geometryBoundingBox ); } - - private static int CalculateStartSplittingDepth(BoundingBox boundingBox) - { - // If we start splitting too low in the octree, we might end up with way too many sectors - // If we start splitting too high, we might get some large sectors with a lot of data, which always will be prioritized - - var diagonalAtDepth = boundingBox.Diagonal; - int depth = 1; - // Todo: Arbitrary numbers in this method based on gut feeling. - // Assumes 3 levels of "LOD Splitting": - // 300x300 for Very large parts - // 150x150 for large parts - // 75x75 for > 1 meter parts - // 37,5 etc by budget - const float level1SectorsMaxDiagonal = 500; - while (diagonalAtDepth > level1SectorsMaxDiagonal) - { - diagonalAtDepth /= 2; - depth++; - } - - Console.WriteLine( - $"Diagonal was: {boundingBox.Diagonal:F2}m. Starting splitting at depth {depth}. Expecting a diagonal of maximum {diagonalAtDepth:F2}m" - ); - return depth; - } } From 5da67dab90e64981dca666b334e14331bc79d707 Mon Sep 17 00:00:00 2001 From: vegasten Date: Wed, 4 Sep 2024 14:00:50 +0200 Subject: [PATCH 04/41] Refactor --- .../HighlightSectorSplitter.cs | 53 ++++-------- .../SectorSplitting/SectorSplitterOctree.cs | 73 ++-------------- .../SectorSplitting/SplittingUtils.cs | 83 +++++++++++++++++++ .../SectorSplitting/TooFewInstancesHandler.cs | 8 +- .../TooFewPrimitivesHandler.cs | 12 +-- 5 files changed, 116 insertions(+), 113 deletions(-) diff --git a/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs index aef4c4858..6d368de6b 100644 --- a/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs +++ b/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs @@ -23,7 +23,11 @@ public IEnumerable SplitIntoSectors(APrimitive[] allGeometries, var rootSectorId = (uint)sectorIdGenerator.GetNextId(); const string rootPath = "/0"; - yield return CreateRootSector(rootSectorId, rootPath, new BoundingBox(Vector3.Zero, Vector3.One)); + yield return SplittingUtils.CreateRootSector( + rootSectorId, + rootPath, + new BoundingBox(Vector3.Zero, Vector3.One) + ); var primitivesGroupedByDiscipline = allGeometries.GroupBy(x => x.Discipline); @@ -68,7 +72,14 @@ SequentialIdGenerator sectorIdGenerator var sectorId = (uint)sectorIdGenerator.GetNextId(); var subtreeBoundingBox = nodesByBudget.CalculateBoundingBox(); - yield return CreateSector(nodesByBudget, sectorId, rootSectorId, rootPath, 1, subtreeBoundingBox); + yield return SplittingUtils.CreateSector( + nodesByBudget, + sectorId, + rootSectorId, + rootPath, + 1, + subtreeBoundingBox + ); } } @@ -114,7 +125,7 @@ int depthToStartSplittingGeometry { var sectorId = (uint)sectorIdGenerator.GetNextId(); - yield return CreateSector( + yield return SplittingUtils.CreateSector( mainVoxelNodes, sectorId, parentSectorId, @@ -136,7 +147,7 @@ int depthToStartSplittingGeometry var sectorId = (uint)sectorIdGenerator.GetNextId(); var path = $"{parentPath}/{sectorId}"; - yield return CreateSector( + yield return SplittingUtils.CreateSector( mainVoxelNodes, sectorId, parentSectorId, @@ -248,38 +259,4 @@ private static IEnumerable GetNodesByBudget(IReadOnlyList nodes, lon yield return node; } } - - private InternalSector CreateRootSector(uint sectorId, string path, BoundingBox subtreeBoundingBox) - { - return new InternalSector(sectorId, null, 0, path, 0, 0, Array.Empty(), subtreeBoundingBox, null); - } - - private InternalSector CreateSector( - Node[] nodes, - uint sectorId, - uint? parentSectorId, - string parentPath, - int depth, - BoundingBox subtreeBoundingBox - ) - { - var path = $"{parentPath}/{sectorId}"; - - var minDiagonal = nodes.Any() ? nodes.Min(n => n.Diagonal) : 0; - var maxDiagonal = nodes.Any() ? nodes.Max(n => n.Diagonal) : 0; - var geometries = nodes.SelectMany(n => n.Geometries).ToArray(); - var geometryBoundingBox = geometries.CalculateBoundingBox(); - - return new InternalSector( - sectorId, - parentSectorId, - depth, - path, - minDiagonal, - maxDiagonal, - geometries, - subtreeBoundingBox, - geometryBoundingBox - ); - } } diff --git a/CadRevealComposer/Operations/SectorSplitting/SectorSplitterOctree.cs b/CadRevealComposer/Operations/SectorSplitting/SectorSplitterOctree.cs index 97cf3279f..1bba7b739 100644 --- a/CadRevealComposer/Operations/SectorSplitting/SectorSplitterOctree.cs +++ b/CadRevealComposer/Operations/SectorSplitting/SectorSplitterOctree.cs @@ -1,11 +1,11 @@ namespace CadRevealComposer.Operations.SectorSplitting; -using IdProviders; -using Primitives; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using IdProviders; +using Primitives; using Utils; public class SectorSplitterOctree : ISectorSplitter @@ -21,9 +21,6 @@ public class SectorSplitterOctree : ISectorSplitter private const float OutlierGroupingDistance = 20f; // arbitrary distance between nodes before we group them private const int OutlierStartDepth = 20; // arbitrary depth for outlier sectors, just to ensure separation from the rest - private readonly TooFewInstancesHandler _tooFewInstancesHandler = new(); - private readonly TooFewPrimitivesHandler _tooFewPrimitivesHandler = new(); - public IEnumerable SplitIntoSectors(APrimitive[] allGeometries, ulong nextSectorId) { var sectorIdGenerator = new SequentialIdGenerator(nextSectorId); @@ -36,7 +33,7 @@ public IEnumerable SplitIntoSectors(APrimitive[] allGeometries, var rootSectorId = (uint)sectorIdGenerator.GetNextId(); const string rootPath = "/0"; - yield return CreateRootSector(rootSectorId, rootPath, boundingBoxEncapsulatingAllNodes); + yield return SplittingUtils.CreateRootSector(rootSectorId, rootPath, boundingBoxEncapsulatingAllNodes); //Order nodes by diagonal size var sortedNodes = regularNodes.OrderByDescending(n => n.Diagonal).ToArray(); @@ -69,13 +66,13 @@ public IEnumerable SplitIntoSectors(APrimitive[] allGeometries, } Console.WriteLine( - $"Tried to convert {_tooFewPrimitivesHandler.TriedConvertedGroupsOfPrimitives} out of {_tooFewPrimitivesHandler.TotalGroupsOfPrimitive} total groups of primitives" + $"Tried to convert {TooFewPrimitivesHandler.TriedConvertedGroupsOfPrimitives} out of {TooFewPrimitivesHandler.TotalGroupsOfPrimitive} total groups of primitives" ); Console.WriteLine( - $"Successfully converted {_tooFewPrimitivesHandler.SuccessfullyConvertedGroupsOfPrimitives} groups of primitives" + $"Successfully converted {TooFewPrimitivesHandler.SuccessfullyConvertedGroupsOfPrimitives} groups of primitives" ); Console.WriteLine( - $"This resulted in {_tooFewPrimitivesHandler.AdditionalNumberOfTriangles} additional triangles" + $"This resulted in {TooFewPrimitivesHandler.AdditionalNumberOfTriangles} additional triangles" ); } @@ -163,7 +160,7 @@ int depthToStartSplittingGeometry { var sectorId = (uint)sectorIdGenerator.GetNextId(); - yield return CreateSector( + yield return SplittingUtils.CreateSectorWithPrimitiveHandling( mainVoxelNodes, sectorId, parentSectorId, @@ -185,7 +182,7 @@ int depthToStartSplittingGeometry var sectorId = (uint)sectorIdGenerator.GetNextId(); var path = $"{parentPath}/{sectorId}"; - yield return CreateSector( + yield return SplittingUtils.CreateSectorWithPrimitiveHandling( mainVoxelNodes, sectorId, parentSectorId, @@ -252,60 +249,6 @@ int depthToStartSplittingGeometry } } - private InternalSector CreateRootSector(uint sectorId, string path, BoundingBox subtreeBoundingBox) - { - return new InternalSector(sectorId, null, 0, path, 0, 0, Array.Empty(), subtreeBoundingBox, null); - } - - private InternalSector CreateSector( - Node[] nodes, - uint sectorId, - uint? parentSectorId, - string parentPath, - int depth, - BoundingBox subtreeBoundingBox - ) - { - var path = $"{parentPath}/{sectorId}"; - - var minDiagonal = nodes.Any() ? nodes.Min(n => n.Diagonal) : 0; - var maxDiagonal = nodes.Any() ? nodes.Max(n => n.Diagonal) : 0; - var geometries = nodes.SelectMany(n => n.Geometries).ToArray(); - var geometryBoundingBox = geometries.CalculateBoundingBox(); - - var geometriesCount = geometries.Length; - - // NOTE: This increases triangle count - geometries = _tooFewInstancesHandler.ConvertInstancesWhenTooFew(geometries); - if (geometries.Length != geometriesCount) - { - throw new Exception( - $"The number of primitives was changed when running TooFewInstancesHandler from {geometriesCount} to {geometries}" - ); - } - - // NOTE: This increases triangle count - geometries = _tooFewPrimitivesHandler.ConvertPrimitivesWhenTooFew(geometries); - if (geometries.Length != geometriesCount) - { - throw new Exception( - $"The number of primitives was changed when running TooFewPrimitives from {geometriesCount} to {geometries.Length}" - ); - } - - return new InternalSector( - sectorId, - parentSectorId, - depth, - path, - minDiagonal, - maxDiagonal, - geometries, - subtreeBoundingBox, - geometryBoundingBox - ); - } - private static int CalculateStartSplittingDepth(BoundingBox boundingBox) { // If we start splitting too low in the octree, we might end up with way too many sectors diff --git a/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs b/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs index e0cf276e7..eaffc201a 100644 --- a/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs +++ b/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs @@ -221,4 +221,87 @@ float outlierGroupingDistance return listOfGroups; } + + public static InternalSector CreateRootSector(uint sectorId, string path, BoundingBox subtreeBoundingBox) + { + return new InternalSector(sectorId, null, 0, path, 0, 0, Array.Empty(), subtreeBoundingBox, null); + } + + public static InternalSector CreateSector( + Node[] nodes, + uint sectorId, + uint? parentSectorId, + string parentPath, + int depth, + BoundingBox subtreeBoundingBox + ) + { + var path = $"{parentPath}/{sectorId}"; + + var minDiagonal = nodes.Any() ? nodes.Min(n => n.Diagonal) : 0; + var maxDiagonal = nodes.Any() ? nodes.Max(n => n.Diagonal) : 0; + var geometries = nodes.SelectMany(n => n.Geometries).ToArray(); + var geometryBoundingBox = geometries.CalculateBoundingBox(); + + return new InternalSector( + sectorId, + parentSectorId, + depth, + path, + minDiagonal, + maxDiagonal, + geometries, + subtreeBoundingBox, + geometryBoundingBox + ); + } + + public static InternalSector CreateSectorWithPrimitiveHandling( + Node[] nodes, + uint sectorId, + uint? parentSectorId, + string parentPath, + int depth, + BoundingBox subtreeBoundingBox + ) + { + var path = $"{parentPath}/{sectorId}"; + + var minDiagonal = nodes.Any() ? nodes.Min(n => n.Diagonal) : 0; + var maxDiagonal = nodes.Any() ? nodes.Max(n => n.Diagonal) : 0; + var geometries = nodes.SelectMany(n => n.Geometries).ToArray(); + var geometryBoundingBox = geometries.CalculateBoundingBox(); + + var geometriesCount = geometries.Length; + + // NOTE: This increases triangle count + geometries = TooFewInstancesHandler.ConvertInstancesWhenTooFew(geometries); + if (geometries.Length != geometriesCount) + { + throw new Exception( + $"The number of primitives was changed when running TooFewInstancesHandler from {geometriesCount} to {geometries}" + ); + } + + // NOTE: This increases triangle count + geometries = TooFewPrimitivesHandler.ConvertPrimitivesWhenTooFew(geometries); + if (geometries.Length != geometriesCount) + { + throw new Exception( + $"The number of primitives was changed when running TooFewPrimitives from {geometriesCount} to {geometries.Length}" + ); + } + + return new InternalSector( + sectorId, + parentSectorId, + depth, + path, + minDiagonal, + maxDiagonal, + geometries, + subtreeBoundingBox, + geometryBoundingBox + ); + } } diff --git a/CadRevealComposer/Operations/SectorSplitting/TooFewInstancesHandler.cs b/CadRevealComposer/Operations/SectorSplitting/TooFewInstancesHandler.cs index c104a853e..01628a1e0 100644 --- a/CadRevealComposer/Operations/SectorSplitting/TooFewInstancesHandler.cs +++ b/CadRevealComposer/Operations/SectorSplitting/TooFewInstancesHandler.cs @@ -8,9 +8,9 @@ namespace CadRevealComposer.Operations.SectorSplitting; /// The batching is done in Reveal and each type of template in each sector will create a separate batch. /// This can be avoided by converting to TriangleMesh. /// -public class TooFewInstancesHandler +public static class TooFewInstancesHandler { - public APrimitive[] ConvertInstancesWhenTooFew(APrimitive[] geometries) + public static APrimitive[] ConvertInstancesWhenTooFew(APrimitive[] geometries) { var instanceGroups = geometries.Where(g => g is InstancedMesh).GroupBy(i => ((InstancedMesh)i).InstanceId); @@ -28,7 +28,7 @@ public APrimitive[] ConvertInstancesWhenTooFew(APrimitive[] geometries) .ToArray(); } - private TriangleMesh ConvertInstanceToMesh(InstancedMesh instanceMesh) + private static TriangleMesh ConvertInstanceToMesh(InstancedMesh instanceMesh) { var templateMesh = instanceMesh.TemplateMesh; @@ -57,7 +57,7 @@ private TriangleMesh ConvertInstanceToMesh(InstancedMesh instanceMesh) /// /// /// - private bool ShouldConvert(IGrouping instanceGroup) + private static bool ShouldConvert(IGrouping instanceGroup) { const int numberOfInstancesThreshold = 100; // Always keep when the number of instances is exceeding the threshold const int numberOfTrianglesThreshold = 10000; // Alwyas keep when the number of triangles is exceeding the threshold diff --git a/CadRevealComposer/Operations/SectorSplitting/TooFewPrimitivesHandler.cs b/CadRevealComposer/Operations/SectorSplitting/TooFewPrimitivesHandler.cs index e72367cdf..dc453aad9 100644 --- a/CadRevealComposer/Operations/SectorSplitting/TooFewPrimitivesHandler.cs +++ b/CadRevealComposer/Operations/SectorSplitting/TooFewPrimitivesHandler.cs @@ -5,16 +5,16 @@ namespace CadRevealComposer.Operations.SectorSplitting; using Primitives; using Tessellating; -public class TooFewPrimitivesHandler +public static class TooFewPrimitivesHandler { private const int NumberOfPrimitivesThreshold = 10; // Arbitrary number - public int TotalGroupsOfPrimitive { get; private set; } - public int TriedConvertedGroupsOfPrimitives { get; private set; } - public int SuccessfullyConvertedGroupsOfPrimitives { get; private set; } - public int AdditionalNumberOfTriangles { get; private set; } + public static int TotalGroupsOfPrimitive { get; private set; } + public static int TriedConvertedGroupsOfPrimitives { get; private set; } + public static int SuccessfullyConvertedGroupsOfPrimitives { get; private set; } + public static int AdditionalNumberOfTriangles { get; private set; } - public APrimitive[] ConvertPrimitivesWhenTooFew(APrimitive[] geometries) + public static APrimitive[] ConvertPrimitivesWhenTooFew(APrimitive[] geometries) { var newGeometries = new List(); var primitiveGroups = geometries.GroupBy(x => x.GetType()); From 7b0b94094bab31e4e22b98b1072406c3b23bdb93 Mon Sep 17 00:00:00 2001 From: "Vegard Stengrundet (Omega AS)" Date: Thu, 5 Sep 2024 11:58:13 +0200 Subject: [PATCH 05/41] Insert prioritized sectors in db --- CadRevealComposer/CadRevealComposerRunner.cs | 44 ++++++++++++++----- CadRevealComposer/SceneCreator.cs | 5 +++ .../Functions/DatabaseComposer.cs | 43 ++++++++++++++++++ 3 files changed, 81 insertions(+), 11 deletions(-) diff --git a/CadRevealComposer/CadRevealComposerRunner.cs b/CadRevealComposer/CadRevealComposerRunner.cs index 96807724e..e5b655e6a 100644 --- a/CadRevealComposer/CadRevealComposerRunner.cs +++ b/CadRevealComposer/CadRevealComposerRunner.cs @@ -1,12 +1,5 @@ namespace CadRevealComposer; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; using Configuration; using Devtools; using IdProviders; @@ -14,6 +7,13 @@ using Operations; using Operations.SectorSplitting; using Primitives; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; using Utils; public static class CadRevealComposerRunner @@ -121,7 +121,7 @@ IReadOnlyList modelFormatProviders var devCache = new DevPrimitiveCacheFolder(composerParameters.DevPrimitiveCacheFolder); devCache.WriteToPrimitiveCache(geometriesToProcessArray, inputFolderPath); } - ProcessPrimitives(geometriesToProcessArray, outputDirectory, modelParameters, composerParameters); + var treeIndexToPrioritizedSector = ProcessPrimitives(geometriesToProcessArray, outputDirectory, modelParameters, composerParameters); if (!exportHierarchyDatabaseTask.IsCompleted) Console.WriteLine("Waiting for hierarchy export to complete..."); @@ -129,11 +129,16 @@ IReadOnlyList modelFormatProviders WriteParametersToParamsFile(modelParameters, composerParameters, outputDirectory); + // TODO Is this the best place to do this? + var prioritizedSectorInsertionStopwatch = Stopwatch.StartNew(); + SceneCreator.AddPrioritizedSectorsToDatabase(treeIndexToPrioritizedSector, outputDirectory); + Console.WriteLine($"Inserted prioritized sectors in db in {prioritizedSectorInsertionStopwatch.Elapsed}"); + Console.WriteLine($"Export Finished. Wrote output files to \"{Path.GetFullPath(outputDirectory.FullName)}\""); Console.WriteLine($"Convert completed in {totalTimeElapsed.Elapsed}"); } - public static void ProcessPrimitives( + public static Dictionary ProcessPrimitives( APrimitive[] allPrimitives, DirectoryInfo outputDirectory, ModelParameters modelParameters, @@ -162,11 +167,11 @@ ComposerParameters composerParameters var sectors = splitter.SplitIntoSectors(allPrimitives, 0).OrderBy(x => x.SectorId).ToArray(); - var prioritizedForHiglighting = allPrimitives.Where(x => x.Priority == 1).ToArray(); + var prioritizedForHighlighting = allPrimitives.Where(x => x.Priority == 1).ToArray(); var nextSectorId = sectors.Last().SectorId + 1; var highlightSectors = highlightSplitter - .SplitIntoSectors(prioritizedForHiglighting, nextSectorId) + .SplitIntoSectors(prioritizedForHighlighting, nextSectorId) .OrderBy(x => x.SectorId) .ToArray(); @@ -198,6 +203,23 @@ ComposerParameters composerParameters ); Console.WriteLine($"Wrote scene file in {stopwatch.Elapsed}"); stopwatch.Restart(); + + return GetTreeIndexToSectorIdDict(highlightSectors); + } + + private static Dictionary GetTreeIndexToSectorIdDict(InternalSector[] sectors) + { + var sectorIdToTreeIndex = new Dictionary(); + foreach (var sector in sectors) + { + var sectorId = sector.SectorId; + foreach (var node in sector.Geometries) + { + sectorIdToTreeIndex.TryAdd(node.TreeIndex, sectorId); + } + } + + return sectorIdToTreeIndex; } /// diff --git a/CadRevealComposer/SceneCreator.cs b/CadRevealComposer/SceneCreator.cs index 0c2bcc062..fcd1489c6 100644 --- a/CadRevealComposer/SceneCreator.cs +++ b/CadRevealComposer/SceneCreator.cs @@ -53,6 +53,11 @@ public static void ExportHierarchyDatabase(string databasePath, IReadOnlyList treeIndexToPrioritizedSector, DirectoryInfo outputDirectory) + { + DatabaseComposer.AddTreeIndexToSectorToDatabase(treeIndexToPrioritizedSector, outputDirectory); + } + public static void CreateSceneFile( APrimitive[] allPrimitives, DirectoryInfo outputDirectory, diff --git a/HierarchyComposer/Functions/DatabaseComposer.cs b/HierarchyComposer/Functions/DatabaseComposer.cs index 75e617c72..c91be0552 100644 --- a/HierarchyComposer/Functions/DatabaseComposer.cs +++ b/HierarchyComposer/Functions/DatabaseComposer.cs @@ -254,6 +254,49 @@ public void ComposeDatabase(IReadOnlyList inputNodes, string outp sqliteComposeTimer.LogCompletion(); } + public static void AddTreeIndexToSectorToDatabase(Dictionary treeIndexToSectorId, DirectoryInfo outputDirectory) + { + var databasePath = Path.GetFullPath(Path.Join(outputDirectory.FullName, "hierarchy.db")); + using (var connection = new SqliteConnection($"Data Source={databasePath}")) + { + connection.Open(); + + var createTableCommand = connection.CreateCommand(); + createTableCommand.CommandText = + "CREATE TABLE prioritizedSectors (treeindex INTEGER NOT NULL, highlightSectorId INTEGER NOT NULL, PRIMARY KEY (treeindex, highlightSectorId)) WITHOUT ROWID; "; + createTableCommand.ExecuteNonQuery(); + + var command = connection.CreateCommand(); + command.CommandText = + "INSERT OR IGNORE INTO prioritizedSectors (treeindex, highlightSectorId) VALUES ($TreeIndex, $HighlightSectorId)"; + + var treeIndexParameter = command.CreateParameter(); + treeIndexParameter.ParameterName = "$TreeIndex"; + var highlightSectorIdParameter = command.CreateParameter(); + highlightSectorIdParameter.ParameterName = $"HighlightSectorId"; + + command.Parameters.AddRange([treeIndexParameter, highlightSectorIdParameter]); + + var transaction = connection.BeginTransaction(); + command.Transaction = transaction; + + + foreach (var pair in treeIndexToSectorId) + { + treeIndexParameter.Value = pair.Key; + } + + foreach (var pair in treeIndexToSectorId) + { + treeIndexParameter.Value = pair.Key; + highlightSectorIdParameter.Value = pair.Value; + command.ExecuteNonQuery(); + } + + transaction.Commit(); + } + } + private static void CreateEmptyDatabase(DbContextOptions options) { using var context = new HierarchyContext(options); From 910131f2662da9dc4091c01a2ccde73bba73836d Mon Sep 17 00:00:00 2001 From: "Vegard Stengrundet (Omega AS)" Date: Thu, 5 Sep 2024 14:01:05 +0200 Subject: [PATCH 06/41] Rename to prioritized --- HierarchyComposer/Functions/DatabaseComposer.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/HierarchyComposer/Functions/DatabaseComposer.cs b/HierarchyComposer/Functions/DatabaseComposer.cs index c91be0552..b78c35636 100644 --- a/HierarchyComposer/Functions/DatabaseComposer.cs +++ b/HierarchyComposer/Functions/DatabaseComposer.cs @@ -263,19 +263,19 @@ public static void AddTreeIndexToSectorToDatabase(Dictionary treeIn var createTableCommand = connection.CreateCommand(); createTableCommand.CommandText = - "CREATE TABLE prioritizedSectors (treeindex INTEGER NOT NULL, highlightSectorId INTEGER NOT NULL, PRIMARY KEY (treeindex, highlightSectorId)) WITHOUT ROWID; "; + "CREATE TABLE prioritizedSectors (treeindex INTEGER NOT NULL, prioritizedSectorId INTEGER NOT NULL, PRIMARY KEY (treeindex, prioritizedSectorId)) WITHOUT ROWID; "; createTableCommand.ExecuteNonQuery(); var command = connection.CreateCommand(); command.CommandText = - "INSERT OR IGNORE INTO prioritizedSectors (treeindex, highlightSectorId) VALUES ($TreeIndex, $HighlightSectorId)"; + "INSERT OR IGNORE INTO prioritizedSectors (treeindex, prioritizedSectorId) VALUES ($TreeIndex, $PrioritizedSectorId)"; var treeIndexParameter = command.CreateParameter(); treeIndexParameter.ParameterName = "$TreeIndex"; - var highlightSectorIdParameter = command.CreateParameter(); - highlightSectorIdParameter.ParameterName = $"HighlightSectorId"; + var prioritizedSectorIdParameter = command.CreateParameter(); + prioritizedSectorIdParameter.ParameterName = $"PrioritizedSectorId"; - command.Parameters.AddRange([treeIndexParameter, highlightSectorIdParameter]); + command.Parameters.AddRange([treeIndexParameter, prioritizedSectorIdParameter]); var transaction = connection.BeginTransaction(); command.Transaction = transaction; @@ -289,7 +289,7 @@ public static void AddTreeIndexToSectorToDatabase(Dictionary treeIn foreach (var pair in treeIndexToSectorId) { treeIndexParameter.Value = pair.Key; - highlightSectorIdParameter.Value = pair.Value; + prioritizedSectorIdParameter.Value = pair.Value; command.ExecuteNonQuery(); } From 8fc92d88016acc49ecca52be44ae7740382f0af0 Mon Sep 17 00:00:00 2001 From: vegasten Date: Mon, 9 Sep 2024 20:44:10 +0200 Subject: [PATCH 07/41] Cleanup --- CadRevealComposer/CadRevealComposerRunner.cs | 23 +- .../HighlightSectorSplitter.cs | 262 ------------------ .../SectorSplitting/PrioritySectorSplitter.cs | 94 +++++++ ...tingUtils.cs => PrioritySplittingUtils.cs} | 6 +- .../RvmStoreToCadRevealNodesConverter.cs | 2 +- 5 files changed, 113 insertions(+), 274 deletions(-) delete mode 100644 CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs create mode 100644 CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs rename CadRevealComposer/Operations/SectorSplitting/{HighlightSplittingUtils.cs => PrioritySplittingUtils.cs} (94%) diff --git a/CadRevealComposer/CadRevealComposerRunner.cs b/CadRevealComposer/CadRevealComposerRunner.cs index e5b655e6a..e402de32e 100644 --- a/CadRevealComposer/CadRevealComposerRunner.cs +++ b/CadRevealComposer/CadRevealComposerRunner.cs @@ -1,12 +1,5 @@ namespace CadRevealComposer; -using Configuration; -using Devtools; -using IdProviders; -using ModelFormatProvider; -using Operations; -using Operations.SectorSplitting; -using Primitives; using System; using System.Collections.Generic; using System.Diagnostics; @@ -14,6 +7,13 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; +using Configuration; +using Devtools; +using IdProviders; +using ModelFormatProvider; +using Operations; +using Operations.SectorSplitting; +using Primitives; using Utils; public static class CadRevealComposerRunner @@ -121,7 +121,12 @@ IReadOnlyList modelFormatProviders var devCache = new DevPrimitiveCacheFolder(composerParameters.DevPrimitiveCacheFolder); devCache.WriteToPrimitiveCache(geometriesToProcessArray, inputFolderPath); } - var treeIndexToPrioritizedSector = ProcessPrimitives(geometriesToProcessArray, outputDirectory, modelParameters, composerParameters); + var treeIndexToPrioritizedSector = ProcessPrimitives( + geometriesToProcessArray, + outputDirectory, + modelParameters, + composerParameters + ); if (!exportHierarchyDatabaseTask.IsCompleted) Console.WriteLine("Waiting for hierarchy export to complete..."); @@ -163,7 +168,7 @@ ComposerParameters composerParameters splitter = new SectorSplitterOctree(); } - var highlightSplitter = new HighlightSectorSplitter(); + var highlightSplitter = new PrioritySectorSplitter(); var sectors = splitter.SplitIntoSectors(allPrimitives, 0).OrderBy(x => x.SectorId).ToArray(); diff --git a/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs deleted file mode 100644 index 6d368de6b..000000000 --- a/CadRevealComposer/Operations/SectorSplitting/HighlightSectorSplitter.cs +++ /dev/null @@ -1,262 +0,0 @@ -namespace CadRevealComposer.Operations.SectorSplitting; - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Numerics; -using CadRevealComposer.IdProviders; -using CadRevealComposer.Primitives; -using CadRevealComposer.Utils; - -public class HighlightSectorSplitter : ISectorSplitter -{ - private const float MinDiagonalSizeAtDepth_1 = 7; // arbitrary value for min size at depth 1 - private const float MinDiagonalSizeAtDepth_2 = 4; // arbitrary value for min size at depth 2 - private const float MinDiagonalSizeAtDepth_3 = 1.5f; // arbitrary value for min size at depth 3 - private const long SectorEstimatedByteSizeBudget = 100_000; // bytes, Arbitrary value - private const float DoNotChopSectorsSmallerThanMetersInDiameter = 17.4f; // Arbitrary value - - public IEnumerable SplitIntoSectors(APrimitive[] allGeometries, ulong nextSectorId) - { - var sectorIdGenerator = new SequentialIdGenerator(nextSectorId); - - var rootSectorId = (uint)sectorIdGenerator.GetNextId(); - const string rootPath = "/0"; - yield return SplittingUtils.CreateRootSector( - rootSectorId, - rootPath, - new BoundingBox(Vector3.Zero, Vector3.One) - ); - - var primitivesGroupedByDiscipline = allGeometries.GroupBy(x => x.Discipline); - - var sectors = new List(); - foreach (var disciplineGroup in primitivesGroupedByDiscipline) - { - var geometryGroups = disciplineGroup.GroupBy(x => x.TreeIndex); // Group by treeindex to avoid having one treeindex uneccessary many sectors - var nodes = HighlightSplittingUtils.ConvertPrimitiveGroupsToNodes(geometryGroups); - - // Ignore outlier nodes - // TODO: Decide if this is the right thing to do - (Node[] regularNodes, Node[] outlierNodes) = nodes.SplitNodesIntoRegularAndOutlierNodes(); - - sectors.AddRange(SplitIntoTreeIndexSectors(regularNodes, rootPath, rootSectorId, sectorIdGenerator)); - } - - foreach (var sector in sectors) - { - // TODO Is there a better way to mark as highlightsector - - yield return sector with - { - IsHighlightSector = true - }; - } - } - - private IEnumerable SplitIntoTreeIndexSectors( - Node[] nodes, - string rootPath, - uint rootSectorId, - SequentialIdGenerator sectorIdGenerator - ) - { - var nodesUsed = 0; - - while (nodesUsed < nodes.Length) - { - var nodesByBudget = GetNodesByBudgetSimple(nodes, nodesUsed).ToArray(); - nodesUsed += nodesByBudget.Length; - - var sectorId = (uint)sectorIdGenerator.GetNextId(); - var subtreeBoundingBox = nodesByBudget.CalculateBoundingBox(); - - yield return SplittingUtils.CreateSector( - nodesByBudget, - sectorId, - rootSectorId, - rootPath, - 1, - subtreeBoundingBox - ); - } - } - - private IEnumerable SplitIntoSectorsRecursive( - Node[] nodes, - int recursiveDepth, - string parentPath, - uint? parentSectorId, - SequentialIdGenerator sectorIdGenerator, - int depthToStartSplittingGeometry - ) - { - if (nodes.Length == 0) - { - yield break; - } - - var actualDepth = Math.Max(1, recursiveDepth - depthToStartSplittingGeometry + 1); - - var subtreeBoundingBox = nodes.CalculateBoundingBox(); - - var mainVoxelNodes = Array.Empty(); - Node[] subVoxelNodes; - - if (recursiveDepth < depthToStartSplittingGeometry) - { - subVoxelNodes = nodes; - } - else - { - // fill main voxel according to budget - var additionalMainVoxelNodesByBudget = GetNodesByBudget( - nodes.ToArray(), - SectorEstimatedByteSizeBudget, - actualDepth - ) - .ToList(); - mainVoxelNodes = mainVoxelNodes.Concat(additionalMainVoxelNodesByBudget).ToArray(); - subVoxelNodes = nodes.Except(mainVoxelNodes).ToArray(); - } - - if (!subVoxelNodes.Any()) - { - var sectorId = (uint)sectorIdGenerator.GetNextId(); - - yield return SplittingUtils.CreateSector( - mainVoxelNodes, - sectorId, - parentSectorId, - parentPath, - actualDepth, - subtreeBoundingBox - ); - } - else - { - string parentPathForChildren = parentPath; - uint? parentSectorIdForChildren = parentSectorId; - - var geometries = mainVoxelNodes.SelectMany(n => n.Geometries).ToArray(); - - // Should we keep empty sectors???? yes no? - if (geometries.Any() || subVoxelNodes.Any()) - { - var sectorId = (uint)sectorIdGenerator.GetNextId(); - var path = $"{parentPath}/{sectorId}"; - - yield return SplittingUtils.CreateSector( - mainVoxelNodes, - sectorId, - parentSectorId, - parentPath, - actualDepth, - subtreeBoundingBox - ); - - parentPathForChildren = path; - parentSectorIdForChildren = sectorId; - } - - var sizeOfSubVoxelNodes = subVoxelNodes.Sum(x => x.EstimatedByteSize); - var subVoxelDiagonal = subVoxelNodes.CalculateBoundingBox().Diagonal; - - if ( - subVoxelDiagonal < DoNotChopSectorsSmallerThanMetersInDiameter - || sizeOfSubVoxelNodes < SectorEstimatedByteSizeBudget - ) - { - var sectors = SplitIntoSectorsRecursive( - subVoxelNodes, - recursiveDepth + 1, - parentPathForChildren, - parentSectorIdForChildren, - sectorIdGenerator, - depthToStartSplittingGeometry - ); - foreach (var sector in sectors) - { - yield return sector; - } - - yield break; - } - - var voxels = subVoxelNodes - .GroupBy(node => SplittingUtils.CalculateVoxelKeyForNode(node, subtreeBoundingBox)) - .OrderBy(x => x.Key) - .ToImmutableList(); - - foreach (var voxelGroup in voxels) - { - if (voxelGroup.Key == SplittingUtils.MainVoxel) - { - throw new Exception( - "Main voxel should not appear here. Main voxel should be processed separately." - ); - } - - var sectors = SplitIntoSectorsRecursive( - voxelGroup.ToArray(), - recursiveDepth + 1, - parentPathForChildren, - parentSectorIdForChildren, - sectorIdGenerator, - depthToStartSplittingGeometry - ); - foreach (var sector in sectors) - { - yield return sector; - } - } - } - } - - private static IEnumerable GetNodesByBudgetSimple(IReadOnlyList nodes, int indexToStart) - { - var byteSizeBudget = SectorEstimatedByteSizeBudget; - - for (int i = indexToStart; i < nodes.Count; i++) - { - if (byteSizeBudget < 0) - { - yield break; - } - - var node = nodes[i]; - byteSizeBudget -= node.EstimatedByteSize; - - yield return node; - } - } - - private static IEnumerable GetNodesByBudget(IReadOnlyList nodes, long byteSizeBudget, int actualDepth) - { - var selectedNodes = actualDepth switch - { - 1 => nodes.Where(x => x.Diagonal >= MinDiagonalSizeAtDepth_1).ToArray(), - 2 => nodes.Where(x => x.Diagonal >= MinDiagonalSizeAtDepth_2).ToArray(), - 3 => nodes.Where(x => x.Diagonal >= MinDiagonalSizeAtDepth_3).ToArray(), - _ => nodes.ToArray(), - }; - - var nodesInPrioritizedOrder = selectedNodes.OrderByDescending(x => x.Diagonal); - - var nodeArray = nodesInPrioritizedOrder.ToArray(); - var byteSizeBudgetLeft = byteSizeBudget; - for (int i = 0; i < nodeArray.Length; i++) - { - if ((byteSizeBudgetLeft < 0) && nodeArray.Length - i > 10) - { - yield break; - } - - var node = nodeArray[i]; - byteSizeBudgetLeft -= node.EstimatedByteSize; - - yield return node; - } - } -} diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs new file mode 100644 index 000000000..c7b66b47b --- /dev/null +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs @@ -0,0 +1,94 @@ +namespace CadRevealComposer.Operations.SectorSplitting; + +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using CadRevealComposer.IdProviders; +using CadRevealComposer.Primitives; + +public class PrioritySectorSplitter : ISectorSplitter +{ + private const long SectorEstimatedByteSizeBudget = 100_000; // bytes, Arbitrary value + + public IEnumerable SplitIntoSectors(APrimitive[] allGeometries, ulong nextSectorId) + { + var sectorIdGenerator = new SequentialIdGenerator(nextSectorId); + + var rootSectorId = (uint)sectorIdGenerator.GetNextId(); + const string rootPath = "/0"; + yield return SplittingUtils.CreateRootSector( + rootSectorId, + rootPath, + new BoundingBox(Vector3.Zero, Vector3.One) + ); + + var primitivesGroupedByDiscipline = allGeometries.GroupBy(x => x.Discipline); + + var sectors = new List(); + foreach (var disciplineGroup in primitivesGroupedByDiscipline) + { + var geometryGroups = disciplineGroup.GroupBy(x => x.TreeIndex); // Group by treeindex to avoid having one treeindex uneccessary many sectors + var nodes = PrioritySplittingUtils.ConvertPrimitiveGroupsToNodes(geometryGroups); + + // Ignore outlier nodes + // TODO: Decide if this is the right thing to do + (Node[] regularNodes, _) = nodes.SplitNodesIntoRegularAndOutlierNodes(); + + sectors.AddRange(SplitIntoTreeIndexSectors(regularNodes, rootPath, rootSectorId, sectorIdGenerator)); + } + + foreach (var sector in sectors) + { + yield return sector with + { + IsHighlightSector = true + }; + } + } + + private IEnumerable SplitIntoTreeIndexSectors( + Node[] nodes, + string rootPath, + uint rootSectorId, + SequentialIdGenerator sectorIdGenerator + ) + { + var nodesUsed = 0; + + while (nodesUsed < nodes.Length) + { + var nodesByBudget = GetNodesByBudgetSimple(nodes, nodesUsed).ToArray(); + nodesUsed += nodesByBudget.Length; + + var sectorId = (uint)sectorIdGenerator.GetNextId(); + var subtreeBoundingBox = nodesByBudget.CalculateBoundingBox(); + + yield return SplittingUtils.CreateSector( + nodesByBudget, + sectorId, + rootSectorId, + rootPath, + 1, + subtreeBoundingBox + ); + } + } + + private static IEnumerable GetNodesByBudgetSimple(IReadOnlyList nodes, int indexToStart) + { + var byteSizeBudget = SectorEstimatedByteSizeBudget; + + for (int i = indexToStart; i < nodes.Count; i++) + { + if (byteSizeBudget < 0) + { + yield break; + } + + var node = nodes[i]; + byteSizeBudget -= node.EstimatedByteSize; + + yield return node; + } + } +} diff --git a/CadRevealComposer/Operations/SectorSplitting/HighlightSplittingUtils.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs similarity index 94% rename from CadRevealComposer/Operations/SectorSplitting/HighlightSplittingUtils.cs rename to CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs index fcebf8756..68bef5136 100644 --- a/CadRevealComposer/Operations/SectorSplitting/HighlightSplittingUtils.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs @@ -6,8 +6,10 @@ using CadRevealComposer.Primitives; using CadRevealComposer.Utils; -public static class HighlightSplittingUtils +public static class PrioritySplittingUtils { + private static string[] PrioritizedDisciplines = { "PIPE" }; + public static void SetPriorityForHighlightSplitting(CadRevealNode[] nodes) { var disciplineFilteredNodes = FilterAndSetDiscipline(nodes).ToArray(); @@ -27,7 +29,7 @@ private static IEnumerable FilterAndSetDiscipline(CadRevealNode[] { var discipline = node.Attributes.GetValueOrNull("Discipline"); - if (discipline != null && discipline != "STRU") + if (discipline != null && PrioritizedDisciplines.Contains(discipline)) { var children = CadRevealNode.GetAllNodesFlat(node); foreach (var child in children) diff --git a/CadRevealRvmProvider/RvmStoreToCadRevealNodesConverter.cs b/CadRevealRvmProvider/RvmStoreToCadRevealNodesConverter.cs index e998e6c2a..b45db6fd5 100644 --- a/CadRevealRvmProvider/RvmStoreToCadRevealNodesConverter.cs +++ b/CadRevealRvmProvider/RvmStoreToCadRevealNodesConverter.cs @@ -43,7 +43,7 @@ NodeNameFiltering nodeNameFiltering var allNodes = cadRevealRootNodes.SelectMany(CadRevealNode.GetAllNodesFlat).ToArray(); - HighlightSplittingUtils.SetPriorityForHighlightSplitting(allNodes); + PrioritySplittingUtils.SetPriorityForHighlightSplitting(allNodes); return allNodes; } From e32ab1f0f8961b39a817b71da20be78786c36b87 Mon Sep 17 00:00:00 2001 From: vegasten Date: Mon, 9 Sep 2024 20:55:50 +0200 Subject: [PATCH 08/41] Leftover renaming from highlight to priority --- CadRevealComposer/CadRevealComposerRunner.cs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/CadRevealComposer/CadRevealComposerRunner.cs b/CadRevealComposer/CadRevealComposerRunner.cs index e402de32e..b9877c1da 100644 --- a/CadRevealComposer/CadRevealComposerRunner.cs +++ b/CadRevealComposer/CadRevealComposerRunner.cs @@ -168,22 +168,22 @@ ComposerParameters composerParameters splitter = new SectorSplitterOctree(); } - var highlightSplitter = new PrioritySectorSplitter(); + var prioritySplitter = new PrioritySectorSplitter(); var sectors = splitter.SplitIntoSectors(allPrimitives, 0).OrderBy(x => x.SectorId).ToArray(); - var prioritizedForHighlighting = allPrimitives.Where(x => x.Priority == 1).ToArray(); + var prioritizedPrimitives = allPrimitives.Where(x => x.Priority == 1).ToArray(); var nextSectorId = sectors.Last().SectorId + 1; - var highlightSectors = highlightSplitter - .SplitIntoSectors(prioritizedForHighlighting, nextSectorId) + var prioritizedSectors = prioritySplitter + .SplitIntoSectors(prioritizedPrimitives, nextSectorId) .OrderBy(x => x.SectorId) .ToArray(); // Remove redundant root and point to the original root var originalRootId = sectors.First().SectorId; - var redundantRootId = highlightSectors.First().SectorId; - highlightSectors = highlightSectors + var redundantRootId = prioritizedSectors.First().SectorId; + prioritizedSectors = prioritizedSectors .Skip(1) .Select(sector => { @@ -193,9 +193,11 @@ ComposerParameters composerParameters }) .ToArray(); - Console.WriteLine($"Split into {sectors.Length} sectors in {stopwatch.Elapsed}"); + Console.WriteLine( + $"Split into {sectors.Length} sectors and {prioritizedSectors.Length} prioritized sectors in {stopwatch.Elapsed}" + ); - var allSectors = sectors.Concat(highlightSectors).ToArray(); + var allSectors = sectors.Concat(prioritizedSectors).ToArray(); stopwatch.Restart(); SceneCreator.CreateSceneFile( @@ -209,7 +211,7 @@ ComposerParameters composerParameters Console.WriteLine($"Wrote scene file in {stopwatch.Elapsed}"); stopwatch.Restart(); - return GetTreeIndexToSectorIdDict(highlightSectors); + return GetTreeIndexToSectorIdDict(prioritizedSectors); } private static Dictionary GetTreeIndexToSectorIdDict(InternalSector[] sectors) From b0ceb24f88c0d98daf8289386179b031e76033b9 Mon Sep 17 00:00:00 2001 From: vegasten Date: Mon, 9 Sep 2024 21:03:13 +0200 Subject: [PATCH 09/41] Add a todo for protobuf of new aprimitive properties --- CadRevealComposer/Primitives/APrimitive.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/CadRevealComposer/Primitives/APrimitive.cs b/CadRevealComposer/Primitives/APrimitive.cs index a0a391324..b3370ca5d 100644 --- a/CadRevealComposer/Primitives/APrimitive.cs +++ b/CadRevealComposer/Primitives/APrimitive.cs @@ -159,6 +159,7 @@ public abstract record APrimitive( [property: ProtoMember(3)] BoundingBox AxisAlignedBoundingBox ) { + // TODO: Should these be a part of protobuf? public int Priority { get; init; } = 0; public string? Discipline { get; init; } = null; } From 014a2811fd0993e2cbe5486fed2a7525607ecf85 Mon Sep 17 00:00:00 2001 From: vegasten Date: Mon, 9 Sep 2024 21:04:22 +0200 Subject: [PATCH 10/41] Modify budget --- .../Operations/SectorSplitting/PrioritySectorSplitter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs index c7b66b47b..db1848d24 100644 --- a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs @@ -8,7 +8,7 @@ public class PrioritySectorSplitter : ISectorSplitter { - private const long SectorEstimatedByteSizeBudget = 100_000; // bytes, Arbitrary value + private const long SectorEstimatedByteSizeBudget = 50_000; // bytes, Arbitrary value public IEnumerable SplitIntoSectors(APrimitive[] allGeometries, ulong nextSectorId) { From 78aad9256e307e09431616188515a61fbc145d10 Mon Sep 17 00:00:00 2001 From: vegasten Date: Mon, 9 Sep 2024 21:15:24 +0200 Subject: [PATCH 11/41] Add a todo for renaming sectors --- CadRevealComposer/SceneCreator.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CadRevealComposer/SceneCreator.cs b/CadRevealComposer/SceneCreator.cs index fcd1489c6..32c468109 100644 --- a/CadRevealComposer/SceneCreator.cs +++ b/CadRevealComposer/SceneCreator.cs @@ -53,7 +53,10 @@ public static void ExportHierarchyDatabase(string databasePath, IReadOnlyList treeIndexToPrioritizedSector, DirectoryInfo outputDirectory) + public static void AddPrioritizedSectorsToDatabase( + Dictionary treeIndexToPrioritizedSector, + DirectoryInfo outputDirectory + ) { DatabaseComposer.AddTreeIndexToSectorToDatabase(treeIndexToPrioritizedSector, outputDirectory); } @@ -164,7 +167,7 @@ private static SectorInfo SerializeSector(InternalSector p, string outputDirecto ? $"sector_{p.SectorId}.glb" : null : p.Geometries.Any() - ? $"highlight_sector_{p.SectorId}.glb" + ? $"highlight_sector_{p.SectorId}.glb" // TODO Rename to priority sector : null; var sectorInfo = new SectorInfo( From aeb9989b48de6a103d3442b5c6d6851cf28e0eee Mon Sep 17 00:00:00 2001 From: vegasten Date: Tue, 10 Sep 2024 07:57:47 +0200 Subject: [PATCH 12/41] Fix formatting --- HierarchyComposer/Functions/DatabaseComposer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/HierarchyComposer/Functions/DatabaseComposer.cs b/HierarchyComposer/Functions/DatabaseComposer.cs index b78c35636..db5fe8032 100644 --- a/HierarchyComposer/Functions/DatabaseComposer.cs +++ b/HierarchyComposer/Functions/DatabaseComposer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Data.SQLite; -using System.Diagnostics; using System.IO; using System.Linq; using Extensions; @@ -254,7 +253,10 @@ public void ComposeDatabase(IReadOnlyList inputNodes, string outp sqliteComposeTimer.LogCompletion(); } - public static void AddTreeIndexToSectorToDatabase(Dictionary treeIndexToSectorId, DirectoryInfo outputDirectory) + public static void AddTreeIndexToSectorToDatabase( + Dictionary treeIndexToSectorId, + DirectoryInfo outputDirectory + ) { var databasePath = Path.GetFullPath(Path.Join(outputDirectory.FullName, "hierarchy.db")); using (var connection = new SqliteConnection($"Data Source={databasePath}")) @@ -280,7 +282,6 @@ public static void AddTreeIndexToSectorToDatabase(Dictionary treeIn var transaction = connection.BeginTransaction(); command.Transaction = transaction; - foreach (var pair in treeIndexToSectorId) { treeIndexParameter.Value = pair.Key; From dd07bdf28c2998cc37b25a4c2895f8531a85e86f Mon Sep 17 00:00:00 2001 From: Nils Henrik Hals <3185998+Strepto@users.noreply.github.com> Date: Mon, 14 Oct 2024 11:53:22 +0200 Subject: [PATCH 13/41] Reorganize Sector Id code --- .../NodeIdProviderTests.cs | 19 ------ .../SequentialIdProviderTests.cs | 19 ++++++ CadRevealComposer/CadRevealComposerRunner.cs | 66 ++++++++++++------- .../IdProviders/NodeIdProvider.cs | 25 ------- .../IdProviders/SequentialIdGenerator.cs | 19 ++---- .../SectorSplitting/ISectorSplitter.cs | 6 +- .../SectorSplitting/PrioritySectorSplitter.cs | 9 +-- .../SectorSplitting/SectorSplitterOctree.cs | 7 +- .../SectorSplitting/SectorSplitterSingle.cs | 8 ++- 9 files changed, 87 insertions(+), 91 deletions(-) delete mode 100644 CadRevealComposer.Tests/NodeIdProviderTests.cs create mode 100644 CadRevealComposer.Tests/SequentialIdProviderTests.cs delete mode 100644 CadRevealComposer/IdProviders/NodeIdProvider.cs diff --git a/CadRevealComposer.Tests/NodeIdProviderTests.cs b/CadRevealComposer.Tests/NodeIdProviderTests.cs deleted file mode 100644 index 04c2fdfa6..000000000 --- a/CadRevealComposer.Tests/NodeIdProviderTests.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace CadRevealComposer.Tests; - -using IdProviders; - -[TestFixture] -public class NodeIdProviderTests -{ - [Test] - public void FuzzTest_Produces_NoOutsideValidRange() - { - // This test tests N random values. It should never fail. - var nip = new NodeIdProvider(); - - const ulong maxSafeInt = SequentialIdGenerator.MaxSafeInteger; - - for (var i = 0; i < 100; i++) - Assert.That(nip.GetNodeId(null), Is.LessThanOrEqualTo(maxSafeInt)); - } -} diff --git a/CadRevealComposer.Tests/SequentialIdProviderTests.cs b/CadRevealComposer.Tests/SequentialIdProviderTests.cs new file mode 100644 index 000000000..8bff7675a --- /dev/null +++ b/CadRevealComposer.Tests/SequentialIdProviderTests.cs @@ -0,0 +1,19 @@ +namespace CadRevealComposer.Tests; + +using IdProviders; + +[TestFixture] +public class NodeIdProviderTests +{ + [TestCase(0u)] + [TestCase(10u)] + public void GetNodeId_ForFirstId_ReturnsSameAsStartingId(uint firstIdReturned) + { + // This test tests N random values. It should never fail. + var sequentialIdGenerator = new SequentialIdGenerator(firstIdReturned); + uint nextId = sequentialIdGenerator.GetNextId(); + Assert.That(nextId, Is.EqualTo(firstIdReturned)); + var nextId2 = sequentialIdGenerator.GetNextId(); + Assert.That(nextId2, Is.EqualTo(firstIdReturned + 1)); + } +} diff --git a/CadRevealComposer/CadRevealComposerRunner.cs b/CadRevealComposer/CadRevealComposerRunner.cs index b9877c1da..f202b1901 100644 --- a/CadRevealComposer/CadRevealComposerRunner.cs +++ b/CadRevealComposer/CadRevealComposerRunner.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.Contracts; using System.IO; using System.Linq; using System.Text.Json; @@ -42,6 +43,7 @@ IReadOnlyList modelFormatProviders ); return; } + Console.WriteLine( "Did not find a Primitive Cache file for the current input folder. Processing as normal, and saving a new cache for next run." ); @@ -121,6 +123,7 @@ IReadOnlyList modelFormatProviders var devCache = new DevPrimitiveCacheFolder(composerParameters.DevPrimitiveCacheFolder); devCache.WriteToPrimitiveCache(geometriesToProcessArray, inputFolderPath); } + var treeIndexToPrioritizedSector = ProcessPrimitives( geometriesToProcessArray, outputDirectory, @@ -168,37 +171,32 @@ ComposerParameters composerParameters splitter = new SectorSplitterOctree(); } - var prioritySplitter = new PrioritySectorSplitter(); - - var sectors = splitter.SplitIntoSectors(allPrimitives, 0).OrderBy(x => x.SectorId).ToArray(); - - var prioritizedPrimitives = allPrimitives.Where(x => x.Priority == 1).ToArray(); + const uint rootSectorId = 0; + var sectorIdGenerator = new SequentialIdGenerator(firstIdReturned: rootSectorId); + // First split into normal sectors for the entire model + var normalSectors = splitter + .SplitIntoSectors(allPrimitives, sectorIdGenerator) + .OrderBy(x => x.SectorId) + .ToArray(); - var nextSectorId = sectors.Last().SectorId + 1; + // Then split into prioritized sectors, these are loaded on demand based on metadata in the Hierarchy database + var prioritySplitter = new PrioritySectorSplitter(); + var prioritizedPrimitives = allPrimitives.Where(x => x.Priority > 0).ToArray(); var prioritizedSectors = prioritySplitter - .SplitIntoSectors(prioritizedPrimitives, nextSectorId) + .SplitIntoSectors(prioritizedPrimitives, sectorIdGenerator) .OrderBy(x => x.SectorId) .ToArray(); - // Remove redundant root and point to the original root - var originalRootId = sectors.First().SectorId; - var redundantRootId = prioritizedSectors.First().SectorId; - prioritizedSectors = prioritizedSectors - .Skip(1) - .Select(sector => - { - return sector.ParentSectorId == redundantRootId - ? (sector with { ParentSectorId = originalRootId }) - : sector; - }) - .ToArray(); + InternalSector[] remappedPrioritizedSectors = RemapPrioritizedSectorsRootSectorId( + prioritizedSectors, + rootSectorId + ); + var allSectors = normalSectors.Concat(remappedPrioritizedSectors).OrderBy(x => x.SectorId).ToArray(); Console.WriteLine( - $"Split into {sectors.Length} sectors and {prioritizedSectors.Length} prioritized sectors in {stopwatch.Elapsed}" + $"Split into {normalSectors.Length} sectors and {remappedPrioritizedSectors.Length} prioritized sectors in {stopwatch.Elapsed}" ); - var allSectors = sectors.Concat(prioritizedSectors).ToArray(); - stopwatch.Restart(); SceneCreator.CreateSceneFile( allPrimitives, @@ -214,15 +212,35 @@ ComposerParameters composerParameters return GetTreeIndexToSectorIdDict(prioritizedSectors); } + /// + /// Remap Root sector ids to the given input rootId for all sectors in input + /// + [Pure] + private static InternalSector[] RemapPrioritizedSectorsRootSectorId( + InternalSector[] prioritizedSectors, + uint newRootId + ) + { + var redundantRootId = prioritizedSectors.Min(x => x.SectorId); + var remappedPrioritizedSectors = prioritizedSectors + .Where(x => x.SectorId != redundantRootId) + .Select(sector => + sector.ParentSectorId == redundantRootId ? (sector with { ParentSectorId = newRootId }) : sector + ) + .ToArray(); + return remappedPrioritizedSectors; + } + private static Dictionary GetTreeIndexToSectorIdDict(InternalSector[] sectors) { var sectorIdToTreeIndex = new Dictionary(); foreach (var sector in sectors) { var sectorId = sector.SectorId; - foreach (var node in sector.Geometries) + foreach (var geometry in sector.Geometries) { - sectorIdToTreeIndex.TryAdd(node.TreeIndex, sectorId); + // TODO: Cant a TreeIndex be in many sectors? // NIH + sectorIdToTreeIndex.TryAdd(geometry.TreeIndex, sectorId); } } diff --git a/CadRevealComposer/IdProviders/NodeIdProvider.cs b/CadRevealComposer/IdProviders/NodeIdProvider.cs deleted file mode 100644 index 299c4fec5..000000000 --- a/CadRevealComposer/IdProviders/NodeIdProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace CadRevealComposer.IdProviders; - -using System; -using System.Collections.Concurrent; - -// FIXME: placeholder implementation - -// TODO: NodeId is currently unused, we always use TreeIndex -public class NodeIdProvider -{ - // Using ConcurrentDict as a substitute for the non-existent ConcurrentHashSet. - private readonly ConcurrentDictionary _generatedIds = new ConcurrentDictionary(); - - // TODO: this will generate or fetch Node ID based on project, hierarchy, name. The idea is to keep it deterministic if possible - public ulong GetNodeId(CadRevealNode? cadNode) - { - ulong value; - do - { - value = (uint)Random.Shared.Next(0, Int32.MaxValue); // TODO: Expand to Javascript Safe Number range ((2^53)-1) - } while (!_generatedIds.TryAdd(value, 0)); - - return value; - } -} diff --git a/CadRevealComposer/IdProviders/SequentialIdGenerator.cs b/CadRevealComposer/IdProviders/SequentialIdGenerator.cs index 76018745f..227f98cb0 100644 --- a/CadRevealComposer/IdProviders/SequentialIdGenerator.cs +++ b/CadRevealComposer/IdProviders/SequentialIdGenerator.cs @@ -3,26 +3,19 @@ using System; using System.Threading; -public class SequentialIdGenerator +public class SequentialIdGenerator(uint firstIdReturned = 0) { - public const ulong MaxSafeInteger = (1L << 53) - 1; + public static readonly uint MaxSafeInteger = (uint)Math.Pow(2, 24) - 1; // Max sequential whole integer in a 32-bit float as used in reveal shaders. - private ulong _internalIdCounter = ulong.MaxValue; + private long _internalIdCounter = ((long)firstIdReturned) - 1; // It increments before selecting the id, hence -1 - protected SequentialIdGenerator() { } - - public SequentialIdGenerator(ulong startId) - { - _internalIdCounter = startId - 1; // It increments before selecting the id, hence -1 - } - - public ulong GetNextId() + public uint GetNextId() { var candidate = Interlocked.Increment(ref _internalIdCounter); if (candidate > MaxSafeInteger) throw new Exception("Too many ids generated"); - return candidate; + return (uint)candidate; } - public ulong CurrentMaxGeneratedIndex => Interlocked.Read(ref _internalIdCounter); + public uint CurrentMaxGeneratedIndex => (uint)Interlocked.Read(ref _internalIdCounter); } diff --git a/CadRevealComposer/Operations/SectorSplitting/ISectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/ISectorSplitter.cs index 8097f7aab..dba6b4118 100644 --- a/CadRevealComposer/Operations/SectorSplitting/ISectorSplitter.cs +++ b/CadRevealComposer/Operations/SectorSplitting/ISectorSplitter.cs @@ -1,9 +1,13 @@ namespace CadRevealComposer.Operations.SectorSplitting; using System.Collections.Generic; +using IdProviders; using Primitives; public interface ISectorSplitter { - public IEnumerable SplitIntoSectors(APrimitive[] allGeometries, ulong nextSectorId); + public IEnumerable SplitIntoSectors( + APrimitive[] allGeometries, + SequentialIdGenerator sectorIdGenerator + ); } diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs index db1848d24..75d024cf0 100644 --- a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs @@ -10,11 +10,12 @@ public class PrioritySectorSplitter : ISectorSplitter { private const long SectorEstimatedByteSizeBudget = 50_000; // bytes, Arbitrary value - public IEnumerable SplitIntoSectors(APrimitive[] allGeometries, ulong nextSectorId) + public IEnumerable SplitIntoSectors( + APrimitive[] allGeometries, + SequentialIdGenerator sectorIdGenerator + ) { - var sectorIdGenerator = new SequentialIdGenerator(nextSectorId); - - var rootSectorId = (uint)sectorIdGenerator.GetNextId(); + var rootSectorId = sectorIdGenerator.GetNextId(); const string rootPath = "/0"; yield return SplittingUtils.CreateRootSector( rootSectorId, diff --git a/CadRevealComposer/Operations/SectorSplitting/SectorSplitterOctree.cs b/CadRevealComposer/Operations/SectorSplitting/SectorSplitterOctree.cs index 1bba7b739..c509f8a76 100644 --- a/CadRevealComposer/Operations/SectorSplitting/SectorSplitterOctree.cs +++ b/CadRevealComposer/Operations/SectorSplitting/SectorSplitterOctree.cs @@ -21,10 +21,11 @@ public class SectorSplitterOctree : ISectorSplitter private const float OutlierGroupingDistance = 20f; // arbitrary distance between nodes before we group them private const int OutlierStartDepth = 20; // arbitrary depth for outlier sectors, just to ensure separation from the rest - public IEnumerable SplitIntoSectors(APrimitive[] allGeometries, ulong nextSectorId) + public IEnumerable SplitIntoSectors( + APrimitive[] allGeometries, + SequentialIdGenerator sectorIdGenerator + ) { - var sectorIdGenerator = new SequentialIdGenerator(nextSectorId); - var allNodes = SplittingUtils.ConvertPrimitivesToNodes(allGeometries); (Node[] regularNodes, Node[] outlierNodes) = allNodes.SplitNodesIntoRegularAndOutlierNodes(); var boundingBoxEncapsulatingAllNodes = allNodes.CalculateBoundingBox(); diff --git a/CadRevealComposer/Operations/SectorSplitting/SectorSplitterSingle.cs b/CadRevealComposer/Operations/SectorSplitting/SectorSplitterSingle.cs index f8818cb4f..54f845620 100644 --- a/CadRevealComposer/Operations/SectorSplitting/SectorSplitterSingle.cs +++ b/CadRevealComposer/Operations/SectorSplitting/SectorSplitterSingle.cs @@ -3,14 +3,18 @@ using System; using System.Collections.Generic; using System.Linq; +using IdProviders; using Primitives; using Utils; public class SectorSplitterSingle : ISectorSplitter { - public IEnumerable SplitIntoSectors(APrimitive[] allGeometries, ulong nextSectorId) + public IEnumerable SplitIntoSectors( + APrimitive[] allGeometries, + SequentialIdGenerator sectorIdGenerator + ) { - yield return CreateRootSector(0, allGeometries); + yield return CreateRootSector(sectorIdGenerator.GetNextId(), allGeometries); } private InternalSector CreateRootSector(uint sectorId, APrimitive[] geometries) From abd0ced08d2bd5709c3fd5c804f6c72e2fc637a1 Mon Sep 17 00:00:00 2001 From: Nils Henrik Hals <3185998+Strepto@users.noreply.github.com> Date: Mon, 14 Oct 2024 11:54:24 +0200 Subject: [PATCH 14/41] Add System.Diagnostics --- HierarchyComposer/Functions/DatabaseComposer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/HierarchyComposer/Functions/DatabaseComposer.cs b/HierarchyComposer/Functions/DatabaseComposer.cs index db5fe8032..67b82320b 100644 --- a/HierarchyComposer/Functions/DatabaseComposer.cs +++ b/HierarchyComposer/Functions/DatabaseComposer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Data.SQLite; +using System.Diagnostics; using System.IO; using System.Linq; using Extensions; From 443df3447b55db8a13a7c466cd3e26ae8a8e9c9c Mon Sep 17 00:00:00 2001 From: Nils Henrik Hals <3185998+Strepto@users.noreply.github.com> Date: Mon, 14 Oct 2024 12:14:44 +0200 Subject: [PATCH 15/41] Change treeIndexes to uint --- .../Operations/ExteriorSplitterTests.cs | 2 +- .../Utils/HierarchyComposerConverterTests.cs | 22 ------------- CadRevealComposer/CadRevealComposerRunner.cs | 26 +++++++++------ CadRevealComposer/CadRevealNode.cs | 2 +- .../Operations/ExteriorSplitter.cs | 2 +- .../Operations/HierarchyComposerConverter.cs | 10 +++--- .../SectorSplitting/PrioritySplittingUtils.cs | 32 ++++++++++--------- CadRevealComposer/Primitives/APrimitive.cs | 28 ++++++++-------- CadRevealComposer/SceneCreator.cs | 2 +- .../FbxProviderTests.cs | 2 +- .../FbxNodeToCadRevealNodeConverter.cs | 2 +- CadRevealObjProvider/ObjProvider.cs | 2 +- .../Converters/CircleConverterHelper.cs | 2 +- .../Converters/RvmBoxConverter.cs | 2 +- .../Converters/RvmCircularTorusConverter.cs | 2 +- .../Converters/RvmCylinderConverter.cs | 2 +- .../Converters/RvmEllipticalDishConverter.cs | 2 +- .../Converters/RvmFacetGroupConverter.cs | 2 +- .../Converters/RvmPrimitiveToAPrimitive.cs | 2 +- .../Converters/RvmPyramidConverter.cs | 2 +- .../RvmRectangularTorusConverter.cs | 2 +- .../Converters/RvmSnoutConverter.cs | 8 ++--- .../Converters/RvmSphereConverter.cs | 2 +- .../Converters/RvmSphericalDishConverter.cs | 2 +- CadRevealRvmProvider/RvmProtoMesh.cs | 6 ++-- .../RvmStoreToCadRevealNodesConverter.cs | 2 -- .../Functions/DatabaseComposer.cs | 2 +- 27 files changed, 79 insertions(+), 93 deletions(-) diff --git a/CadRevealComposer.Tests/Operations/ExteriorSplitterTests.cs b/CadRevealComposer.Tests/Operations/ExteriorSplitterTests.cs index 8b1fc6e69..b204fd5b5 100644 --- a/CadRevealComposer.Tests/Operations/ExteriorSplitterTests.cs +++ b/CadRevealComposer.Tests/Operations/ExteriorSplitterTests.cs @@ -25,7 +25,7 @@ public void ExteriorTest() /// /// The exterior splitter uses axis aligned bounding box for Box primitive. All other data is irrelevant. /// - private static Box CreateBoxCenteredInOrigin(ulong treeIndex, float boxSize) + private static Box CreateBoxCenteredInOrigin(uint treeIndex, float boxSize) { return new Box( Matrix4x4.Identity, diff --git a/CadRevealComposer.Tests/Utils/HierarchyComposerConverterTests.cs b/CadRevealComposer.Tests/Utils/HierarchyComposerConverterTests.cs index 202f29f05..22379810f 100644 --- a/CadRevealComposer.Tests/Utils/HierarchyComposerConverterTests.cs +++ b/CadRevealComposer.Tests/Utils/HierarchyComposerConverterTests.cs @@ -56,26 +56,4 @@ public void ConvertToHierarchyNodes_GivenRevealNodes_ConvertsWithoutCrashing() ); Assert.That(firstNode.OptionalDiagnosticInfo, Is.EqualTo(arrangedJson)); } - - [Test] - public void ConvertToHierarchyNodes_GivenRevealNodes_CrashesIfTreeIndexIsOutOfRange() - { - var node1 = new CadRevealNode() - { - TreeIndex = uint.MaxValue + 1L, - Children = Array.Empty(), - BoundingBoxAxisAligned = new BoundingBox(-Vector3.One, Vector3.One), - Name = "RootNode", - Attributes = { { "RefNo", "=123/321" }, { "Tag", "23L0001" } }, - Parent = null, - Geometries = Array.Empty() - }; - - var nodes = new[] { node1 }; - - Assert.That( - () => HierarchyComposerConverter.ConvertToHierarchyNodes(nodes), - Throws.Exception.Message.StartsWith("input was higher than the max uint32 value") - ); - } } diff --git a/CadRevealComposer/CadRevealComposerRunner.cs b/CadRevealComposer/CadRevealComposerRunner.cs index f202b1901..d280c189e 100644 --- a/CadRevealComposer/CadRevealComposerRunner.cs +++ b/CadRevealComposer/CadRevealComposerRunner.cs @@ -37,9 +37,9 @@ IReadOnlyList modelFormatProviders if (cachedAPrimitives != null) { Console.WriteLine("Using developer cache file: " + cacheFile); - ProcessPrimitives(cachedAPrimitives, outputDirectory, modelParameters, composerParameters); + SplitAndExportSectors(cachedAPrimitives, outputDirectory, modelParameters, composerParameters); Console.WriteLine( - $"Ran {nameof(ProcessPrimitives)} using cache file {cacheFile} in {totalTimeElapsed.Elapsed}" + $"Ran {nameof(SplitAndExportSectors)} using cache file {cacheFile} in {totalTimeElapsed.Elapsed}" ); return; } @@ -56,7 +56,7 @@ IReadOnlyList modelFormatProviders var filtering = new NodeNameFiltering(composerParameters.NodeNameExcludeRegex); - ModelMetadata metadataFromAllFiles = new ModelMetadata(new()); + ModelMetadata metadataFromAllFiles = new ModelMetadata(new Dictionary()); foreach (IModelFormatProvider modelFormatProvider in modelFormatProviders) { var timer = Stopwatch.StartNew(); @@ -86,6 +86,9 @@ IReadOnlyList modelFormatProviders // collect all nodes for later sector division of the entire scene nodesToExport.AddRange(cadRevealNodes); + // Todo should this return a new list of cadrevealnodes instead of mutating the input? + PrioritySplittingUtils.SetPriorityForHighlightSplittingWithMutation(cadRevealNodes); + var inputGeometries = cadRevealNodes.AsParallel().AsOrdered().SelectMany(x => x.Geometries).ToArray(); var geometriesIncludingMeshes = modelFormatProvider.ProcessGeometries( @@ -124,7 +127,7 @@ IReadOnlyList modelFormatProviders devCache.WriteToPrimitiveCache(geometriesToProcessArray, inputFolderPath); } - var treeIndexToPrioritizedSector = ProcessPrimitives( + var treeIndexToPrioritizedSector = SplitAndExportSectors( geometriesToProcessArray, outputDirectory, modelParameters, @@ -139,14 +142,19 @@ IReadOnlyList modelFormatProviders // TODO Is this the best place to do this? var prioritizedSectorInsertionStopwatch = Stopwatch.StartNew(); - SceneCreator.AddPrioritizedSectorsToDatabase(treeIndexToPrioritizedSector, outputDirectory); + SceneCreator.AddPrioritizedSectorsToDatabase( + treeIndexToPrioritizedSector.TreeIndexToSectorIdMap, + outputDirectory + ); Console.WriteLine($"Inserted prioritized sectors in db in {prioritizedSectorInsertionStopwatch.Elapsed}"); Console.WriteLine($"Export Finished. Wrote output files to \"{Path.GetFullPath(outputDirectory.FullName)}\""); Console.WriteLine($"Convert completed in {totalTimeElapsed.Elapsed}"); } - public static Dictionary ProcessPrimitives( + public record SplitAndExportResults(Dictionary TreeIndexToSectorIdMap); + + public static SplitAndExportResults SplitAndExportSectors( APrimitive[] allPrimitives, DirectoryInfo outputDirectory, ModelParameters modelParameters, @@ -209,7 +217,7 @@ ComposerParameters composerParameters Console.WriteLine($"Wrote scene file in {stopwatch.Elapsed}"); stopwatch.Restart(); - return GetTreeIndexToSectorIdDict(prioritizedSectors); + return new SplitAndExportResults(TreeIndexToSectorIdMap: GetTreeIndexToSectorIdDict(prioritizedSectors)); } /// @@ -231,9 +239,9 @@ uint newRootId return remappedPrioritizedSectors; } - private static Dictionary GetTreeIndexToSectorIdDict(InternalSector[] sectors) + private static Dictionary GetTreeIndexToSectorIdDict(InternalSector[] sectors) { - var sectorIdToTreeIndex = new Dictionary(); + var sectorIdToTreeIndex = new Dictionary(); foreach (var sector in sectors) { var sectorId = sector.SectorId; diff --git a/CadRevealComposer/CadRevealNode.cs b/CadRevealComposer/CadRevealNode.cs index 8d957512f..b23fcb3e5 100644 --- a/CadRevealComposer/CadRevealNode.cs +++ b/CadRevealComposer/CadRevealNode.cs @@ -64,7 +64,7 @@ public bool EqualTo(BoundingBox other, int precisionDigits = 3) public class CadRevealNode { - public required ulong TreeIndex { get; init; } + public required uint TreeIndex { get; init; } public required string Name { get; init; } // TODO support Store, Model, File and maybe not RVM diff --git a/CadRevealComposer/Operations/ExteriorSplitter.cs b/CadRevealComposer/Operations/ExteriorSplitter.cs index 1b318c539..55c89eee0 100644 --- a/CadRevealComposer/Operations/ExteriorSplitter.cs +++ b/CadRevealComposer/Operations/ExteriorSplitter.cs @@ -260,7 +260,7 @@ private static Node[] CreateNodes(APrimitive[] primitives) return new TessellatedPrimitive(triangles, primitive); } - static Node ConvertNode(IGrouping nodeGroup) + static Node ConvertNode(IGrouping nodeGroup) { var boundingBox = nodeGroup.ToArray().CalculateBoundingBox(); diff --git a/CadRevealComposer/Operations/HierarchyComposerConverter.cs b/CadRevealComposer/Operations/HierarchyComposerConverter.cs index b79a9e442..ece7f6c88 100644 --- a/CadRevealComposer/Operations/HierarchyComposerConverter.cs +++ b/CadRevealComposer/Operations/HierarchyComposerConverter.cs @@ -60,14 +60,14 @@ public static IReadOnlyList ConvertToHierarchyNodes(IReadOnlyList return new HierarchyNode { - NodeId = ConvertUlongToUintOrThrowIfTooLarge(revealNode.TreeIndex), - EndId = ConvertUlongToUintOrThrowIfTooLarge(GetLastIndexInChildrenIncludingSelf(revealNode)), + NodeId = revealNode.TreeIndex, + EndId = GetLastIndexInChildrenIncludingSelf(revealNode), RefNoPrefix = maybeRefNo?.Prefix, RefNoDb = maybeRefNo?.DbNo, RefNoSequence = maybeRefNo?.SequenceNo, Name = revealNode.Name, - TopNodeId = ConvertUlongToUintOrThrowIfTooLarge(rootNode.TreeIndex), - ParentId = maybeParent != null ? ConvertUlongToUintOrThrowIfTooLarge(maybeParent.TreeIndex) : null, + TopNodeId = rootNode.TreeIndex, + ParentId = maybeParent?.TreeIndex, PDMSData = FilterRedundantAttributes(revealNode.Attributes), HasMesh = hasMesh, AABB = aabb, @@ -79,7 +79,7 @@ public static IReadOnlyList ConvertToHierarchyNodes(IReadOnlyList /// Finds the last index of this node or its children. Including its own index. /// Assumes children are sorted by index /// - private static ulong GetLastIndexInChildrenIncludingSelf(CadRevealNode node) + private static uint GetLastIndexInChildrenIncludingSelf(CadRevealNode node) { while (true) { diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs index 68bef5136..7979c2dfb 100644 --- a/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs @@ -8,11 +8,11 @@ public static class PrioritySplittingUtils { - private static string[] PrioritizedDisciplines = { "PIPE" }; + private static readonly string[] PrioritizedDisciplines = ["PIPE"]; - public static void SetPriorityForHighlightSplitting(CadRevealNode[] nodes) + public static void SetPriorityForHighlightSplittingWithMutation(IReadOnlyList nodes) { - var disciplineFilteredNodes = FilterAndSetDiscipline(nodes).ToArray(); + var disciplineFilteredNodes = FilterByDisciplineAndAddDisciplineMetadata(nodes).ToArray(); // TODO // Are we going to use the custom STID mapper, or should we wait for a more official solution? @@ -20,25 +20,27 @@ public static void SetPriorityForHighlightSplitting(CadRevealNode[] nodes) var tagMappingAndDisciplineFilteredNodes = disciplineFilteredNodes; // StidTagMapper.FilterNodesWithTag(disciplineFilteredNodes); var tagAndDisciplineFilteredNodes = FilterByIfTagExists(tagMappingAndDisciplineFilteredNodes).ToArray(); - SetPriortyOnNodesAndChildren(tagAndDisciplineFilteredNodes); + SetPriorityOnNodesAndChildren(tagAndDisciplineFilteredNodes); } - private static IEnumerable FilterAndSetDiscipline(CadRevealNode[] nodes) + private static IEnumerable FilterByDisciplineAndAddDisciplineMetadata( + IReadOnlyList nodes + ) { foreach (var node in nodes) { var discipline = node.Attributes.GetValueOrNull("Discipline"); - if (discipline != null && PrioritizedDisciplines.Contains(discipline)) - { - var children = CadRevealNode.GetAllNodesFlat(node); - foreach (var child in children) - { - child.Geometries = child.Geometries.Select(g => g with { Discipline = discipline }).ToArray(); - } + if (!PrioritizedDisciplines.Contains(discipline)) + continue; - yield return node; + var children = CadRevealNode.GetAllNodesFlat(node); + foreach (var child in children) + { + child.Geometries = child.Geometries.Select(g => g with { Discipline = discipline }).ToArray(); } + + yield return node; } } @@ -55,7 +57,7 @@ private static IEnumerable FilterByIfTagExists(CadRevealNode[] no } } - private static void SetPriortyOnNodesAndChildren(CadRevealNode[] nodes) + private static void SetPriorityOnNodesAndChildren(CadRevealNode[] nodes) { foreach (var node in nodes) { @@ -67,7 +69,7 @@ private static void SetPriortyOnNodesAndChildren(CadRevealNode[] nodes) } } - public static Node[] ConvertPrimitiveGroupsToNodes(IEnumerable> geometryGroups) + public static Node[] ConvertPrimitiveGroupsToNodes(IEnumerable> geometryGroups) { float sizeCutoff = 0.1f; diff --git a/CadRevealComposer/Primitives/APrimitive.cs b/CadRevealComposer/Primitives/APrimitive.cs index b3370ca5d..a2f7fd120 100644 --- a/CadRevealComposer/Primitives/APrimitive.cs +++ b/CadRevealComposer/Primitives/APrimitive.cs @@ -7,14 +7,14 @@ namespace CadRevealComposer.Primitives; // Reveal GLTF model [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] -public sealed record Box(Matrix4x4 InstanceMatrix, ulong TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox) +public sealed record Box(Matrix4x4 InstanceMatrix, uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] public sealed record Circle( Matrix4x4 InstanceMatrix, Vector3 Normal, - ulong TreeIndex, + uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox ) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); @@ -28,7 +28,7 @@ public sealed record Cone( Vector3 LocalXAxis, float RadiusA, float RadiusB, - ulong TreeIndex, + uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox ) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); @@ -40,7 +40,7 @@ public sealed record EccentricCone( Vector3 Normal, float RadiusA, float RadiusB, - ulong TreeIndex, + uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox ) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); @@ -52,7 +52,7 @@ public sealed record EllipsoidSegment( float Height, Vector3 Center, Vector3 Normal, - ulong TreeIndex, + uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox ) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); @@ -67,7 +67,7 @@ public sealed record GeneralCylinder( Vector4 PlaneA, Vector4 PlaneB, float Radius, - ulong TreeIndex, + uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox ) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); @@ -79,17 +79,17 @@ public sealed record GeneralRing( Matrix4x4 InstanceMatrix, Vector3 Normal, float Thickness, - ulong TreeIndex, + uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox ) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] -public sealed record Nut(Matrix4x4 InstanceMatrix, ulong TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox) +public sealed record Nut(Matrix4x4 InstanceMatrix, uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] -public sealed record Quad(Matrix4x4 InstanceMatrix, ulong TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox) +public sealed record Quad(Matrix4x4 InstanceMatrix, uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] @@ -98,7 +98,7 @@ public sealed record TorusSegment( Matrix4x4 InstanceMatrix, float Radius, float TubeRadius, - ulong TreeIndex, + uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox ) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); @@ -109,7 +109,7 @@ public sealed record Trapezium( Vector3 Vertex2, Vector3 Vertex3, Vector3 Vertex4, - ulong TreeIndex, + uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox ) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); @@ -130,13 +130,13 @@ public sealed record InstancedMesh( ulong InstanceId, Mesh TemplateMesh, Matrix4x4 InstanceMatrix, - ulong TreeIndex, + uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox ) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] -public sealed record TriangleMesh(Mesh Mesh, ulong TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox) +public sealed record TriangleMesh(Mesh Mesh, uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); [ProtoContract(SkipConstructor = true, IgnoreUnknownSubTypes = false)] @@ -154,7 +154,7 @@ public sealed record TriangleMesh(Mesh Mesh, ulong TreeIndex, Color Color, Bound [ProtoInclude(511, typeof(Box))] [ProtoInclude(512, typeof(EccentricCone))] public abstract record APrimitive( - [property: ProtoMember(1)] ulong TreeIndex, + [property: ProtoMember(1)] uint TreeIndex, [property: ProtoMember(2)] Color Color, [property: ProtoMember(3)] BoundingBox AxisAlignedBoundingBox ) diff --git a/CadRevealComposer/SceneCreator.cs b/CadRevealComposer/SceneCreator.cs index 32c468109..08d562e1d 100644 --- a/CadRevealComposer/SceneCreator.cs +++ b/CadRevealComposer/SceneCreator.cs @@ -54,7 +54,7 @@ public static void ExportHierarchyDatabase(string databasePath, IReadOnlyList treeIndexToPrioritizedSector, + Dictionary treeIndexToPrioritizedSector, DirectoryInfo outputDirectory ) { diff --git a/CadRevealFbxProvider.Tests/FbxProviderTests.cs b/CadRevealFbxProvider.Tests/FbxProviderTests.cs index 7e643a039..526fb02cc 100644 --- a/CadRevealFbxProvider.Tests/FbxProviderTests.cs +++ b/CadRevealFbxProvider.Tests/FbxProviderTests.cs @@ -156,7 +156,7 @@ public void SampleModel_Load() var geometriesToProcess = flatNodes.SelectMany(x => x.Geometries).ToArray(); Assert.That(geometriesToProcess, Has.None.TypeOf()); // All meshes in the input data are used more than once Assert.That(geometriesToProcess, Has.All.TypeOf()); // Because the geometriesThatShouldBeInstanced list is empty - CadRevealComposerRunner.ProcessPrimitives( + CadRevealComposerRunner.SplitAndExportSectors( geometriesToProcess.ToArray(), OutputDirectoryCorrect, ModelParameters, diff --git a/CadRevealFbxProvider/FbxNodeToCadRevealNodeConverter.cs b/CadRevealFbxProvider/FbxNodeToCadRevealNodeConverter.cs index 46a2b81b7..6523f5f2d 100644 --- a/CadRevealFbxProvider/FbxNodeToCadRevealNodeConverter.cs +++ b/CadRevealFbxProvider/FbxNodeToCadRevealNodeConverter.cs @@ -120,7 +120,7 @@ public static class FbxNodeToCadRevealNodeConverter } private static APrimitive? ReadGeometry( - ulong treeIndex, + uint treeIndex, FbxNode node, InstanceIdGenerator instanceIdGenerator, IDictionary meshInstanceLookup, diff --git a/CadRevealObjProvider/ObjProvider.cs b/CadRevealObjProvider/ObjProvider.cs index 43143279e..9879ccc57 100644 --- a/CadRevealObjProvider/ObjProvider.cs +++ b/CadRevealObjProvider/ObjProvider.cs @@ -62,7 +62,7 @@ FileInfo filePath in filesToParse.Where(x => x.Extension.Equals(".obj", StringCo return (nodes, null); } - private static APrimitive[] ConvertObjMeshToAPrimitive(ObjMesh mesh, ulong treeIndex) + private static APrimitive[] ConvertObjMeshToAPrimitive(ObjMesh mesh, uint treeIndex) { return new APrimitive[] { diff --git a/CadRevealRvmProvider/Converters/CircleConverterHelper.cs b/CadRevealRvmProvider/Converters/CircleConverterHelper.cs index 8797d9885..159ea4171 100644 --- a/CadRevealRvmProvider/Converters/CircleConverterHelper.cs +++ b/CadRevealRvmProvider/Converters/CircleConverterHelper.cs @@ -15,7 +15,7 @@ public static class CircleConverterHelper /// /// /// - public static Circle ConvertCircle(Matrix4x4 matrix, Vector3 normal, ulong treeIndex, Color color) + public static Circle ConvertCircle(Matrix4x4 matrix, Vector3 normal, uint treeIndex, Color color) { // Circles don't have bounding boxes in RVM, since they are only used as caps // This means that we have to calculate a new bounding box for the circle diff --git a/CadRevealRvmProvider/Converters/RvmBoxConverter.cs b/CadRevealRvmProvider/Converters/RvmBoxConverter.cs index eeee61c15..bb9125efd 100644 --- a/CadRevealRvmProvider/Converters/RvmBoxConverter.cs +++ b/CadRevealRvmProvider/Converters/RvmBoxConverter.cs @@ -10,7 +10,7 @@ public static class RvmBoxConverter { public static IEnumerable ConvertToRevealPrimitive( this RvmBox rvmBox, - ulong treeIndex, + uint treeIndex, Color color, FailedPrimitivesLogObject failedPrimitivesLogObject ) diff --git a/CadRevealRvmProvider/Converters/RvmCircularTorusConverter.cs b/CadRevealRvmProvider/Converters/RvmCircularTorusConverter.cs index 06fea91af..8ed5a2c90 100644 --- a/CadRevealRvmProvider/Converters/RvmCircularTorusConverter.cs +++ b/CadRevealRvmProvider/Converters/RvmCircularTorusConverter.cs @@ -12,7 +12,7 @@ public static class RvmCircularTorusConverter { public static IEnumerable ConvertToRevealPrimitive( this RvmCircularTorus rvmCircularTorus, - ulong treeIndex, + uint treeIndex, Color color, FailedPrimitivesLogObject failedPrimitivesLogObject ) diff --git a/CadRevealRvmProvider/Converters/RvmCylinderConverter.cs b/CadRevealRvmProvider/Converters/RvmCylinderConverter.cs index 826e08ef1..557af0b24 100644 --- a/CadRevealRvmProvider/Converters/RvmCylinderConverter.cs +++ b/CadRevealRvmProvider/Converters/RvmCylinderConverter.cs @@ -11,7 +11,7 @@ public static class RvmCylinderConverter { public static IEnumerable ConvertToRevealPrimitive( this RvmCylinder rvmCylinder, - ulong treeIndex, + uint treeIndex, Color color, FailedPrimitivesLogObject failedPrimitivesLogObject ) diff --git a/CadRevealRvmProvider/Converters/RvmEllipticalDishConverter.cs b/CadRevealRvmProvider/Converters/RvmEllipticalDishConverter.cs index 09e4a7d3a..a0dd1e818 100644 --- a/CadRevealRvmProvider/Converters/RvmEllipticalDishConverter.cs +++ b/CadRevealRvmProvider/Converters/RvmEllipticalDishConverter.cs @@ -11,7 +11,7 @@ public static class RvmEllipticalDishConverter { public static IEnumerable ConvertToRevealPrimitive( this RvmEllipticalDish rvmEllipticalDish, - ulong treeIndex, + uint treeIndex, Color color, FailedPrimitivesLogObject failedPrimitivesLogObject ) diff --git a/CadRevealRvmProvider/Converters/RvmFacetGroupConverter.cs b/CadRevealRvmProvider/Converters/RvmFacetGroupConverter.cs index 9537b76a3..488d94e6f 100644 --- a/CadRevealRvmProvider/Converters/RvmFacetGroupConverter.cs +++ b/CadRevealRvmProvider/Converters/RvmFacetGroupConverter.cs @@ -8,7 +8,7 @@ public static class RvmFacetGroupConverter { public static IEnumerable ConvertToRevealPrimitive( this RvmFacetGroup rvmFacetGroup, - ulong treeIndex, + uint treeIndex, Color color, FailedPrimitivesLogObject failedPrimitivesLogObject ) diff --git a/CadRevealRvmProvider/Converters/RvmPrimitiveToAPrimitive.cs b/CadRevealRvmProvider/Converters/RvmPrimitiveToAPrimitive.cs index 7065be58e..b0559bd53 100644 --- a/CadRevealRvmProvider/Converters/RvmPrimitiveToAPrimitive.cs +++ b/CadRevealRvmProvider/Converters/RvmPrimitiveToAPrimitive.cs @@ -6,7 +6,7 @@ public static class RvmPrimitiveToAPrimitive { public static IEnumerable FromRvmPrimitive( - ulong treeIndex, + uint treeIndex, RvmPrimitive rvmPrimitive, RvmNode rvmNode, FailedPrimitivesLogObject failedPrimitivesLogObject diff --git a/CadRevealRvmProvider/Converters/RvmPyramidConverter.cs b/CadRevealRvmProvider/Converters/RvmPyramidConverter.cs index f1ce89183..726f14f6e 100644 --- a/CadRevealRvmProvider/Converters/RvmPyramidConverter.cs +++ b/CadRevealRvmProvider/Converters/RvmPyramidConverter.cs @@ -12,7 +12,7 @@ public static class RvmPyramidConverter { public static IEnumerable ConvertToRevealPrimitive( this RvmPyramid rvmPyramid, - ulong treeIndex, + uint treeIndex, Color color, FailedPrimitivesLogObject failedPrimitivesLogObject ) diff --git a/CadRevealRvmProvider/Converters/RvmRectangularTorusConverter.cs b/CadRevealRvmProvider/Converters/RvmRectangularTorusConverter.cs index 9c63d97f8..3a5927bc9 100644 --- a/CadRevealRvmProvider/Converters/RvmRectangularTorusConverter.cs +++ b/CadRevealRvmProvider/Converters/RvmRectangularTorusConverter.cs @@ -11,7 +11,7 @@ public static class RvmRectangularTorusConverter { public static IEnumerable ConvertToRevealPrimitive( this RvmRectangularTorus rvmRectangularTorus, - ulong treeIndex, + uint treeIndex, Color color, FailedPrimitivesLogObject failedPrimitivesLogObject ) diff --git a/CadRevealRvmProvider/Converters/RvmSnoutConverter.cs b/CadRevealRvmProvider/Converters/RvmSnoutConverter.cs index 9afe143c4..da6f632b9 100644 --- a/CadRevealRvmProvider/Converters/RvmSnoutConverter.cs +++ b/CadRevealRvmProvider/Converters/RvmSnoutConverter.cs @@ -14,7 +14,7 @@ public static class RvmSnoutConverter { public static IEnumerable ConvertToRevealPrimitive( this RvmSnout rvmSnout, - ulong treeIndex, + uint treeIndex, Color color, FailedPrimitivesLogObject failedPrimitivesLogObject ) @@ -104,7 +104,7 @@ private static IEnumerable CreateCone( Vector3 normal, float radiusA, float radiusB, - ulong treeIndex, + uint treeIndex, Color color, BoundingBox bbox ) @@ -164,7 +164,7 @@ private static IEnumerable CreateEccentricCone( float radiusA, float radiusB, float length, - ulong treeIndex, + uint treeIndex, Color color, BoundingBox bbox ) @@ -225,7 +225,7 @@ private static IEnumerable CreateCylinderWithShear( Vector3 normal, float height, Vector3 scale, - ulong treeIndex, + uint treeIndex, Color color, BoundingBox bbox, FailedPrimitivesLogObject? failedPrimitivesLogObject = null diff --git a/CadRevealRvmProvider/Converters/RvmSphereConverter.cs b/CadRevealRvmProvider/Converters/RvmSphereConverter.cs index 81142dec4..95985472f 100644 --- a/CadRevealRvmProvider/Converters/RvmSphereConverter.cs +++ b/CadRevealRvmProvider/Converters/RvmSphereConverter.cs @@ -9,7 +9,7 @@ public static class RvmSphereConverter { public static IEnumerable ConvertToRevealPrimitive( this RvmSphere rvmSphere, - ulong treeIndex, + uint treeIndex, Color color, FailedPrimitivesLogObject failedPrimitivesLogObject ) diff --git a/CadRevealRvmProvider/Converters/RvmSphericalDishConverter.cs b/CadRevealRvmProvider/Converters/RvmSphericalDishConverter.cs index 91f3e6744..c7385bf43 100644 --- a/CadRevealRvmProvider/Converters/RvmSphericalDishConverter.cs +++ b/CadRevealRvmProvider/Converters/RvmSphericalDishConverter.cs @@ -11,7 +11,7 @@ public static class RvmSphericalDishConverter { public static IEnumerable ConvertToRevealPrimitive( this RvmSphericalDish rvmSphericalDish, - ulong treeIndex, + uint treeIndex, Color color, FailedPrimitivesLogObject failedPrimitivesLogObject ) diff --git a/CadRevealRvmProvider/RvmProtoMesh.cs b/CadRevealRvmProvider/RvmProtoMesh.cs index 280cc69ff..d2c5cb517 100644 --- a/CadRevealRvmProvider/RvmProtoMesh.cs +++ b/CadRevealRvmProvider/RvmProtoMesh.cs @@ -9,21 +9,21 @@ namespace CadRevealRvmProvider; // instancing processing - converted to GLTF model in the end (InstancedMesh/TriangleMesh) public abstract record ProtoMesh( RvmPrimitive RvmPrimitive, - ulong TreeIndex, + uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox ) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); public sealed record ProtoMeshFromFacetGroup( RvmFacetGroup FacetGroup, - ulong TreeIndex, + uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox ) : ProtoMesh(FacetGroup, TreeIndex, Color, AxisAlignedBoundingBox); public sealed record ProtoMeshFromRvmPyramid( RvmPyramid Pyramid, - ulong TreeIndex, + uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox ) : ProtoMesh(Pyramid, TreeIndex, Color, AxisAlignedBoundingBox); diff --git a/CadRevealRvmProvider/RvmStoreToCadRevealNodesConverter.cs b/CadRevealRvmProvider/RvmStoreToCadRevealNodesConverter.cs index b45db6fd5..75ece791b 100644 --- a/CadRevealRvmProvider/RvmStoreToCadRevealNodesConverter.cs +++ b/CadRevealRvmProvider/RvmStoreToCadRevealNodesConverter.cs @@ -43,8 +43,6 @@ NodeNameFiltering nodeNameFiltering var allNodes = cadRevealRootNodes.SelectMany(CadRevealNode.GetAllNodesFlat).ToArray(); - PrioritySplittingUtils.SetPriorityForHighlightSplitting(allNodes); - return allNodes; } diff --git a/HierarchyComposer/Functions/DatabaseComposer.cs b/HierarchyComposer/Functions/DatabaseComposer.cs index 67b82320b..bf524278f 100644 --- a/HierarchyComposer/Functions/DatabaseComposer.cs +++ b/HierarchyComposer/Functions/DatabaseComposer.cs @@ -255,7 +255,7 @@ public void ComposeDatabase(IReadOnlyList inputNodes, string outp } public static void AddTreeIndexToSectorToDatabase( - Dictionary treeIndexToSectorId, + Dictionary treeIndexToSectorId, DirectoryInfo outputDirectory ) { From 414bc92abe506bf8a88084909176deb87dad1f25 Mon Sep 17 00:00:00 2001 From: Nils Henrik Hals <3185998+Strepto@users.noreply.github.com> Date: Mon, 14 Oct 2024 12:42:39 +0200 Subject: [PATCH 16/41] Add better types to TreeIndex to SectorId map --- CadRevealComposer/CadRevealComposerRunner.cs | 25 +++++++++---------- CadRevealComposer/SceneCreator.cs | 7 ++++-- .../Functions/DatabaseComposer.cs | 17 +++++-------- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/CadRevealComposer/CadRevealComposerRunner.cs b/CadRevealComposer/CadRevealComposerRunner.cs index d280c189e..c6578cd64 100644 --- a/CadRevealComposer/CadRevealComposerRunner.cs +++ b/CadRevealComposer/CadRevealComposerRunner.cs @@ -127,7 +127,7 @@ IReadOnlyList modelFormatProviders devCache.WriteToPrimitiveCache(geometriesToProcessArray, inputFolderPath); } - var treeIndexToPrioritizedSector = SplitAndExportSectors( + var splitExportResults = SplitAndExportSectors( geometriesToProcessArray, outputDirectory, modelParameters, @@ -140,19 +140,19 @@ IReadOnlyList modelFormatProviders WriteParametersToParamsFile(modelParameters, composerParameters, outputDirectory); - // TODO Is this the best place to do this? + // TODO Is this the best place to add sector id mapping to the database? + // TODO Cont: Today we start hierarchy export before we have the sector id mapping ready. Is it ok to mutate the database after it has been exported? var prioritizedSectorInsertionStopwatch = Stopwatch.StartNew(); - SceneCreator.AddPrioritizedSectorsToDatabase( - treeIndexToPrioritizedSector.TreeIndexToSectorIdMap, - outputDirectory - ); + SceneCreator.AddPrioritizedSectorsToDatabase(splitExportResults.TreeIndexToSectorId, outputDirectory); Console.WriteLine($"Inserted prioritized sectors in db in {prioritizedSectorInsertionStopwatch.Elapsed}"); Console.WriteLine($"Export Finished. Wrote output files to \"{Path.GetFullPath(outputDirectory.FullName)}\""); Console.WriteLine($"Convert completed in {totalTimeElapsed.Elapsed}"); } - public record SplitAndExportResults(Dictionary TreeIndexToSectorIdMap); + public record SplitAndExportResults(List TreeIndexToSectorId); + + public record TreeIndexSectorIdPair(uint TreeIndex, uint SectorId); public static SplitAndExportResults SplitAndExportSectors( APrimitive[] allPrimitives, @@ -217,7 +217,7 @@ ComposerParameters composerParameters Console.WriteLine($"Wrote scene file in {stopwatch.Elapsed}"); stopwatch.Restart(); - return new SplitAndExportResults(TreeIndexToSectorIdMap: GetTreeIndexToSectorIdDict(prioritizedSectors)); + return new SplitAndExportResults(TreeIndexToSectorId: GetTreeIndexToSectorIdDict(prioritizedSectors)); } /// @@ -239,20 +239,19 @@ uint newRootId return remappedPrioritizedSectors; } - private static Dictionary GetTreeIndexToSectorIdDict(InternalSector[] sectors) + private static List GetTreeIndexToSectorIdDict(InternalSector[] sectors) { - var sectorIdToTreeIndex = new Dictionary(); + var sectorIdToTreeIndex = new HashSet(); foreach (var sector in sectors) { var sectorId = sector.SectorId; foreach (var geometry in sector.Geometries) { - // TODO: Cant a TreeIndex be in many sectors? // NIH - sectorIdToTreeIndex.TryAdd(geometry.TreeIndex, sectorId); + sectorIdToTreeIndex.Add(new TreeIndexSectorIdPair(geometry.TreeIndex, sectorId)); } } - return sectorIdToTreeIndex; + return sectorIdToTreeIndex.ToList(); } /// diff --git a/CadRevealComposer/SceneCreator.cs b/CadRevealComposer/SceneCreator.cs index 08d562e1d..723566e45 100644 --- a/CadRevealComposer/SceneCreator.cs +++ b/CadRevealComposer/SceneCreator.cs @@ -54,11 +54,14 @@ public static void ExportHierarchyDatabase(string databasePath, IReadOnlyList treeIndexToPrioritizedSector, + IReadOnlyList treeIndexToPrioritizedSector, DirectoryInfo outputDirectory ) { - DatabaseComposer.AddTreeIndexToSectorToDatabase(treeIndexToPrioritizedSector, outputDirectory); + DatabaseComposer.AddTreeIndexToSectorToDatabase( + treeIndexToPrioritizedSector.Select(x => (x.TreeIndex, x.SectorId)).ToList(), + outputDirectory + ); } public static void CreateSceneFile( diff --git a/HierarchyComposer/Functions/DatabaseComposer.cs b/HierarchyComposer/Functions/DatabaseComposer.cs index bf524278f..98979c043 100644 --- a/HierarchyComposer/Functions/DatabaseComposer.cs +++ b/HierarchyComposer/Functions/DatabaseComposer.cs @@ -255,7 +255,7 @@ public void ComposeDatabase(IReadOnlyList inputNodes, string outp } public static void AddTreeIndexToSectorToDatabase( - Dictionary treeIndexToSectorId, + IReadOnlyList<(uint TreeIndex, uint SectorId)> treeIndexToSectorId, DirectoryInfo outputDirectory ) { @@ -266,12 +266,12 @@ DirectoryInfo outputDirectory var createTableCommand = connection.CreateCommand(); createTableCommand.CommandText = - "CREATE TABLE prioritizedSectors (treeindex INTEGER NOT NULL, prioritizedSectorId INTEGER NOT NULL, PRIMARY KEY (treeindex, prioritizedSectorId)) WITHOUT ROWID; "; + "CREATE TABLE PrioritizedSectors (treeindex INTEGER NOT NULL, prioritizedSectorId INTEGER NOT NULL, PRIMARY KEY (treeindex, prioritizedSectorId)) WITHOUT ROWID; "; createTableCommand.ExecuteNonQuery(); var command = connection.CreateCommand(); command.CommandText = - "INSERT OR IGNORE INTO prioritizedSectors (treeindex, prioritizedSectorId) VALUES ($TreeIndex, $PrioritizedSectorId)"; + "INSERT INTO PrioritizedSectors (treeindex, prioritizedSectorId) VALUES ($TreeIndex, $PrioritizedSectorId)"; var treeIndexParameter = command.CreateParameter(); treeIndexParameter.ParameterName = "$TreeIndex"; @@ -283,15 +283,10 @@ DirectoryInfo outputDirectory var transaction = connection.BeginTransaction(); command.Transaction = transaction; - foreach (var pair in treeIndexToSectorId) + foreach (var pair in treeIndexToSectorId.Distinct()) { - treeIndexParameter.Value = pair.Key; - } - - foreach (var pair in treeIndexToSectorId) - { - treeIndexParameter.Value = pair.Key; - prioritizedSectorIdParameter.Value = pair.Value; + treeIndexParameter.Value = pair.TreeIndex; + prioritizedSectorIdParameter.Value = pair.SectorId; command.ExecuteNonQuery(); } From 02fa36b8daa25299fee6fb3b511b672b92cd4e3c Mon Sep 17 00:00:00 2001 From: Nils Henrik Hals <3185998+Strepto@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:00:04 +0200 Subject: [PATCH 17/41] Renaming --- .../Operations/SectorSplitting/SplittingUtils.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs b/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs index eaffc201a..14847f124 100644 --- a/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs +++ b/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs @@ -146,19 +146,19 @@ private static Vector3 TruncatedAverageCenter(this IReadOnlyCollection nod public static Node[] ConvertPrimitivesToNodes(APrimitive[] primitives) { return primitives - .Select(g => + .Select(primitive => { - var geometries = new[] { g }; - var boundingBox = geometries.CalculateBoundingBox(); + APrimitive[] primitiveGeometry = [primitive]; + var boundingBox = primitiveGeometry.CalculateBoundingBox(); if (boundingBox == null) { throw new Exception("Unexpected error, the bounding box should not have been null."); } return new Node( - g.TreeIndex, - geometries, - geometries.Sum(DrawCallEstimator.EstimateByteSize), - EstimatedTriangleCount: DrawCallEstimator.Estimate(geometries).EstimatedTriangleCount, + primitive.TreeIndex, + [primitive], + primitiveGeometry.Sum(DrawCallEstimator.EstimateByteSize), + EstimatedTriangleCount: DrawCallEstimator.Estimate(primitiveGeometry).EstimatedTriangleCount, boundingBox ); }) From bb69bbd8e1871438dea044a65c9bebeec1a7534f Mon Sep 17 00:00:00 2001 From: Nils Henrik Hals <3185998+Strepto@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:36:08 +0200 Subject: [PATCH 18/41] Refactor splitting stuff --- .../Operations/SectorSplitting/Node.cs | 7 +++- .../SectorSplitting/PrioritySectorSplitter.cs | 33 +++++++++---------- .../SectorSplitting/PrioritySplittingUtils.cs | 9 +++-- .../SectorSplitting/SplittingUtils.cs | 13 ++++---- 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/CadRevealComposer/Operations/SectorSplitting/Node.cs b/CadRevealComposer/Operations/SectorSplitting/Node.cs index 871657dc8..7758a5b14 100644 --- a/CadRevealComposer/Operations/SectorSplitting/Node.cs +++ b/CadRevealComposer/Operations/SectorSplitting/Node.cs @@ -2,8 +2,13 @@ namespace CadRevealComposer.Operations.SectorSplitting; using Primitives; +/// +/// A node for use in splitting +/// Is a collection for one or more geometries, connected to a tree index. +/// The same tree index can be present in multiple nodes. +/// public record Node( - ulong NodeId, + ulong TreeIndex, APrimitive[] Geometries, long EstimatedByteSize, long EstimatedTriangleCount, diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs index 75d024cf0..1e405d379 100644 --- a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs @@ -15,27 +15,21 @@ public IEnumerable SplitIntoSectors( SequentialIdGenerator sectorIdGenerator ) { - var rootSectorId = sectorIdGenerator.GetNextId(); - const string rootPath = "/0"; - yield return SplittingUtils.CreateRootSector( - rootSectorId, - rootPath, - new BoundingBox(Vector3.Zero, Vector3.One) - ); - + var rootSector = SplittingUtils.CreateRootSector(0, "/0", new BoundingBox(Vector3.Zero, Vector3.One)); + yield return rootSector; var primitivesGroupedByDiscipline = allGeometries.GroupBy(x => x.Discipline); var sectors = new List(); foreach (var disciplineGroup in primitivesGroupedByDiscipline) { - var geometryGroups = disciplineGroup.GroupBy(x => x.TreeIndex); // Group by treeindex to avoid having one treeindex uneccessary many sectors + var geometryGroups = disciplineGroup.GroupBy(x => x.TreeIndex).OrderBy(x => x.Key); // Group by treeindex to avoid having one treeindex uneccessary many sectors var nodes = PrioritySplittingUtils.ConvertPrimitiveGroupsToNodes(geometryGroups); // Ignore outlier nodes // TODO: Decide if this is the right thing to do (Node[] regularNodes, _) = nodes.SplitNodesIntoRegularAndOutlierNodes(); - sectors.AddRange(SplitIntoTreeIndexSectors(regularNodes, rootPath, rootSectorId, sectorIdGenerator)); + sectors.AddRange(SplitIntoTreeIndexSectors(regularNodes, rootSector, sectorIdGenerator)); } foreach (var sector in sectors) @@ -49,8 +43,7 @@ SequentialIdGenerator sectorIdGenerator private IEnumerable SplitIntoTreeIndexSectors( Node[] nodes, - string rootPath, - uint rootSectorId, + InternalSector rootSector, SequentialIdGenerator sectorIdGenerator ) { @@ -58,18 +51,24 @@ SequentialIdGenerator sectorIdGenerator while (nodesUsed < nodes.Length) { - var nodesByBudget = GetNodesByBudgetSimple(nodes, nodesUsed).ToArray(); + // TODO: Should this use spatially aware splitting? Should it place nodes with similar attributes together? Ex: tags? + var nodesByBudget = GetNodesByBudgetSimple( + nodes + .OrderBy(x => x.TreeIndex) // Sorting by Id as we believe the TreeIndex to group similar parts in the hierarchy together + .ToArray(), + nodesUsed + ) + .ToArray(); nodesUsed += nodesByBudget.Length; - var sectorId = (uint)sectorIdGenerator.GetNextId(); + var sectorId = sectorIdGenerator.GetNextId(); var subtreeBoundingBox = nodesByBudget.CalculateBoundingBox(); yield return SplittingUtils.CreateSector( nodesByBudget, sectorId, - rootSectorId, - rootPath, - 1, + rootSector, + rootSector.Depth + 1, subtreeBoundingBox ); } diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs index 7979c2dfb..9245969d2 100644 --- a/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs @@ -71,22 +71,25 @@ private static void SetPriorityOnNodesAndChildren(CadRevealNode[] nodes) public static Node[] ConvertPrimitiveGroupsToNodes(IEnumerable> geometryGroups) { - float sizeCutoff = 0.1f; + const float sizeCutoff = 0.10f; // Arbitrary value return geometryGroups .Select(g => { var allGeometries = g.Select(x => x).ToArray(); - var orderedBySizeDescending = allGeometries.OrderByDescending(x => x.AxisAlignedBoundingBox.Diagonal); + var largestPart = allGeometries.Max(x => x.AxisAlignedBoundingBox.Diagonal); APrimitive[] geometries; - if (orderedBySizeDescending.First().AxisAlignedBoundingBox.Diagonal < sizeCutoff) + if (largestPart < sizeCutoff) { + // The idea here is: If the largest geometry is smaller than the size cutoff, we include all geometries because we + // believe the sum of the parts may represent a larger "whole" geometries = allGeometries; } else { geometries = allGeometries.Where(x => x.AxisAlignedBoundingBox.Diagonal > sizeCutoff).ToArray(); } + var boundingBox = geometries.CalculateBoundingBox(); if (boundingBox == null) { diff --git a/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs b/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs index 14847f124..fc7ae2b96 100644 --- a/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs +++ b/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs @@ -109,8 +109,8 @@ public static (Node[] regularNodes, Node[] outlierNodes) SplitNodesIntoRegularAn return (nodes.ToArray(), Array.Empty()); } - var regularNodes = orderedNodes.Take(outlierStartIndex).ToArray(); - var outlierNodes = orderedNodes.Skip(outlierStartIndex).ToArray(); + var regularNodes = orderedNodes.Take(outlierStartIndex).OrderBy(x => x.TreeIndex).ToArray(); + var outlierNodes = orderedNodes.Skip(outlierStartIndex).OrderBy(x => x.TreeIndex).ToArray(); return (regularNodes, outlierNodes); } @@ -230,22 +230,21 @@ public static InternalSector CreateRootSector(uint sectorId, string path, Boundi public static InternalSector CreateSector( Node[] nodes, uint sectorId, - uint? parentSectorId, - string parentPath, + InternalSector parent, int depth, BoundingBox subtreeBoundingBox ) { - var path = $"{parentPath}/{sectorId}"; - var minDiagonal = nodes.Any() ? nodes.Min(n => n.Diagonal) : 0; var maxDiagonal = nodes.Any() ? nodes.Max(n => n.Diagonal) : 0; var geometries = nodes.SelectMany(n => n.Geometries).ToArray(); var geometryBoundingBox = geometries.CalculateBoundingBox(); + var path = parent.Path + "/" + sectorId; + return new InternalSector( sectorId, - parentSectorId, + parent.SectorId, depth, path, minDiagonal, From 0bb052335fb8ca36034232c5391fed7a12d3e030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stig=20Rune=20S=C3=B8berg?= Date: Tue, 17 Sep 2024 08:28:03 +0200 Subject: [PATCH 19/41] Refactoring --- .../SectorSplitting/PrioritySplittingUtils.cs | 31 ++++++------------- .../SectorSplitting/SplittingUtils.cs | 23 ++++---------- 2 files changed, 16 insertions(+), 38 deletions(-) diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs index 9245969d2..4ca7ec802 100644 --- a/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs @@ -46,15 +46,7 @@ IReadOnlyList nodes private static IEnumerable FilterByIfTagExists(CadRevealNode[] nodes) { - foreach (var node in nodes) - { - var tag = node.Attributes.GetValueOrNull("Tag"); - - if (tag != null) - { - yield return node; - } - } + return nodes.Where(node => node.Attributes.ContainsKey("Tag")); } private static void SetPriorityOnNodesAndChildren(CadRevealNode[] nodes) @@ -77,24 +69,21 @@ public static Node[] ConvertPrimitiveGroupsToNodes(IEnumerable { var allGeometries = g.Select(x => x).ToArray(); - var largestPart = allGeometries.Max(x => x.AxisAlignedBoundingBox.Diagonal); - APrimitive[] geometries; - if (largestPart < sizeCutoff) - { - // The idea here is: If the largest geometry is smaller than the size cutoff, we include all geometries because we - // believe the sum of the parts may represent a larger "whole" - geometries = allGeometries; - } - else - { - geometries = allGeometries.Where(x => x.AxisAlignedBoundingBox.Diagonal > sizeCutoff).ToArray(); - } + + // The idea here is: If the largest geometry is smaller than the size cutoff, we include all geometries because we + // believe the sum of the parts may represent a larger "whole" + // else we only include the larger geometries + var geometries = + allGeometries.Max(x => x.AxisAlignedBoundingBox.Diagonal) < sizeCutoff + ? allGeometries + : allGeometries.Where(x => x.AxisAlignedBoundingBox.Diagonal > sizeCutoff).ToArray(); var boundingBox = geometries.CalculateBoundingBox(); if (boundingBox == null) { throw new Exception("Unexpected error, the bounding box should not have been null."); } + return new Node( g.Key, geometries, diff --git a/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs b/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs index fc7ae2b96..d16312aaf 100644 --- a/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs +++ b/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs @@ -122,25 +122,14 @@ public static (Node[] regularNodes, Node[] outlierNodes) SplitNodesIntoRegularAn /// private static Vector3 TruncatedAverageCenter(this IReadOnlyCollection nodes) { - var avgCenterX = nodes - .OrderBy(x => x.BoundingBox.Center.X) - .Skip((int)(nodes.Count * 0.05)) - .Take((int)(nodes.Count * 0.95)) - .Average(x => x.BoundingBox.Center.X); - - var avgCenterY = nodes - .OrderBy(x => x.BoundingBox.Center.Y) - .Skip((int)(nodes.Count * 0.05)) - .Take((int)(nodes.Count * 0.95)) - .Average(x => x.BoundingBox.Center.Y); - - var avgCenterZ = nodes - .OrderBy(x => x.BoundingBox.Center.Z) - .Skip((int)(nodes.Count * 0.05)) - .Take((int)(nodes.Count * 0.95)) - .Average(x => x.BoundingBox.Center.Z); + var avgCenterX = AvgCenter(nodes.Select(node => node.BoundingBox.Center.X)); + var avgCenterY = AvgCenter(nodes.Select(node => node.BoundingBox.Center.Y)); + var avgCenterZ = AvgCenter(nodes.Select(node => node.BoundingBox.Center.Z)); return new Vector3(avgCenterX, avgCenterY, avgCenterZ); + + float AvgCenter(IEnumerable values) => + values.Order().Skip((int)(nodes.Count * 0.05)).Take((int)(nodes.Count * 0.95)).Average(); } public static Node[] ConvertPrimitivesToNodes(APrimitive[] primitives) From 8b2315444aa8664f32e1fd1f16ecc6ab65515a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stig=20Rune=20S=C3=B8berg?= Date: Tue, 15 Oct 2024 10:26:40 +0200 Subject: [PATCH 20/41] Refactor splitting --- CadRevealComposer/CadRevealComposerRunner.cs | 69 ++++++++++--------- .../SectorSplitting/PrioritySectorSplitter.cs | 8 +-- .../SectorSplitting/SectorSplitterSingle.cs | 2 +- .../SectorSplitting/SplittingUtils.cs | 37 +++++----- 4 files changed, 59 insertions(+), 57 deletions(-) diff --git a/CadRevealComposer/CadRevealComposerRunner.cs b/CadRevealComposer/CadRevealComposerRunner.cs index c6578cd64..1783ec185 100644 --- a/CadRevealComposer/CadRevealComposerRunner.cs +++ b/CadRevealComposer/CadRevealComposerRunner.cs @@ -165,44 +165,18 @@ ComposerParameters composerParameters var stopwatch = Stopwatch.StartNew(); - ISectorSplitter splitter; - if (composerParameters.SingleSector) - { - splitter = new SectorSplitterSingle(); - } - else if (composerParameters.SplitIntoZones) - { - throw new ArgumentException("SplitIntoZones is no longer supported. Use regular Octree splitting instead."); - } - else - { - splitter = new SectorSplitterOctree(); - } - const uint rootSectorId = 0; var sectorIdGenerator = new SequentialIdGenerator(firstIdReturned: rootSectorId); // First split into normal sectors for the entire model - var normalSectors = splitter - .SplitIntoSectors(allPrimitives, sectorIdGenerator) - .OrderBy(x => x.SectorId) - .ToArray(); + var normalSectors = PerformSectorSplitting(allPrimitives, composerParameters, sectorIdGenerator); // Then split into prioritized sectors, these are loaded on demand based on metadata in the Hierarchy database - var prioritySplitter = new PrioritySectorSplitter(); - var prioritizedPrimitives = allPrimitives.Where(x => x.Priority > 0).ToArray(); - var prioritizedSectors = prioritySplitter - .SplitIntoSectors(prioritizedPrimitives, sectorIdGenerator) - .OrderBy(x => x.SectorId) - .ToArray(); + var prioritizedSectors = PerformPrioritizedSectorSplitting(allPrimitives, sectorIdGenerator, rootSectorId); - InternalSector[] remappedPrioritizedSectors = RemapPrioritizedSectorsRootSectorId( - prioritizedSectors, - rootSectorId - ); - var allSectors = normalSectors.Concat(remappedPrioritizedSectors).OrderBy(x => x.SectorId).ToArray(); + var allSectors = normalSectors.Concat(prioritizedSectors).OrderBy(x => x.SectorId).ToArray(); Console.WriteLine( - $"Split into {normalSectors.Length} sectors and {remappedPrioritizedSectors.Length} prioritized sectors in {stopwatch.Elapsed}" + $"Split into {normalSectors.Length} sectors and {prioritizedSectors.Length} prioritized sectors in {stopwatch.Elapsed}" ); stopwatch.Restart(); @@ -215,7 +189,6 @@ ComposerParameters composerParameters allSectors ); Console.WriteLine($"Wrote scene file in {stopwatch.Elapsed}"); - stopwatch.Restart(); return new SplitAndExportResults(TreeIndexToSectorId: GetTreeIndexToSectorIdDict(prioritizedSectors)); } @@ -239,6 +212,40 @@ uint newRootId return remappedPrioritizedSectors; } + private static InternalSector[] PerformSectorSplitting( + APrimitive[] allPrimitives, + ComposerParameters composerParameters, + SequentialIdGenerator sectorIdGenerator + ) + { + if (composerParameters.SplitIntoZones) + { + throw new ArgumentException("SplitIntoZones is no longer supported. Use regular Octree splitting instead."); + } + + ISectorSplitter splitter = composerParameters.SingleSector + ? new SectorSplitterSingle() + : new SectorSplitterOctree(); + + return splitter.SplitIntoSectors(allPrimitives, sectorIdGenerator).OrderBy(x => x.SectorId).ToArray(); + } + + private static InternalSector[] PerformPrioritizedSectorSplitting( + APrimitive[] allPrimitives, + SequentialIdGenerator sectorIdGenerator, + uint rootSectorId + ) + { + var prioritySplitter = new PrioritySectorSplitter(); + var prioritizedPrimitives = allPrimitives.Where(x => x.Priority > 0).ToArray(); + var prioritizedSectors = prioritySplitter + .SplitIntoSectors(prioritizedPrimitives, sectorIdGenerator) + .OrderBy(x => x.SectorId) + .ToArray(); + + return RemapPrioritizedSectorsRootSectorId(prioritizedSectors, rootSectorId); + } + private static List GetTreeIndexToSectorIdDict(InternalSector[] sectors) { var sectorIdToTreeIndex = new HashSet(); diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs index 1e405d379..b029398a4 100644 --- a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; -using CadRevealComposer.IdProviders; -using CadRevealComposer.Primitives; +using IdProviders; +using Primitives; public class PrioritySectorSplitter : ISectorSplitter { @@ -22,7 +22,7 @@ SequentialIdGenerator sectorIdGenerator var sectors = new List(); foreach (var disciplineGroup in primitivesGroupedByDiscipline) { - var geometryGroups = disciplineGroup.GroupBy(x => x.TreeIndex).OrderBy(x => x.Key); // Group by treeindex to avoid having one treeindex uneccessary many sectors + var geometryGroups = disciplineGroup.GroupBy(primitive => primitive.TreeIndex); // Group by treeindex to avoid having one treeindex unnecessary many sectors var nodes = PrioritySplittingUtils.ConvertPrimitiveGroupsToNodes(geometryGroups); // Ignore outlier nodes @@ -41,7 +41,7 @@ SequentialIdGenerator sectorIdGenerator } } - private IEnumerable SplitIntoTreeIndexSectors( + private static IEnumerable SplitIntoTreeIndexSectors( Node[] nodes, InternalSector rootSector, SequentialIdGenerator sectorIdGenerator diff --git a/CadRevealComposer/Operations/SectorSplitting/SectorSplitterSingle.cs b/CadRevealComposer/Operations/SectorSplitting/SectorSplitterSingle.cs index 54f845620..0fe984631 100644 --- a/CadRevealComposer/Operations/SectorSplitting/SectorSplitterSingle.cs +++ b/CadRevealComposer/Operations/SectorSplitting/SectorSplitterSingle.cs @@ -17,7 +17,7 @@ SequentialIdGenerator sectorIdGenerator yield return CreateRootSector(sectorIdGenerator.GetNextId(), allGeometries); } - private InternalSector CreateRootSector(uint sectorId, APrimitive[] geometries) + private static InternalSector CreateRootSector(uint sectorId, APrimitive[] geometries) { var bb = geometries.CalculateBoundingBox(); if (bb == null) diff --git a/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs b/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs index d16312aaf..c4314ce72 100644 --- a/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs +++ b/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs @@ -89,30 +89,21 @@ public static (Node[] regularNodes, Node[] outlierNodes) SplitNodesIntoRegularAn var truncatedAverage = TruncatedAverageCenter(nodes); var orderedNodes = nodes.OrderBy(x => Vector3.Distance(x.BoundingBox.Center, truncatedAverage)).ToArray(); + var orderedNodeDistances = orderedNodes + .Select(x => Vector3.Distance(x.BoundingBox.Center, truncatedAverage)) + .ToArray(); - bool outliersExist = false; - int outlierStartIndex = 0; - for (int i = 1; i < orderedNodes.Length; i++) + for (int i = 1; i < orderedNodeDistances.Length; i++) { - var firstDistance = Vector3.Distance(orderedNodes[i - 1].BoundingBox.Center, truncatedAverage); - var secondDistance = Vector3.Distance(orderedNodes[i].BoundingBox.Center, truncatedAverage); - if (secondDistance - firstDistance >= outlierDistance) + if (orderedNodeDistances[i] - orderedNodeDistances[i - 1] >= outlierDistance) { - outliersExist = true; - outlierStartIndex = i; - break; + var regularNodes = orderedNodes.Take(i).ToArray(); + var outlierNodes = orderedNodes.Skip(i).ToArray(); + return (regularNodes, outlierNodes); } } - if (!outliersExist) - { - return (nodes.ToArray(), Array.Empty()); - } - - var regularNodes = orderedNodes.Take(outlierStartIndex).OrderBy(x => x.TreeIndex).ToArray(); - var outlierNodes = orderedNodes.Skip(outlierStartIndex).OrderBy(x => x.TreeIndex).ToArray(); - - return (regularNodes, outlierNodes); + return (nodes.ToArray(), []); } /// @@ -128,11 +119,15 @@ private static Vector3 TruncatedAverageCenter(this IReadOnlyCollection nod return new Vector3(avgCenterX, avgCenterY, avgCenterZ); - float AvgCenter(IEnumerable values) => - values.Order().Skip((int)(nodes.Count * 0.05)).Take((int)(nodes.Count * 0.95)).Average(); + float AvgCenter(IEnumerable values) + { + var discardCount = (int)(nodes.Count * 0.05); + var keepCount = nodes.Count - 2 * discardCount; + return values.Order().Skip(discardCount).Take(keepCount).Average(); + } } - public static Node[] ConvertPrimitivesToNodes(APrimitive[] primitives) + public static Node[] ConvertPrimitivesToNodes(IEnumerable primitives) { return primitives .Select(primitive => From fc9fa10f4437a11ea7eae881e48200af43a3d87b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stig=20Rune=20S=C3=B8berg?= Date: Tue, 15 Oct 2024 12:12:24 +0200 Subject: [PATCH 21/41] Order nodes by treeindex only once --- .../SectorSplitting/PrioritySectorSplitter.cs | 11 ++++------- .../SectorSplitting/PrioritySplittingUtils.cs | 4 ++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs index b029398a4..c3dff99ad 100644 --- a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs @@ -49,16 +49,13 @@ SequentialIdGenerator sectorIdGenerator { var nodesUsed = 0; + // Sorting by Id as we believe the TreeIndex to group similar parts in the hierarchy together + var nodesOrderedByTreeIndex = nodes.OrderBy(x => x.TreeIndex).ToArray(); + while (nodesUsed < nodes.Length) { // TODO: Should this use spatially aware splitting? Should it place nodes with similar attributes together? Ex: tags? - var nodesByBudget = GetNodesByBudgetSimple( - nodes - .OrderBy(x => x.TreeIndex) // Sorting by Id as we believe the TreeIndex to group similar parts in the hierarchy together - .ToArray(), - nodesUsed - ) - .ToArray(); + var nodesByBudget = GetNodesByBudgetSimple(nodesOrderedByTreeIndex, nodesUsed).ToArray(); nodesUsed += nodesByBudget.Length; var sectorId = sectorIdGenerator.GetNextId(); diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs index 4ca7ec802..cd16cdf1a 100644 --- a/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; using System.Linq; -using CadRevealComposer.Primitives; -using CadRevealComposer.Utils; +using Primitives; +using Utils; public static class PrioritySplittingUtils { From 263bfedae52493f89027ed4ac257cf739451cc52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stig=20Rune=20S=C3=B8berg?= Date: Tue, 15 Oct 2024 14:28:54 +0200 Subject: [PATCH 22/41] Remove StidTagMapper stuff (Moving it to a its own branch) --- .../SectorSplitting/PrioritySplittingUtils.cs | 8 +- .../Operations/StidTagMapper/StidTagMapper.cs | 92 ------------------- .../StidTagMapper/TagDataFromStid.cs | 33 ------- 3 files changed, 1 insertion(+), 132 deletions(-) delete mode 100644 CadRevealComposer/Operations/StidTagMapper/StidTagMapper.cs delete mode 100644 CadRevealComposer/Operations/StidTagMapper/TagDataFromStid.cs diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs index cd16cdf1a..a95c696da 100644 --- a/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs @@ -13,13 +13,7 @@ public static class PrioritySplittingUtils public static void SetPriorityForHighlightSplittingWithMutation(IReadOnlyList nodes) { var disciplineFilteredNodes = FilterByDisciplineAndAddDisciplineMetadata(nodes).ToArray(); - - // TODO - // Are we going to use the custom STID mapper, or should we wait for a more official solution? - // Notes: Uncommenting code below requires that a relevant file exists on build server - var tagMappingAndDisciplineFilteredNodes = disciplineFilteredNodes; // StidTagMapper.FilterNodesWithTag(disciplineFilteredNodes); - - var tagAndDisciplineFilteredNodes = FilterByIfTagExists(tagMappingAndDisciplineFilteredNodes).ToArray(); + var tagAndDisciplineFilteredNodes = FilterByIfTagExists(disciplineFilteredNodes).ToArray(); SetPriorityOnNodesAndChildren(tagAndDisciplineFilteredNodes); } diff --git a/CadRevealComposer/Operations/StidTagMapper/StidTagMapper.cs b/CadRevealComposer/Operations/StidTagMapper/StidTagMapper.cs deleted file mode 100644 index 0fdf6bf90..000000000 --- a/CadRevealComposer/Operations/StidTagMapper/StidTagMapper.cs +++ /dev/null @@ -1,92 +0,0 @@ -namespace CadRevealComposer.Operations; - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Newtonsoft.Json; - -public static class StidTagMapper -{ - public static CadRevealNode[] FilterNodesWithTag(CadRevealNode[] nodes) - { - // string filename = "troa_tags.json"; - string path = @"\\ws1611\AppResources\TrollA\Tags_temp2024-08-08\troa_tags.json"; - - var tagDataFromStid = ParseFromJson(path); - return FilterNodesByStidTags(nodes, tagDataFromStid); - } - - private static TagDataFromStid[] ParseFromJson(string path) - { - var json = File.ReadAllText(path); - - return JsonConvert.DeserializeObject(json)!; - } - - private static CadRevealNode[] FilterNodesByStidTags( - IReadOnlyList revealNodes, - TagDataFromStid[] tagDataFromStid - ) - { - var acceptedNodes = new List(); - - var tagLookup = tagDataFromStid.ToDictionary(x => x.TagNo.Trim(), x => x, StringComparer.OrdinalIgnoreCase); - - var lineTags = tagDataFromStid.Where(x => x.TagCategory == 6).ToArray(); - - foreach (CadRevealNode revealNode in revealNodes) - { - if (revealNode.Attributes.TryGetValue("Tag", out var pdmsTag)) - { - if (tagLookup.TryGetValue(pdmsTag, out var stidTag)) - { - acceptedNodes.Add(revealNode); - continue; - } - } - - if (tagLookup.TryGetValue(revealNode.Name.Trim(['/']).Trim(), out var value)) - { - acceptedNodes.Add(revealNode); - continue; - } - - if (revealNode.Attributes.TryGetValue("Type", out var type) && type == "PIPE") - { - var baseLineTag = revealNode.Name.Split("_")[0].TrimStart('/').Trim(); - var matchingLineTags = lineTags - .Where(x => x.TagNo.Contains(baseLineTag, StringComparison.OrdinalIgnoreCase)) - .ToArray(); - - if (pdmsTag != null) - { - var pdmsTagWithoutStars = pdmsTag!.Trim('*'); - var pdmsTagMatchingLineTags = lineTags - .Where(x => x.TagNo.Contains(pdmsTagWithoutStars, StringComparison.OrdinalIgnoreCase)) - .ToArray(); - if (pdmsTagMatchingLineTags.Any()) - { - if (pdmsTagMatchingLineTags.Length == 1) - { - acceptedNodes.Add(revealNode); - continue; - } - else - { - acceptedNodes.Add(revealNode); - continue; - } - } - } - if (matchingLineTags.Any()) - { - acceptedNodes.Add(revealNode); - continue; - } - } - } - - return acceptedNodes.ToArray(); - } -} diff --git a/CadRevealComposer/Operations/StidTagMapper/TagDataFromStid.cs b/CadRevealComposer/Operations/StidTagMapper/TagDataFromStid.cs deleted file mode 100644 index 084555135..000000000 --- a/CadRevealComposer/Operations/StidTagMapper/TagDataFromStid.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace CadRevealComposer.Operations; - -using Newtonsoft.Json; - -[JsonObject] -public class TagDataFromStid -{ - public required string TagNo { get; set; } - public required string Description { get; set; } - public required string TagStatus { get; set; } - public required int TagCategory { get; set; } - public required string TagCategoryDescription { get; set; } - public required string? TagType { get; set; } - public required string UpdatedDate { get; set; } - public required string LocationCode { get; set; } - public required string DisciplineCode { get; set; } - public required string ContrCode { get; set; } - public required string System { get; set; } - public required string ProjectCode { get; set; } - public required string PoNo { get; set; } - public required string PlantNo { get; set; } - public float? XCoordinate { get; set; } - public float? YCoordinate { get; set; } - public float? ZCoordinate { get; set; } - public required AdditionalFields[] AdditionalFields { get; set; } -} - -[JsonObject] -public class AdditionalFields -{ - public required string Type { get; set; } - public required string Value { get; set; } -} From bb4adcde5824f05ab85676569bf8776aada68ef4 Mon Sep 17 00:00:00 2001 From: Nils Henrik Hals <3185998+Strepto@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:50:06 +0200 Subject: [PATCH 23/41] Simplify SequentialIdGenerator --- .../IdProviders/SequentialIdGenerator.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/CadRevealComposer/IdProviders/SequentialIdGenerator.cs b/CadRevealComposer/IdProviders/SequentialIdGenerator.cs index 227f98cb0..49ee15060 100644 --- a/CadRevealComposer/IdProviders/SequentialIdGenerator.cs +++ b/CadRevealComposer/IdProviders/SequentialIdGenerator.cs @@ -5,17 +5,21 @@ public class SequentialIdGenerator(uint firstIdReturned = 0) { - public static readonly uint MaxSafeInteger = (uint)Math.Pow(2, 24) - 1; // Max sequential whole integer in a 32-bit float as used in reveal shaders. + private static readonly uint MaxSafeIdForFloats = (uint)Math.Pow(2, 24) - 1; // Max sequential whole integer in a 32-bit float as used in reveal shaders. - private long _internalIdCounter = ((long)firstIdReturned) - 1; // It increments before selecting the id, hence -1 + private uint _internalIdCounter = firstIdReturned; // It increments before selecting the id, hence -1 public uint GetNextId() { - var candidate = Interlocked.Increment(ref _internalIdCounter); - if (candidate > MaxSafeInteger) + var idToReturn = _internalIdCounter; + _internalIdCounter++; + if (idToReturn > MaxSafeIdForFloats) throw new Exception("Too many ids generated"); - return (uint)candidate; + return idToReturn; } - public uint CurrentMaxGeneratedIndex => (uint)Interlocked.Read(ref _internalIdCounter); + /// + /// Look at the next id that will be generated + /// + public uint PeekNextId => _internalIdCounter; } From b9017f34ea5565f688ccd0f20ccb90ff2b663bd1 Mon Sep 17 00:00:00 2001 From: Nils Henrik Hals <3185998+Strepto@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:50:21 +0200 Subject: [PATCH 24/41] Refactoring --- .../SectorSplitting/PrioritySectorSplitter.cs | 36 +++++++++---------- .../SectorSplitting/SplittingUtils.cs | 3 +- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs index c3dff99ad..7a5959856 100644 --- a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs @@ -15,11 +15,15 @@ public IEnumerable SplitIntoSectors( SequentialIdGenerator sectorIdGenerator ) { - var rootSector = SplittingUtils.CreateRootSector(0, "/0", new BoundingBox(Vector3.Zero, Vector3.One)); - yield return rootSector; + if (sectorIdGenerator.PeekNextId == 0) + _ = sectorIdGenerator.GetNextId(); // Get and discard id 0, as 0 is hardcoded below + var dummyRootSector = SplittingUtils.CreateRootSector(0, "/0", new BoundingBox(Vector3.Zero, Vector3.One)); + yield return dummyRootSector; var primitivesGroupedByDiscipline = allGeometries.GroupBy(x => x.Discipline); var sectors = new List(); + // We split the geometries into sectors based on the discipline because there are very few highlight cases that are cross-discipline + // This helps reduce the overhead of a highlight sector, since highlighting of multiple tags within the same discipline is more common foreach (var disciplineGroup in primitivesGroupedByDiscipline) { var geometryGroups = disciplineGroup.GroupBy(primitive => primitive.TreeIndex); // Group by treeindex to avoid having one treeindex unnecessary many sectors @@ -29,7 +33,7 @@ SequentialIdGenerator sectorIdGenerator // TODO: Decide if this is the right thing to do (Node[] regularNodes, _) = nodes.SplitNodesIntoRegularAndOutlierNodes(); - sectors.AddRange(SplitIntoTreeIndexSectors(regularNodes, rootSector, sectorIdGenerator)); + sectors.AddRange(SplitIntoTreeIndexSectors(regularNodes, dummyRootSector, sectorIdGenerator)); } foreach (var sector in sectors) @@ -47,43 +51,37 @@ private static IEnumerable SplitIntoTreeIndexSectors( SequentialIdGenerator sectorIdGenerator ) { - var nodesUsed = 0; + var nodesProcessed = 0; // Sorting by Id as we believe the TreeIndex to group similar parts in the hierarchy together var nodesOrderedByTreeIndex = nodes.OrderBy(x => x.TreeIndex).ToArray(); - while (nodesUsed < nodes.Length) + while (nodesProcessed < nodes.Length) { // TODO: Should this use spatially aware splitting? Should it place nodes with similar attributes together? Ex: tags? - var nodesByBudget = GetNodesByBudgetSimple(nodesOrderedByTreeIndex, nodesUsed).ToArray(); - nodesUsed += nodesByBudget.Length; + var nodesByBudget = GetNodesByBudgetSimple(nodesOrderedByTreeIndex, nodesProcessed).ToArray(); + nodesProcessed += nodesByBudget.Length; var sectorId = sectorIdGenerator.GetNextId(); var subtreeBoundingBox = nodesByBudget.CalculateBoundingBox(); - yield return SplittingUtils.CreateSector( - nodesByBudget, - sectorId, - rootSector, - rootSector.Depth + 1, - subtreeBoundingBox - ); + yield return SplittingUtils.CreateSector(nodesByBudget, sectorId, rootSector, subtreeBoundingBox); } } - private static IEnumerable GetNodesByBudgetSimple(IReadOnlyList nodes, int indexToStart) + private static IEnumerable GetNodesByBudgetSimple(Node[] nodes, int indexToStart) { - var byteSizeBudget = SectorEstimatedByteSizeBudget; + var remainingBudget = SectorEstimatedByteSizeBudget; - for (int i = indexToStart; i < nodes.Count; i++) + for (int i = indexToStart; i < nodes.Length; i++) { - if (byteSizeBudget < 0) + if (remainingBudget < 0) { yield break; } var node = nodes[i]; - byteSizeBudget -= node.EstimatedByteSize; + remainingBudget -= node.EstimatedByteSize; yield return node; } diff --git a/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs b/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs index c4314ce72..6860b2c95 100644 --- a/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs +++ b/CadRevealComposer/Operations/SectorSplitting/SplittingUtils.cs @@ -215,7 +215,6 @@ public static InternalSector CreateSector( Node[] nodes, uint sectorId, InternalSector parent, - int depth, BoundingBox subtreeBoundingBox ) { @@ -229,7 +228,7 @@ BoundingBox subtreeBoundingBox return new InternalSector( sectorId, parent.SectorId, - depth, + parent.Depth + 1, path, minDiagonal, maxDiagonal, From 8dfd64b7c2ca1db8d22b9903b378b1f5fd743583 Mon Sep 17 00:00:00 2001 From: Nils Henrik Hals <3185998+Strepto@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:53:23 +0200 Subject: [PATCH 25/41] Update tests --- ...entialIdProviderTests.cs => SequentialIdGeneratorTests.cs} | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) rename CadRevealComposer.Tests/{SequentialIdProviderTests.cs => SequentialIdGeneratorTests.cs} (73%) diff --git a/CadRevealComposer.Tests/SequentialIdProviderTests.cs b/CadRevealComposer.Tests/SequentialIdGeneratorTests.cs similarity index 73% rename from CadRevealComposer.Tests/SequentialIdProviderTests.cs rename to CadRevealComposer.Tests/SequentialIdGeneratorTests.cs index 8bff7675a..da4eaa228 100644 --- a/CadRevealComposer.Tests/SequentialIdProviderTests.cs +++ b/CadRevealComposer.Tests/SequentialIdGeneratorTests.cs @@ -3,7 +3,7 @@ using IdProviders; [TestFixture] -public class NodeIdProviderTests +public class SequentialIdGeneratorTests { [TestCase(0u)] [TestCase(10u)] @@ -13,7 +13,9 @@ public void GetNodeId_ForFirstId_ReturnsSameAsStartingId(uint firstIdReturned) var sequentialIdGenerator = new SequentialIdGenerator(firstIdReturned); uint nextId = sequentialIdGenerator.GetNextId(); Assert.That(nextId, Is.EqualTo(firstIdReturned)); + var expectedNextIdFromPeekNextBeforeGetNextId = sequentialIdGenerator.PeekNextId; var nextId2 = sequentialIdGenerator.GetNextId(); Assert.That(nextId2, Is.EqualTo(firstIdReturned + 1)); + Assert.That(nextId2, Is.EqualTo(expectedNextIdFromPeekNextBeforeGetNextId)); } } From a15d306e37c2bed9f079bf63500737ff9d198832 Mon Sep 17 00:00:00 2001 From: Nils Henrik Hals <3185998+Strepto@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:56:57 +0200 Subject: [PATCH 26/41] More test --- CadRevealComposer.Tests/SequentialIdGeneratorTests.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CadRevealComposer.Tests/SequentialIdGeneratorTests.cs b/CadRevealComposer.Tests/SequentialIdGeneratorTests.cs index da4eaa228..4daf0d69d 100644 --- a/CadRevealComposer.Tests/SequentialIdGeneratorTests.cs +++ b/CadRevealComposer.Tests/SequentialIdGeneratorTests.cs @@ -18,4 +18,15 @@ public void GetNodeId_ForFirstId_ReturnsSameAsStartingId(uint firstIdReturned) Assert.That(nextId2, Is.EqualTo(firstIdReturned + 1)); Assert.That(nextId2, Is.EqualTo(expectedNextIdFromPeekNextBeforeGetNextId)); } + + [Test] + public void GetNextId_WhenIdIsAboveFloatMaxInt_ThrowsException() + { + // This test tests N random values. It should never fail. + var sequentialIdGenerator = new SequentialIdGenerator((uint)Math.Pow(2, 24)); + Assert.That( + () => sequentialIdGenerator.GetNextId(), + Throws.Exception.With.Message.EqualTo("Too many ids generated") + ); + } } From 155895ef7c34522c767aea1a2180d38f8761e963 Mon Sep 17 00:00:00 2001 From: Nils Henrik Hals <3185998+Strepto@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:14:08 +0200 Subject: [PATCH 27/41] Expand to max id 2^24 for real --- CadRevealComposer.Tests/SequentialIdGeneratorTests.cs | 1 + CadRevealComposer/IdProviders/SequentialIdGenerator.cs | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CadRevealComposer.Tests/SequentialIdGeneratorTests.cs b/CadRevealComposer.Tests/SequentialIdGeneratorTests.cs index 4daf0d69d..9c2c14f74 100644 --- a/CadRevealComposer.Tests/SequentialIdGeneratorTests.cs +++ b/CadRevealComposer.Tests/SequentialIdGeneratorTests.cs @@ -24,6 +24,7 @@ public void GetNextId_WhenIdIsAboveFloatMaxInt_ThrowsException() { // This test tests N random values. It should never fail. var sequentialIdGenerator = new SequentialIdGenerator((uint)Math.Pow(2, 24)); + _ = sequentialIdGenerator.GetNextId(); // Should not throw yet. Assert.That( () => sequentialIdGenerator.GetNextId(), Throws.Exception.With.Message.EqualTo("Too many ids generated") diff --git a/CadRevealComposer/IdProviders/SequentialIdGenerator.cs b/CadRevealComposer/IdProviders/SequentialIdGenerator.cs index 49ee15060..8fe458b15 100644 --- a/CadRevealComposer/IdProviders/SequentialIdGenerator.cs +++ b/CadRevealComposer/IdProviders/SequentialIdGenerator.cs @@ -1,11 +1,10 @@ namespace CadRevealComposer.IdProviders; using System; -using System.Threading; public class SequentialIdGenerator(uint firstIdReturned = 0) { - private static readonly uint MaxSafeIdForFloats = (uint)Math.Pow(2, 24) - 1; // Max sequential whole integer in a 32-bit float as used in reveal shaders. + private static readonly uint MaxSafeIdForReveal = (uint)Math.Pow(2, 24); // Max number of cells in a 4k texture (in reveal) private uint _internalIdCounter = firstIdReturned; // It increments before selecting the id, hence -1 @@ -13,7 +12,7 @@ public uint GetNextId() { var idToReturn = _internalIdCounter; _internalIdCounter++; - if (idToReturn > MaxSafeIdForFloats) + if (idToReturn > MaxSafeIdForReveal) throw new Exception("Too many ids generated"); return idToReturn; } From 57bb38e1a519aba31200f0cff4c60afcdb3ac561 Mon Sep 17 00:00:00 2001 From: Nils Henrik Hals <3185998+Strepto@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:15:24 +0200 Subject: [PATCH 28/41] Simplify TreeIndexGenerator --- CadRevealComposer/IdProviders/TreeIndexGenerator.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/CadRevealComposer/IdProviders/TreeIndexGenerator.cs b/CadRevealComposer/IdProviders/TreeIndexGenerator.cs index 10bbec059..2b1252ccb 100644 --- a/CadRevealComposer/IdProviders/TreeIndexGenerator.cs +++ b/CadRevealComposer/IdProviders/TreeIndexGenerator.cs @@ -15,11 +15,4 @@ namespace CadRevealComposer.IdProviders; /// 7 /// 8 /// -public class TreeIndexGenerator : SequentialIdGenerator -{ - public TreeIndexGenerator() - { - // "Pre-increment" the first id (which is zero) to avoid that being used anywhere else. - _ = GetNextId(); // This returns zero, but we discard it. - } -} +public class TreeIndexGenerator() : SequentialIdGenerator(firstIdReturned: 1); From ae9d9b0f9acc1793420b09620f97a77a16e5b0bb Mon Sep 17 00:00:00 2001 From: Nils Henrik Hals <3185998+Strepto@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:24:32 +0200 Subject: [PATCH 29/41] Add more comments --- .../SectorSplitting/PrioritySectorSplitter.cs | 4 ++-- CadRevealComposer/SceneCreator.cs | 10 +++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs index 7a5959856..3335383c8 100644 --- a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs @@ -18,12 +18,12 @@ SequentialIdGenerator sectorIdGenerator if (sectorIdGenerator.PeekNextId == 0) _ = sectorIdGenerator.GetNextId(); // Get and discard id 0, as 0 is hardcoded below var dummyRootSector = SplittingUtils.CreateRootSector(0, "/0", new BoundingBox(Vector3.Zero, Vector3.One)); - yield return dummyRootSector; - var primitivesGroupedByDiscipline = allGeometries.GroupBy(x => x.Discipline); + yield return dummyRootSector; // Using dummy root sector because we need to remap it to the non-priority sector root later on var sectors = new List(); // We split the geometries into sectors based on the discipline because there are very few highlight cases that are cross-discipline // This helps reduce the overhead of a highlight sector, since highlighting of multiple tags within the same discipline is more common + var primitivesGroupedByDiscipline = allGeometries.GroupBy(x => x.Discipline); foreach (var disciplineGroup in primitivesGroupedByDiscipline) { var geometryGroups = disciplineGroup.GroupBy(primitive => primitive.TreeIndex); // Group by treeindex to avoid having one treeindex unnecessary many sectors diff --git a/CadRevealComposer/SceneCreator.cs b/CadRevealComposer/SceneCreator.cs index 723566e45..5651f328e 100644 --- a/CadRevealComposer/SceneCreator.cs +++ b/CadRevealComposer/SceneCreator.cs @@ -165,13 +165,9 @@ private static SectorInfo SerializeSector(InternalSector p, string outputDirecto { var (estimatedTriangleCount, estimatedDrawCalls) = DrawCallEstimator.Estimate(p.Geometries); - string? sectorFilename = !p.IsHighlightSector - ? p.Geometries.Any() - ? $"sector_{p.SectorId}.glb" - : null - : p.Geometries.Any() - ? $"highlight_sector_{p.SectorId}.glb" // TODO Rename to priority sector - : null; + string? sectorFilename = !p.Geometries.Any() + ? null + : (!p.IsHighlightSector ? $"sector_{p.SectorId}.glb" : $"highlight_sector_{p.SectorId}.glb"); // The highlight_sector name is used in the frontend to identify the highlight sectors, don't rename without syncing with the frontend. var sectorInfo = new SectorInfo( SectorId: p.SectorId, From 15959c1f1ce8a2921c9f173861a88742a4f95c18 Mon Sep 17 00:00:00 2001 From: Nils Henrik Hals <3185998+Strepto@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:28:47 +0200 Subject: [PATCH 30/41] Rename dict and move logigng --- CadRevealComposer/CadRevealComposerRunner.cs | 8 +++----- CadRevealComposer/SceneCreator.cs | 2 ++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CadRevealComposer/CadRevealComposerRunner.cs b/CadRevealComposer/CadRevealComposerRunner.cs index 1783ec185..5a3fa2bb0 100644 --- a/CadRevealComposer/CadRevealComposerRunner.cs +++ b/CadRevealComposer/CadRevealComposerRunner.cs @@ -142,15 +142,13 @@ IReadOnlyList modelFormatProviders // TODO Is this the best place to add sector id mapping to the database? // TODO Cont: Today we start hierarchy export before we have the sector id mapping ready. Is it ok to mutate the database after it has been exported? - var prioritizedSectorInsertionStopwatch = Stopwatch.StartNew(); - SceneCreator.AddPrioritizedSectorsToDatabase(splitExportResults.TreeIndexToSectorId, outputDirectory); - Console.WriteLine($"Inserted prioritized sectors in db in {prioritizedSectorInsertionStopwatch.Elapsed}"); + SceneCreator.AddPrioritizedSectorsToDatabase(splitExportResults.TreeIndexToSectorIdDict, outputDirectory); Console.WriteLine($"Export Finished. Wrote output files to \"{Path.GetFullPath(outputDirectory.FullName)}\""); Console.WriteLine($"Convert completed in {totalTimeElapsed.Elapsed}"); } - public record SplitAndExportResults(List TreeIndexToSectorId); + public record SplitAndExportResults(List TreeIndexToSectorIdDict); public record TreeIndexSectorIdPair(uint TreeIndex, uint SectorId); @@ -190,7 +188,7 @@ ComposerParameters composerParameters ); Console.WriteLine($"Wrote scene file in {stopwatch.Elapsed}"); - return new SplitAndExportResults(TreeIndexToSectorId: GetTreeIndexToSectorIdDict(prioritizedSectors)); + return new SplitAndExportResults(TreeIndexToSectorIdDict: GetTreeIndexToSectorIdDict(prioritizedSectors)); } /// diff --git a/CadRevealComposer/SceneCreator.cs b/CadRevealComposer/SceneCreator.cs index 5651f328e..000f66ca4 100644 --- a/CadRevealComposer/SceneCreator.cs +++ b/CadRevealComposer/SceneCreator.cs @@ -58,10 +58,12 @@ public static void AddPrioritizedSectorsToDatabase( DirectoryInfo outputDirectory ) { + var prioritizedSectorInsertionStopwatch = Stopwatch.StartNew(); DatabaseComposer.AddTreeIndexToSectorToDatabase( treeIndexToPrioritizedSector.Select(x => (x.TreeIndex, x.SectorId)).ToList(), outputDirectory ); + Console.WriteLine($"Inserted prioritized sectors in db in {prioritizedSectorInsertionStopwatch.Elapsed}"); } public static void CreateSceneFile( From 4e6e6969957754387cabad8c2ad32996b8a1eae3 Mon Sep 17 00:00:00 2001 From: Nils Henrik Hals <3185998+Strepto@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:30:40 +0200 Subject: [PATCH 31/41] Update casing --- HierarchyComposer/Functions/DatabaseComposer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HierarchyComposer/Functions/DatabaseComposer.cs b/HierarchyComposer/Functions/DatabaseComposer.cs index 98979c043..942306aff 100644 --- a/HierarchyComposer/Functions/DatabaseComposer.cs +++ b/HierarchyComposer/Functions/DatabaseComposer.cs @@ -266,12 +266,12 @@ DirectoryInfo outputDirectory var createTableCommand = connection.CreateCommand(); createTableCommand.CommandText = - "CREATE TABLE PrioritizedSectors (treeindex INTEGER NOT NULL, prioritizedSectorId INTEGER NOT NULL, PRIMARY KEY (treeindex, prioritizedSectorId)) WITHOUT ROWID; "; + "CREATE TABLE PrioritizedSectors (TreeIndex INTEGER NOT NULL, PrioritizedSectorId INTEGER NOT NULL, PRIMARY KEY (TreeIndex, PrioritizedSectorId)) WITHOUT ROWID; "; createTableCommand.ExecuteNonQuery(); var command = connection.CreateCommand(); command.CommandText = - "INSERT INTO PrioritizedSectors (treeindex, prioritizedSectorId) VALUES ($TreeIndex, $PrioritizedSectorId)"; + "INSERT INTO PrioritizedSectors (TreeIndex, PrioritizedSectorId) VALUES ($TreeIndex, $PrioritizedSectorId)"; var treeIndexParameter = command.CreateParameter(); treeIndexParameter.ParameterName = "$TreeIndex"; From 5d1e3d948b5e12f3975b159d8d7cf503efff66d8 Mon Sep 17 00:00:00 2001 From: Nils Henrik Hals <3185998+Strepto@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:30:47 +0200 Subject: [PATCH 32/41] Update comment --- CadRevealComposer/CadRevealComposerRunner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CadRevealComposer/CadRevealComposerRunner.cs b/CadRevealComposer/CadRevealComposerRunner.cs index 5a3fa2bb0..82cfd94ba 100644 --- a/CadRevealComposer/CadRevealComposerRunner.cs +++ b/CadRevealComposer/CadRevealComposerRunner.cs @@ -141,7 +141,7 @@ IReadOnlyList modelFormatProviders WriteParametersToParamsFile(modelParameters, composerParameters, outputDirectory); // TODO Is this the best place to add sector id mapping to the database? - // TODO Cont: Today we start hierarchy export before we have the sector id mapping ready. Is it ok to mutate the database after it has been exported? + // TODO Cont: Is it ok to mutate the database after it has been exported? Yes? SceneCreator.AddPrioritizedSectorsToDatabase(splitExportResults.TreeIndexToSectorIdDict, outputDirectory); Console.WriteLine($"Export Finished. Wrote output files to \"{Path.GetFullPath(outputDirectory.FullName)}\""); From 18840d264e39e442630a63809937c6d0c882d42b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stig=20Rune=20S=C3=B8berg?= Date: Thu, 24 Oct 2024 10:22:16 +0200 Subject: [PATCH 33/41] Adding tests for PrioritySectorSplitter --- .../Splitting/PrioritySectorSplitterTests.cs | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 CadRevealComposer.Tests/Operations/Splitting/PrioritySectorSplitterTests.cs diff --git a/CadRevealComposer.Tests/Operations/Splitting/PrioritySectorSplitterTests.cs b/CadRevealComposer.Tests/Operations/Splitting/PrioritySectorSplitterTests.cs new file mode 100644 index 000000000..211a37888 --- /dev/null +++ b/CadRevealComposer.Tests/Operations/Splitting/PrioritySectorSplitterTests.cs @@ -0,0 +1,84 @@ +namespace CadRevealComposer.Tests.Operations.Splitting; + +using System.Drawing; +using System.Numerics; +using CadRevealComposer.Operations.SectorSplitting; +using IdProviders; +using Primitives; + +[TestFixture] +public class PrioritySectorSplitterTests +{ + const string DisciplinePipe = "PIPE"; + const string DisciplineCable = "CABLE"; + + [Test] + public void OnePipeAndOneCablePrimitive_SplitIntoSectors_ShouldResultInRootSectorAndOneSectorForEachDiscipline() + { + APrimitive[] primitives = [CreatePrimitive(0f, 0, DisciplinePipe), CreatePrimitive(2f, 1, DisciplineCable)]; + + var splitter = new PrioritySectorSplitter(); + var sectors = splitter.SplitIntoSectors(primitives, new SequentialIdGenerator()).ToArray(); + + Assert.That(sectors, Has.Length.EqualTo(3)); + } + + [Test] + public void FivePrimitivesWhereTwoAreFarAway_SplitIntoSectors_ShouldResultInRootSectorAndOneSectorWithThree() + { + var primitives = PositionsToPrimitives([-40f, -10f, 0f, 10f, 40f]); + + var splitter = new PrioritySectorSplitter(); + var sectors = splitter.SplitIntoSectors(primitives, new SequentialIdGenerator()).ToArray(); + + Assert.That(sectors, Has.Length.EqualTo(2)); + Assert.That(sectors[1].Geometries, Has.Length.EqualTo(3)); + } + + [Test] + public void ManyPrimitivesToExceedByteSizeBudget_SplitIntoSectors_ShouldResultMoreThanOneNonRootSectors() + { + const int count = 1000; + var positions = Enumerable + .Range(0, count + 1) + .Select(i => + { + var factor = i / (double)count; + var value = (-1 + 2 * factor) * 10; + + return (float)(Math.Sign(value) * Math.Pow(value, 2)); + }); + + var primitives = PositionsToPrimitives(positions); + + var splitter = new PrioritySectorSplitter(); + var sectors = splitter.SplitIntoSectors(primitives, new SequentialIdGenerator()).ToArray(); + + Assert.Multiple(() => + { + Assert.That(sectors, Has.Length.EqualTo(3)); + Assert.That(sectors[1].Geometries, Has.Length.EqualTo(569)); + Assert.That(sectors[2].Geometries, Has.Length.EqualTo(432)); + }); + } + + private static APrimitive[] PositionsToPrimitives(IEnumerable positions) => + positions + .Select((position, index) => CreatePrimitive(position, (uint)index, DisciplinePipe)) + .ToArray(); + + private static Box CreatePrimitive(float xPosition, uint treeIndex, string discipline) + { + var position = new Vector3(xPosition, 0, 0); + + return new Box( + Matrix4x4.CreateTranslation(xPosition + 0.5f, 0, 0), + treeIndex, + Color.Black, + new BoundingBox(position, position + Vector3.One) + ) + { + Discipline = discipline + }; + } +} From 6127303a3a3311584922828938d59ffd36718f2d Mon Sep 17 00:00:00 2001 From: vegasten Date: Thu, 24 Oct 2024 11:57:47 +0200 Subject: [PATCH 34/41] Add priority and discipline to protobuf --- CadRevealComposer/Primitives/APrimitive.cs | 37 ++++++++++------------ CadRevealRvmProvider/RvmProtoMesh.cs | 2 +- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/CadRevealComposer/Primitives/APrimitive.cs b/CadRevealComposer/Primitives/APrimitive.cs index a2f7fd120..e2d03d5f7 100644 --- a/CadRevealComposer/Primitives/APrimitive.cs +++ b/CadRevealComposer/Primitives/APrimitive.cs @@ -8,7 +8,7 @@ namespace CadRevealComposer.Primitives; // Reveal GLTF model [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] public sealed record Box(Matrix4x4 InstanceMatrix, uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox) - : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); + : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox, 0, null); [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] public sealed record Circle( @@ -17,7 +17,7 @@ public sealed record Circle( uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox -) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); +) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox, 0, null); [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] public sealed record Cone( @@ -31,7 +31,7 @@ public sealed record Cone( uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox -) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); +) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox, 0, null); [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] public sealed record EccentricCone( @@ -43,7 +43,7 @@ public sealed record EccentricCone( uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox -) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); +) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox, 0, null); [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] public sealed record EllipsoidSegment( @@ -55,7 +55,7 @@ public sealed record EllipsoidSegment( uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox -) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); +) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox, 0, null); [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] public sealed record GeneralCylinder( @@ -70,7 +70,7 @@ public sealed record GeneralCylinder( uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox -) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); +) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox, 0, null); [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] public sealed record GeneralRing( @@ -82,15 +82,15 @@ public sealed record GeneralRing( uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox -) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); +) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox, 0, null); [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] public sealed record Nut(Matrix4x4 InstanceMatrix, uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox) - : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); + : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox, 0, null); [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] public sealed record Quad(Matrix4x4 InstanceMatrix, uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox) - : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); + : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox, 0, null); [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] public sealed record TorusSegment( @@ -101,7 +101,7 @@ public sealed record TorusSegment( uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox -) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); +) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox, 0, null); [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] public sealed record Trapezium( @@ -112,7 +112,7 @@ public sealed record Trapezium( uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox -) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); +) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox, 0, null); /// /// Defines an "Instance" of a Template. A instance that shares a Geometry representation with other instances of a shared "Template" reference. @@ -133,11 +133,11 @@ public sealed record InstancedMesh( uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox -) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); +) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox, 0, null); [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, SkipConstructor = true)] public sealed record TriangleMesh(Mesh Mesh, uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox) - : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); + : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox, 0, null); [ProtoContract(SkipConstructor = true, IgnoreUnknownSubTypes = false)] [ProtoInclude(500, typeof(TriangleMesh))] @@ -156,10 +156,7 @@ public sealed record TriangleMesh(Mesh Mesh, uint TreeIndex, Color Color, Boundi public abstract record APrimitive( [property: ProtoMember(1)] uint TreeIndex, [property: ProtoMember(2)] Color Color, - [property: ProtoMember(3)] BoundingBox AxisAlignedBoundingBox -) -{ - // TODO: Should these be a part of protobuf? - public int Priority { get; init; } = 0; - public string? Discipline { get; init; } = null; -} + [property: ProtoMember(3)] BoundingBox AxisAlignedBoundingBox, + [property: ProtoMember(4)] int Priority, + [property: ProtoMember(5)] string? Discipline +); diff --git a/CadRevealRvmProvider/RvmProtoMesh.cs b/CadRevealRvmProvider/RvmProtoMesh.cs index d2c5cb517..4354326dd 100644 --- a/CadRevealRvmProvider/RvmProtoMesh.cs +++ b/CadRevealRvmProvider/RvmProtoMesh.cs @@ -12,7 +12,7 @@ public abstract record ProtoMesh( uint TreeIndex, Color Color, BoundingBox AxisAlignedBoundingBox -) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox); +) : APrimitive(TreeIndex, Color, AxisAlignedBoundingBox, 0, null); public sealed record ProtoMeshFromFacetGroup( RvmFacetGroup FacetGroup, From dca742b4bf07f94b3117edf7bff2d1096ada0c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stig=20Rune=20S=C3=B8berg?= Date: Fri, 25 Oct 2024 10:05:47 +0200 Subject: [PATCH 35/41] Add tests for PrioritySplittingUtils --- .../Splitting/PrioritySplittingUtilsTests.cs | 104 ++++++++++++++++++ .../SectorSplitting/PrioritySplittingUtils.cs | 14 +-- 2 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 CadRevealComposer.Tests/Operations/Splitting/PrioritySplittingUtilsTests.cs diff --git a/CadRevealComposer.Tests/Operations/Splitting/PrioritySplittingUtilsTests.cs b/CadRevealComposer.Tests/Operations/Splitting/PrioritySplittingUtilsTests.cs new file mode 100644 index 000000000..82bb0f3b6 --- /dev/null +++ b/CadRevealComposer.Tests/Operations/Splitting/PrioritySplittingUtilsTests.cs @@ -0,0 +1,104 @@ +#nullable enable +namespace CadRevealComposer.Tests.Operations.Splitting; + +using System.Drawing; +using System.Numerics; +using CadRevealComposer.Operations.SectorSplitting; +using Primitives; + +[TestFixture] +public class PrioritySplittingUtilsTests +{ + const string DisciplinePipe = "PIPE"; + const string DisciplineCable = "CABLE"; + + [Test] + public void CadRevealNodesWithVariousConfigurations_SetPriorityForHighlightSplittingWithMutation_VerifyCorrectlyMutated() + { + const uint treeIndexDisciplinePipeWithTag = 0; + const uint treeIndexDisciplinePipeMissingTag = 1; + const uint treeIndexDisciplineCableWithTag = 2; + const uint treeIndexDisciplineCableMissingTag = 3; + + CadRevealNode[] nodes = + [ + CreateCadRevealNode(treeIndexDisciplinePipeWithTag, DisciplinePipe, true), + CreateCadRevealNode(treeIndexDisciplinePipeMissingTag, DisciplinePipe, false), + CreateCadRevealNode(treeIndexDisciplineCableWithTag, DisciplineCable, true), + CreateCadRevealNode(treeIndexDisciplineCableMissingTag, DisciplineCable, false), + ]; + + PrioritySplittingUtils.SetPriorityForHighlightSplittingWithMutation(nodes); + + AssertDisciplineAndPriority(nodes[treeIndexDisciplinePipeWithTag].Geometries[0], DisciplinePipe, 1); + AssertDisciplineAndPriority(nodes[treeIndexDisciplinePipeMissingTag].Geometries[0], DisciplinePipe, 0); + AssertDisciplineAndPriority(nodes[treeIndexDisciplineCableWithTag].Geometries[0], null, 0); + AssertDisciplineAndPriority(nodes[treeIndexDisciplineCableMissingTag].Geometries[0], null, 0); + return; + + void AssertDisciplineAndPriority(APrimitive primitive, string? expectedDiscipline, int expectedPriority) + { + Assert.Multiple(() => + { + Assert.That(primitive.Discipline, Is.EqualTo(expectedDiscipline)); + Assert.That(primitive.Priority, Is.EqualTo(expectedPriority)); + }); + } + } + + [Test] + public void ThreePrimitiveGroupsWithDifferentCombinationsOfSizes_ConvertPrimitiveGroupsToNodes_ShouldReturnNodesWithCorrectNumberOfPrimitives() + { + const float smallPrimitiveSize = 0.01f; + const float largePrimitiveSize = 1.0f; + const uint treeIndexOneLargeOneSmallPrimitive = 0; + const uint treeIndexTwoSmallPrimitives = 1; + const uint treeIndexTwoLargePrimitives = 2; + + APrimitive[] primitives = + [ + CreatePrimitive(smallPrimitiveSize, treeIndexOneLargeOneSmallPrimitive, DisciplinePipe), + CreatePrimitive(largePrimitiveSize, treeIndexOneLargeOneSmallPrimitive, DisciplinePipe), + CreatePrimitive(smallPrimitiveSize, treeIndexTwoSmallPrimitives, DisciplinePipe), + CreatePrimitive(smallPrimitiveSize, treeIndexTwoSmallPrimitives, DisciplinePipe), + CreatePrimitive(largePrimitiveSize, treeIndexTwoLargePrimitives, DisciplinePipe), + CreatePrimitive(largePrimitiveSize, treeIndexTwoLargePrimitives, DisciplinePipe), + ]; + + var geometryGroups = primitives.GroupBy(primitive => primitive.TreeIndex); + + var result = PrioritySplittingUtils.ConvertPrimitiveGroupsToNodes(geometryGroups); + + Assert.That(result, Has.Length.EqualTo(3)); + Assert.Multiple(() => + { + Assert.That(result[treeIndexOneLargeOneSmallPrimitive].Geometries, Has.Length.EqualTo(1)); + Assert.That(result[treeIndexTwoSmallPrimitives].Geometries, Has.Length.EqualTo(2)); + Assert.That(result[treeIndexTwoLargePrimitives].Geometries, Has.Length.EqualTo(2)); + }); + } + + private static Box CreatePrimitive(float size, uint treeIndex, string? discipline = null) + { + return new Box(Matrix4x4.Identity, treeIndex, Color.Black, new BoundingBox(Vector3.Zero, size * Vector3.One)) + { + Discipline = discipline + }; + } + + private static CadRevealNode CreateCadRevealNode(uint treeIndex, string discipline, bool hasTagAttribute) + { + const string dummyName = "dummyName"; + + return new CadRevealNode + { + Attributes = hasTagAttribute + ? new Dictionary { ["Discipline"] = discipline, ["Tag"] = "tag" } + : new Dictionary { ["Discipline"] = discipline }, + Geometries = [CreatePrimitive(1.0f, treeIndex)], + TreeIndex = treeIndex, + Name = dummyName, + Parent = null + }; + } +} diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs index a95c696da..43fb1c739 100644 --- a/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs @@ -12,13 +12,13 @@ public static class PrioritySplittingUtils public static void SetPriorityForHighlightSplittingWithMutation(IReadOnlyList nodes) { - var disciplineFilteredNodes = FilterByDisciplineAndAddDisciplineMetadata(nodes).ToArray(); - var tagAndDisciplineFilteredNodes = FilterByIfTagExists(disciplineFilteredNodes).ToArray(); + var disciplineFilteredNodes = FilterByDisciplineAndAddDisciplineMetadata(nodes); + var tagAndDisciplineFilteredNodes = FilterByIfTagExists(disciplineFilteredNodes); SetPriorityOnNodesAndChildren(tagAndDisciplineFilteredNodes); } private static IEnumerable FilterByDisciplineAndAddDisciplineMetadata( - IReadOnlyList nodes + IEnumerable nodes ) { foreach (var node in nodes) @@ -38,12 +38,10 @@ IReadOnlyList nodes } } - private static IEnumerable FilterByIfTagExists(CadRevealNode[] nodes) - { - return nodes.Where(node => node.Attributes.ContainsKey("Tag")); - } + private static IEnumerable FilterByIfTagExists(IEnumerable nodes) => + nodes.Where(node => node.Attributes.ContainsKey("Tag")); - private static void SetPriorityOnNodesAndChildren(CadRevealNode[] nodes) + private static void SetPriorityOnNodesAndChildren(IEnumerable nodes) { foreach (var node in nodes) { From 39509107948512a70f0bdf3e1f8ce85167b5d347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stig=20Rune=20S=C3=B8berg?= Date: Fri, 25 Oct 2024 10:29:39 +0200 Subject: [PATCH 36/41] Fix small issue in CameraPositioningTests --- CadRevealComposer.Tests/Operations/CameraPositioningTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CadRevealComposer.Tests/Operations/CameraPositioningTests.cs b/CadRevealComposer.Tests/Operations/CameraPositioningTests.cs index 90c1bf7ad..57dc10262 100644 --- a/CadRevealComposer.Tests/Operations/CameraPositioningTests.cs +++ b/CadRevealComposer.Tests/Operations/CameraPositioningTests.cs @@ -32,5 +32,5 @@ public void InitialCameraPositionLongestAxisY() } private record TestPrimitiveWithBoundingBox(Vector3 Min, Vector3 Max) - : APrimitive(int.MaxValue, Color.Red, new BoundingBox(Min, Max)); + : APrimitive(int.MaxValue, Color.Red, new BoundingBox(Min, Max), 0, null); } From 4dd1a752375bb032eaa1c459649e339c8e507f25 Mon Sep 17 00:00:00 2001 From: vegasten Date: Mon, 28 Oct 2024 15:32:35 +0100 Subject: [PATCH 37/41] Renaming --- .../Operations/Splitting/PrioritySplittingUtilsTests.cs | 4 ++-- CadRevealComposer/CadRevealComposerRunner.cs | 2 +- .../Operations/SectorSplitting/InternalSector.cs | 2 +- .../Operations/SectorSplitting/PrioritySectorSplitter.cs | 4 ++-- .../Operations/SectorSplitting/PrioritySplittingUtils.cs | 2 +- CadRevealComposer/SceneCreator.cs | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CadRevealComposer.Tests/Operations/Splitting/PrioritySplittingUtilsTests.cs b/CadRevealComposer.Tests/Operations/Splitting/PrioritySplittingUtilsTests.cs index 82bb0f3b6..d27412f03 100644 --- a/CadRevealComposer.Tests/Operations/Splitting/PrioritySplittingUtilsTests.cs +++ b/CadRevealComposer.Tests/Operations/Splitting/PrioritySplittingUtilsTests.cs @@ -13,7 +13,7 @@ public class PrioritySplittingUtilsTests const string DisciplineCable = "CABLE"; [Test] - public void CadRevealNodesWithVariousConfigurations_SetPriorityForHighlightSplittingWithMutation_VerifyCorrectlyMutated() + public void CadRevealNodesWithVariousConfigurations_SetPriorityForPrioritySplittingWithMutation_VerifyCorrectlyMutated() { const uint treeIndexDisciplinePipeWithTag = 0; const uint treeIndexDisciplinePipeMissingTag = 1; @@ -28,7 +28,7 @@ public void CadRevealNodesWithVariousConfigurations_SetPriorityForHighlightSplit CreateCadRevealNode(treeIndexDisciplineCableMissingTag, DisciplineCable, false), ]; - PrioritySplittingUtils.SetPriorityForHighlightSplittingWithMutation(nodes); + PrioritySplittingUtils.SetPriorityForPrioritySplittingWithMutation(nodes); AssertDisciplineAndPriority(nodes[treeIndexDisciplinePipeWithTag].Geometries[0], DisciplinePipe, 1); AssertDisciplineAndPriority(nodes[treeIndexDisciplinePipeMissingTag].Geometries[0], DisciplinePipe, 0); diff --git a/CadRevealComposer/CadRevealComposerRunner.cs b/CadRevealComposer/CadRevealComposerRunner.cs index 82cfd94ba..0797ee874 100644 --- a/CadRevealComposer/CadRevealComposerRunner.cs +++ b/CadRevealComposer/CadRevealComposerRunner.cs @@ -87,7 +87,7 @@ IReadOnlyList modelFormatProviders nodesToExport.AddRange(cadRevealNodes); // Todo should this return a new list of cadrevealnodes instead of mutating the input? - PrioritySplittingUtils.SetPriorityForHighlightSplittingWithMutation(cadRevealNodes); + PrioritySplittingUtils.SetPriorityForPrioritySplittingWithMutation(cadRevealNodes); var inputGeometries = cadRevealNodes.AsParallel().AsOrdered().SelectMany(x => x.Geometries).ToArray(); diff --git a/CadRevealComposer/Operations/SectorSplitting/InternalSector.cs b/CadRevealComposer/Operations/SectorSplitting/InternalSector.cs index 943a19007..f5d848d0b 100644 --- a/CadRevealComposer/Operations/SectorSplitting/InternalSector.cs +++ b/CadRevealComposer/Operations/SectorSplitting/InternalSector.cs @@ -12,5 +12,5 @@ public record InternalSector( APrimitive[] Geometries, BoundingBox SubtreeBoundingBox, BoundingBox? GeometryBoundingBox, - bool IsHighlightSector = false + bool IsPrioritizedSector = false ); diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs index 3335383c8..2c681a4b4 100644 --- a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs @@ -22,7 +22,7 @@ SequentialIdGenerator sectorIdGenerator var sectors = new List(); // We split the geometries into sectors based on the discipline because there are very few highlight cases that are cross-discipline - // This helps reduce the overhead of a highlight sector, since highlighting of multiple tags within the same discipline is more common + // This helps reduce the overhead of a priority sector, since highlighting of multiple tags within the same discipline is more common var primitivesGroupedByDiscipline = allGeometries.GroupBy(x => x.Discipline); foreach (var disciplineGroup in primitivesGroupedByDiscipline) { @@ -40,7 +40,7 @@ SequentialIdGenerator sectorIdGenerator { yield return sector with { - IsHighlightSector = true + IsPrioritizedSector = true }; } } diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs index 43fb1c739..5d0c9a068 100644 --- a/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySplittingUtils.cs @@ -10,7 +10,7 @@ public static class PrioritySplittingUtils { private static readonly string[] PrioritizedDisciplines = ["PIPE"]; - public static void SetPriorityForHighlightSplittingWithMutation(IReadOnlyList nodes) + public static void SetPriorityForPrioritySplittingWithMutation(IReadOnlyList nodes) { var disciplineFilteredNodes = FilterByDisciplineAndAddDisciplineMetadata(nodes); var tagAndDisciplineFilteredNodes = FilterByIfTagExists(disciplineFilteredNodes); diff --git a/CadRevealComposer/SceneCreator.cs b/CadRevealComposer/SceneCreator.cs index 000f66ca4..108619e75 100644 --- a/CadRevealComposer/SceneCreator.cs +++ b/CadRevealComposer/SceneCreator.cs @@ -169,7 +169,7 @@ private static SectorInfo SerializeSector(InternalSector p, string outputDirecto string? sectorFilename = !p.Geometries.Any() ? null - : (!p.IsHighlightSector ? $"sector_{p.SectorId}.glb" : $"highlight_sector_{p.SectorId}.glb"); // The highlight_sector name is used in the frontend to identify the highlight sectors, don't rename without syncing with the frontend. + : (!p.IsPrioritizedSector ? $"sector_{p.SectorId}.glb" : $"highlight_sector_{p.SectorId}.glb"); // The highlight_sector name is used in the frontend to identify the highlight sectors, don't rename without syncing with the frontend. var sectorInfo = new SectorInfo( SectorId: p.SectorId, From 90c46e522de90d6503a1839c2dfa98f742909455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stig=20Rune=20S=C3=B8berg?= Date: Wed, 30 Oct 2024 09:45:57 +0100 Subject: [PATCH 38/41] Rename filename used for prioritized sectors --- CadRevealComposer/SceneCreator.cs | 40 ++++++++++++++++++------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/CadRevealComposer/SceneCreator.cs b/CadRevealComposer/SceneCreator.cs index 108619e75..9054f7f92 100644 --- a/CadRevealComposer/SceneCreator.cs +++ b/CadRevealComposer/SceneCreator.cs @@ -6,20 +6,20 @@ using System.Diagnostics; using System.IO; using System.Linq; -using CadRevealComposer.Operations.SectorSplitting; using Commons.Utils; using Configuration; using HierarchyComposer.Functions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Operations; +using Operations.SectorSplitting; using Primitives; using Utils; using Writers; public static class SceneCreator { - public record SectorInfo( + private record SectorInfo( uint SectorId, uint? ParentSectorId, long Depth, @@ -163,27 +163,25 @@ private static void ExportSectorGeometries( gltfSectorFile.Flush(true); } - private static SectorInfo SerializeSector(InternalSector p, string outputDirectory) + private static SectorInfo SerializeSector(InternalSector sector, string outputDirectory) { - var (estimatedTriangleCount, estimatedDrawCalls) = DrawCallEstimator.Estimate(p.Geometries); + var (estimatedTriangleCount, estimatedDrawCalls) = DrawCallEstimator.Estimate(sector.Geometries); - string? sectorFilename = !p.Geometries.Any() - ? null - : (!p.IsPrioritizedSector ? $"sector_{p.SectorId}.glb" : $"highlight_sector_{p.SectorId}.glb"); // The highlight_sector name is used in the frontend to identify the highlight sectors, don't rename without syncing with the frontend. + var sectorFilename = GetSectorFileName(sector); var sectorInfo = new SectorInfo( - SectorId: p.SectorId, - ParentSectorId: p.ParentSectorId, - Depth: p.Depth, - Path: p.Path, + SectorId: sector.SectorId, + ParentSectorId: sector.ParentSectorId, + Depth: sector.Depth, + Path: sector.Path, Filename: sectorFilename, EstimatedTriangleCount: estimatedTriangleCount, EstimatedDrawCalls: estimatedDrawCalls, - MinNodeDiagonal: p.MinNodeDiagonal, - MaxNodeDiagonal: p.MaxNodeDiagonal, - Geometries: p.Geometries, - SubtreeBoundingBox: p.SubtreeBoundingBox, - GeometryBoundingBox: p.GeometryBoundingBox + MinNodeDiagonal: sector.MinNodeDiagonal, + MaxNodeDiagonal: sector.MaxNodeDiagonal, + Geometries: sector.Geometries, + SubtreeBoundingBox: sector.SubtreeBoundingBox, + GeometryBoundingBox: sector.GeometryBoundingBox ); if (sectorFilename != null) @@ -191,6 +189,16 @@ private static SectorInfo SerializeSector(InternalSector p, string outputDirecto return sectorInfo; } + private static string? GetSectorFileName(InternalSector sector) + { + if (sector.Geometries.Length == 0) + return null; + + return sector.IsPrioritizedSector + ? $"prioritized_sector_{sector.SectorId}.glb" + : $"sector_{sector.SectorId}.glb"; + } + private static IEnumerable CalculateDownloadSizes( IEnumerable sectors, DirectoryInfo outputDirectory From 700d5e0721f79640d3102a3ee8f3fcff4f5a73a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stig=20Rune=20S=C3=B8berg?= Date: Wed, 30 Oct 2024 11:02:01 +0100 Subject: [PATCH 39/41] Add/fix a couple of comments --- .../Operations/CameraPositioningTests.cs | 1 - .../Operations/SectorSplitting/PrioritySectorSplitter.cs | 6 +++--- CadRevealComposer/SceneCreator.cs | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CadRevealComposer.Tests/Operations/CameraPositioningTests.cs b/CadRevealComposer.Tests/Operations/CameraPositioningTests.cs index 57dc10262..82c4f22b6 100644 --- a/CadRevealComposer.Tests/Operations/CameraPositioningTests.cs +++ b/CadRevealComposer.Tests/Operations/CameraPositioningTests.cs @@ -3,7 +3,6 @@ using System.Drawing; using System.Numerics; using CadRevealComposer.Operations; -using NUnit.Framework.Legacy; using Primitives; [TestFixture] diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs index 2c681a4b4..667033d70 100644 --- a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs @@ -58,8 +58,8 @@ SequentialIdGenerator sectorIdGenerator while (nodesProcessed < nodes.Length) { - // TODO: Should this use spatially aware splitting? Should it place nodes with similar attributes together? Ex: tags? - var nodesByBudget = GetNodesByBudgetSimple(nodesOrderedByTreeIndex, nodesProcessed).ToArray(); + // Batching nodes in groups by budget from list on nodes ordered by TreeIndex + var nodesByBudget = GetNodesByBudget(nodesOrderedByTreeIndex, nodesProcessed).ToArray(); nodesProcessed += nodesByBudget.Length; var sectorId = sectorIdGenerator.GetNextId(); @@ -69,7 +69,7 @@ SequentialIdGenerator sectorIdGenerator } } - private static IEnumerable GetNodesByBudgetSimple(Node[] nodes, int indexToStart) + private static IEnumerable GetNodesByBudget(Node[] nodes, int indexToStart) { var remainingBudget = SectorEstimatedByteSizeBudget; diff --git a/CadRevealComposer/SceneCreator.cs b/CadRevealComposer/SceneCreator.cs index 9054f7f92..f206683b8 100644 --- a/CadRevealComposer/SceneCreator.cs +++ b/CadRevealComposer/SceneCreator.cs @@ -194,6 +194,7 @@ private static SectorInfo SerializeSector(InternalSector sector, string outputDi if (sector.Geometries.Length == 0) return null; + // "sector" as prefix is reserved for normal sectors, do not start any other sector filenames with "sector" return sector.IsPrioritizedSector ? $"prioritized_sector_{sector.SectorId}.glb" : $"sector_{sector.SectorId}.glb"; From 8afa288c14f95d9744e307d29efe7ef69aa5b732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stig=20Rune=20S=C3=B8berg?= Date: Mon, 4 Nov 2024 13:02:01 +0100 Subject: [PATCH 40/41] Log how many primitives was considered outliers --- .../Operations/SectorSplitting/PrioritySectorSplitter.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs index 667033d70..293343b88 100644 --- a/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs +++ b/CadRevealComposer/Operations/SectorSplitting/PrioritySectorSplitter.cs @@ -1,5 +1,6 @@ namespace CadRevealComposer.Operations.SectorSplitting; +using System; using System.Collections.Generic; using System.Linq; using System.Numerics; @@ -30,8 +31,11 @@ SequentialIdGenerator sectorIdGenerator var nodes = PrioritySplittingUtils.ConvertPrimitiveGroupsToNodes(geometryGroups); // Ignore outlier nodes - // TODO: Decide if this is the right thing to do - (Node[] regularNodes, _) = nodes.SplitNodesIntoRegularAndOutlierNodes(); + (Node[] regularNodes, Node[] outliers) = nodes.SplitNodesIntoRegularAndOutlierNodes(); + if (outliers.Length > 0) + { + Console.WriteLine("{0} outliers found in priority sector splitting", outliers.Length); + } sectors.AddRange(SplitIntoTreeIndexSectors(regularNodes, dummyRootSector, sectorIdGenerator)); } From 7988a55c839c26121a84014e936794c4b8c6659e Mon Sep 17 00:00:00 2001 From: vegasten Date: Tue, 5 Nov 2024 11:04:01 +0100 Subject: [PATCH 41/41] Remove TODO and create story + create a method for post process db manipulation --- CadRevealComposer/CadRevealComposerRunner.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/CadRevealComposer/CadRevealComposerRunner.cs b/CadRevealComposer/CadRevealComposerRunner.cs index 0797ee874..d2bffd250 100644 --- a/CadRevealComposer/CadRevealComposerRunner.cs +++ b/CadRevealComposer/CadRevealComposerRunner.cs @@ -86,7 +86,6 @@ IReadOnlyList modelFormatProviders // collect all nodes for later sector division of the entire scene nodesToExport.AddRange(cadRevealNodes); - // Todo should this return a new list of cadrevealnodes instead of mutating the input? PrioritySplittingUtils.SetPriorityForPrioritySplittingWithMutation(cadRevealNodes); var inputGeometries = cadRevealNodes.AsParallel().AsOrdered().SelectMany(x => x.Geometries).ToArray(); @@ -140,9 +139,7 @@ IReadOnlyList modelFormatProviders WriteParametersToParamsFile(modelParameters, composerParameters, outputDirectory); - // TODO Is this the best place to add sector id mapping to the database? - // TODO Cont: Is it ok to mutate the database after it has been exported? Yes? - SceneCreator.AddPrioritizedSectorsToDatabase(splitExportResults.TreeIndexToSectorIdDict, outputDirectory); + ModifyHierarchyPostProcess(outputDirectory, splitExportResults); Console.WriteLine($"Export Finished. Wrote output files to \"{Path.GetFullPath(outputDirectory.FullName)}\""); Console.WriteLine($"Convert completed in {totalTimeElapsed.Elapsed}"); @@ -281,4 +278,15 @@ DirectoryInfo outputDirectory JsonSerializer.Serialize(json, new JsonSerializerOptions { WriteIndented = true }) ); } + + /// + /// Try to keep all manipulations of the hierarchy database after processing the model here + /// + private static void ModifyHierarchyPostProcess( + DirectoryInfo outputDirectory, + SplitAndExportResults splitExportResults + ) + { + SceneCreator.AddPrioritizedSectorsToDatabase(splitExportResults.TreeIndexToSectorIdDict, outputDirectory); + } }