diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f62bc7de..ca76164d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,7 @@ +### 5.5.4 +- [PointSet] fixed out-of-core node merge for part indices +- [PointSet] code cleanup, C#12 + ### 5.5.3 - Aardvark.Data.E57 parser now reads custom "Reflectance" data. - Updated System.Text.Json dependency (CVE-2024-43485) diff --git a/src/Aardvark.Algodat.Tests/Aardvark.Algodat.Tests.csproj b/src/Aardvark.Algodat.Tests/Aardvark.Algodat.Tests.csproj index 745149c9..37ef3851 100644 --- a/src/Aardvark.Algodat.Tests/Aardvark.Algodat.Tests.csproj +++ b/src/Aardvark.Algodat.Tests/Aardvark.Algodat.Tests.csproj @@ -10,12 +10,12 @@ ../../bin/Debug true - 1701;1702;7022 + 1701;1702;7022;0130 ../../bin/Release true - 1701;1702;7022 + 1701;1702;7022;0130 diff --git a/src/Aardvark.Algodat.Tests/Program.cs b/src/Aardvark.Algodat.Tests/Program.cs index 1fd09197..351a24cd 100644 --- a/src/Aardvark.Algodat.Tests/Program.cs +++ b/src/Aardvark.Algodat.Tests/Program.cs @@ -38,13 +38,13 @@ public class Program { #region CreateStore - internal static Task CreateStore(string filename, string storeDir, double minDist) - => CreateStore(filename, new DirectoryInfo(storeDir), minDist); + internal static Task CreateStore(string filename, string storeDir, double minDist, int maxDegreeOfParallelism = 0) + => CreateStore(filename, new DirectoryInfo(storeDir), minDist, maxDegreeOfParallelism); internal static Task CreateStore(IEnumerable chunks, string storeDir, double minDist, int? splitLimit = null) => CreateStore(chunks, new DirectoryInfo(storeDir), minDist, splitLimit); - internal static Task CreateStore(string filename, DirectoryInfo storeDir, double minDist) + internal static Task CreateStore(string filename, DirectoryInfo storeDir, double minDist, int maxDegreeOfParallelism = 0) { if (!storeDir.Exists) storeDir.Create(); @@ -56,7 +56,7 @@ internal static Task CreateStore(string filename, DirectoryInfo storeDir .WithStorage(store) .WithKey(key) .WithVerbose(true) - .WithMaxDegreeOfParallelism(0) + .WithMaxDegreeOfParallelism(maxDegreeOfParallelism) .WithMinDist(minDist) .WithNormalizePointDensityGlobal(true) //.WithMaxChunkPointCount(32 * 1024 * 1024) @@ -73,7 +73,7 @@ internal static Task CreateStore(string filename, DirectoryInfo storeDir return Task.FromResult(key); } - internal static Task CreateStore(IEnumerable chunks, DirectoryInfo storeDir, double minDist, int? splitLimit = null) + internal static Task CreateStore(IEnumerable chunks, DirectoryInfo storeDir, double minDist, int? splitLimit = null, int maxDegreeOfParallelism = 0) { if (!storeDir.Exists) storeDir.Create(); @@ -86,7 +86,7 @@ internal static Task CreateStore(IEnumerable chunks, DirectoryInf .WithStorage(store) .WithKey(key) .WithVerbose(true) - .WithMaxDegreeOfParallelism(0) + .WithMaxDegreeOfParallelism(maxDegreeOfParallelism) .WithMinDist(minDist) .WithNormalizePointDensityGlobal(true) //.WithMaxChunkPointCount(32 * 1024 * 1024) @@ -2807,7 +2807,14 @@ public static async Task Main(string[] _) { await Task.CompletedTask; // avoid warning if no async methods are called here ... - Test_Parse_Regression(); + await CreateStore( + @"W:\Datasets\Vgm\Data\2024-11-27_bugreport\Haus1cm.e57", + @"E:\tmp\Haus1cm.e57_000", + minDist: 0.005, + maxDegreeOfParallelism: 1 + ); + + //Test_Parse_Regression(); //Test_Import_Regression(); diff --git a/src/Aardvark.Geometry.PointSet/Aardvark.Geometry.PointSet.csproj b/src/Aardvark.Geometry.PointSet/Aardvark.Geometry.PointSet.csproj index 84a33b8d..5103d8bc 100644 --- a/src/Aardvark.Geometry.PointSet/Aardvark.Geometry.PointSet.csproj +++ b/src/Aardvark.Geometry.PointSet/Aardvark.Geometry.PointSet.csproj @@ -2,9 +2,9 @@ netstandard2.0 - 10 + 12 enable - 1701;1702;1591 + 1701;1702;1591;0130 ..\..\bin\Release\netstandard2.0\Aardvark.Geometry.PointSet.xml true Aardvark.Geometry.Points diff --git a/src/Aardvark.Geometry.PointSet/Editing/IMask2d.cs b/src/Aardvark.Geometry.PointSet/Editing/IMask2d.cs index b5de73bf..033fcaf4 100644 --- a/src/Aardvark.Geometry.PointSet/Editing/IMask2d.cs +++ b/src/Aardvark.Geometry.PointSet/Editing/IMask2d.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -14,38 +14,37 @@ You should have received a copy of the GNU Affero General Public License using Aardvark.Base; using System.Text.Json.Nodes; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public interface IMask2d { /// /// - public interface IMask2d - { - /// - /// - Triangle2d[] ComputeTriangulation(); - - /// - /// - IMask2d And(IMask2d other); - - /// - /// - IMask2d Or(IMask2d other); - - /// - /// - IMask2d Xor(IMask2d other); - - /// - /// - IMask2d Subtract(IMask2d other); - - /// - /// - bool IsEmpty { get; } - - /// - /// - JsonNode ToJson(); - } + Triangle2d[] ComputeTriangulation(); + + /// + /// + IMask2d And(IMask2d other); + + /// + /// + IMask2d Or(IMask2d other); + + /// + /// + IMask2d Xor(IMask2d other); + + /// + /// + IMask2d Subtract(IMask2d other); + + /// + /// + bool IsEmpty { get; } + + /// + /// + JsonNode ToJson(); } diff --git a/src/Aardvark.Geometry.PointSet/Editing/Mask3d.cs b/src/Aardvark.Geometry.PointSet/Editing/Mask3d.cs index 0d7f7ef3..b7ab00bd 100644 --- a/src/Aardvark.Geometry.PointSet/Editing/Mask3d.cs +++ b/src/Aardvark.Geometry.PointSet/Editing/Mask3d.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -16,141 +16,140 @@ You should have received a copy of the GNU Affero General Public License using System.Linq; using System.Text.Json.Nodes; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +[Serializable] +public class Mask3d { + private V3d m_camPosition; + private readonly IMask2d m_mask; + private readonly Trafo3d m_model2mask; + private readonly Triangle2d[] m_triangulation; + /// /// - [Serializable] - public class Mask3d + public Mask3d(V3d cameraPositionInModelSpace, IMask2d mask, Trafo3d model2mask) { - private V3d m_camPosition; - private readonly IMask2d m_mask; - private readonly Trafo3d m_model2mask; - private readonly Triangle2d[] m_triangulation; - - /// - /// - public Mask3d(V3d cameraPositionInModelSpace, IMask2d mask, Trafo3d model2mask) - { - m_camPosition = cameraPositionInModelSpace; - m_mask = mask; - m_model2mask = model2mask; - m_triangulation = m_mask.ComputeTriangulation(); - } + m_camPosition = cameraPositionInModelSpace; + m_mask = mask; + m_model2mask = model2mask; + m_triangulation = m_mask.ComputeTriangulation(); + } - /// - /// - public Mask3d And(Mask3d other) - { - if (m_model2mask != other.m_model2mask) throw new InvalidOperationException(); - return new Mask3d(m_camPosition, m_mask.And(other.m_mask), m_model2mask); - } + /// + /// + public Mask3d And(Mask3d other) + { + if (m_model2mask != other.m_model2mask) throw new InvalidOperationException(); + return new Mask3d(m_camPosition, m_mask.And(other.m_mask), m_model2mask); + } - /// - /// - public Mask3d Or(Mask3d other) - { - if (m_model2mask != other.m_model2mask) throw new InvalidOperationException(); - return new Mask3d(m_camPosition, m_mask.Or(other.m_mask), m_model2mask); - } + /// + /// + public Mask3d Or(Mask3d other) + { + if (m_model2mask != other.m_model2mask) throw new InvalidOperationException(); + return new Mask3d(m_camPosition, m_mask.Or(other.m_mask), m_model2mask); + } - /// - /// - public Mask3d Xor(Mask3d other) - { - if (m_model2mask != other.m_model2mask) throw new InvalidOperationException(); - return new Mask3d(m_camPosition, m_mask.Xor(other.m_mask), m_model2mask); - } + /// + /// + public Mask3d Xor(Mask3d other) + { + if (m_model2mask != other.m_model2mask) throw new InvalidOperationException(); + return new Mask3d(m_camPosition, m_mask.Xor(other.m_mask), m_model2mask); + } - /// - /// - public Mask3d Subtract(Mask3d other) - { - if (m_model2mask != other.m_model2mask) throw new InvalidOperationException(); - return new Mask3d(m_camPosition, m_mask.Subtract(other.m_mask), m_model2mask); - } + /// + /// + public Mask3d Subtract(Mask3d other) + { + if (m_model2mask != other.m_model2mask) throw new InvalidOperationException(); + return new Mask3d(m_camPosition, m_mask.Subtract(other.m_mask), m_model2mask); + } - /// - /// - public bool Contains(V3d p) + /// + /// + public bool Contains(V3d p) + { + var q_xyz = m_model2mask.Forward.TransformPosProj(p); + if (q_xyz.Z < 0.0) return false; + var q_xy = q_xyz.XY; + for (var i = 0; i < m_triangulation.Length; i++) { - var q_xyz = m_model2mask.Forward.TransformPosProj(p); - if (q_xyz.Z < 0.0) return false; - var q_xy = q_xyz.XY; - for (var i = 0; i < m_triangulation.Length; i++) - { - if (m_triangulation[i].Contains(q_xy)) return true; - } - return false; + if (m_triangulation[i].Contains(q_xy)) return true; } + return false; + } - /// - /// - public bool Contains(Box3d box, Func create) - { - //var cs3 = box.ComputeCorners().Map(c => m_model2mask.Forward.TransformPosProj(c)); - //if (cs3.Any(c => c.Z > 1.0)) return false; - //var cs = cs3.Map(c => c.XY); - //var outline = new GpcPolygon(new[] { cs[0], cs[2], cs[3], cs[1] }) - // .Unite(new GpcPolygon(new[] { cs[0], cs[4], cs[5], cs[1] })) - // .Unite(new GpcPolygon(new[] { cs[1], cs[5], cs[7], cs[3] })) - // .Unite(new GpcPolygon(new[] { cs[3], cs[7], cs[6], cs[2] })) - // .Unite(new GpcPolygon(new[] { cs[2], cs[6], cs[4], cs[0] })) - // .Unite(new GpcPolygon(new[] { cs[4], cs[6], cs[7], cs[5] })) - // ; - //var r = outline.Subtract(m_mask); + /// + /// + public bool Contains(Box3d box, Func create) + { + //var cs3 = box.ComputeCorners().Map(c => m_model2mask.Forward.TransformPosProj(c)); + //if (cs3.Any(c => c.Z > 1.0)) return false; + //var cs = cs3.Map(c => c.XY); + //var outline = new GpcPolygon(new[] { cs[0], cs[2], cs[3], cs[1] }) + // .Unite(new GpcPolygon(new[] { cs[0], cs[4], cs[5], cs[1] })) + // .Unite(new GpcPolygon(new[] { cs[1], cs[5], cs[7], cs[3] })) + // .Unite(new GpcPolygon(new[] { cs[3], cs[7], cs[6], cs[2] })) + // .Unite(new GpcPolygon(new[] { cs[2], cs[6], cs[4], cs[0] })) + // .Unite(new GpcPolygon(new[] { cs[4], cs[6], cs[7], cs[5] })) + // ; + //var r = outline.Subtract(m_mask); - var outline = box.GetOutlineProjected(m_camPosition, m_model2mask.Forward); - if (outline == null || outline.Length == 0) return false; - var r = create(outline).Subtract(m_mask); + var outline = box.GetOutlineProjected(m_camPosition, m_model2mask.Forward); + if (outline == null || outline.Length == 0) return false; + var r = create(outline).Subtract(m_mask); - return r.IsEmpty; - } + return r.IsEmpty; + } - /// - /// - public bool Intersects(Box3d box, Func create) + /// + /// + public bool Intersects(Box3d box, Func create) + { + try { - try - { - var cs3 = box.ComputeCorners().Map(c => m_model2mask.Forward.TransformPosProj(c)); - if (cs3.All(c => c.Z < 0.0)) return false; - if (cs3.Any(c => c.Z < 0.0)) return true; - var cs = cs3.Map(c => c.XY); - var outline = create(new[] { cs[0], cs[2], cs[3], cs[1] }) - .Or(create(new[] { cs[0], cs[4], cs[5], cs[1] })) - .Or(create(new[] { cs[1], cs[5], cs[7], cs[3] })) - .Or(create(new[] { cs[3], cs[7], cs[6], cs[2] })) - .Or(create(new[] { cs[2], cs[6], cs[4], cs[0] })) - .Or(create(new[] { cs[4], cs[6], cs[7], cs[5] })) - ; - var r = outline.And(m_mask); + var cs3 = box.ComputeCorners().Map(c => m_model2mask.Forward.TransformPosProj(c)); + if (cs3.All(c => c.Z < 0.0)) return false; + if (cs3.Any(c => c.Z < 0.0)) return true; + var cs = cs3.Map(c => c.XY); + var outline = create([cs[0], cs[2], cs[3], cs[1]]) + .Or(create([cs[0], cs[4], cs[5], cs[1]])) + .Or(create([cs[1], cs[5], cs[7], cs[3]])) + .Or(create([cs[3], cs[7], cs[6], cs[2]])) + .Or(create([cs[2], cs[6], cs[4], cs[0]])) + .Or(create([cs[4], cs[6], cs[7], cs[5]])) + ; + var r = outline.And(m_mask); - return !r.IsEmpty; - } - catch - { - return false; - } + return !r.IsEmpty; } - - /// - /// - public JsonObject ToJson() => new () - { - ["mask"] = m_mask.ToJson(), - ["model2mask"] = m_model2mask.ToString(), - ["camPosition"] = m_camPosition.ToString() - }; - - /// - /// - public static Mask3d Parse(JsonObject json, Func deserialize) + catch { - var mask = deserialize(json["mask"]!); - var model2mask = Trafo3d.Parse((string)json["model2mask"]!); - var camPosition = V3d.Parse((string)json["camPosition"]!); - return new Mask3d(camPosition, mask, model2mask); + return false; } } + + /// + /// + public JsonObject ToJson() => new () + { + ["mask"] = m_mask.ToJson(), + ["model2mask"] = m_model2mask.ToString(), + ["camPosition"] = m_camPosition.ToString() + }; + + /// + /// + public static Mask3d Parse(JsonObject json, Func deserialize) + { + var mask = deserialize(json["mask"]!); + var model2mask = Trafo3d.Parse((string)json["model2mask"]!); + var camPosition = V3d.Parse((string)json["camPosition"]!); + return new Mask3d(camPosition, mask, model2mask); + } } diff --git a/src/Aardvark.Geometry.PointSet/GlobalSuppressions.cs b/src/Aardvark.Geometry.PointSet/GlobalSuppressions.cs new file mode 100644 index 00000000..cd8a749d --- /dev/null +++ b/src/Aardvark.Geometry.PointSet/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0130:Namespace does not match folder structure", Justification = "", Scope = "namespace", Target = "~N:Aardvark.Geometry.Points")] diff --git a/src/Aardvark.Geometry.PointSet/Import/ImportChunks.cs b/src/Aardvark.Geometry.PointSet/Import/ImportChunks.cs index 64dc2873..4e9db31c 100644 --- a/src/Aardvark.Geometry.PointSet/Import/ImportChunks.cs +++ b/src/Aardvark.Geometry.PointSet/Import/ImportChunks.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -18,183 +18,182 @@ You should have received a copy of the GNU Affero General Public License using System.Linq; using System.Threading; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// Importers for various formats. +/// +public static partial class PointCloud { - /// - /// Importers for various formats. - /// - public static partial class PointCloud + private static IEnumerable MergeSmall(int limit, IEnumerable input) { - private static IEnumerable MergeSmall(int limit, IEnumerable input) + var current = default(Chunk); + foreach(var c in input) { - var current = default(Chunk); - foreach(var c in input) - { - if (c.Count == 0) Report.Warn($"[PointCloud.MergeSmall] empty chunk"); + if (c.Count == 0) Report.Warn($"[PointCloud.MergeSmall] empty chunk"); - if(c.Count < limit) - { - if (current != null) current = current.Union(c); - else current = c; - - if (current.Count >= limit) - { - yield return current; - current = null; - } + if(c.Count < limit) + { + if (current != null) current = current.Union(c); + else current = c; - } - else + if (current.Count >= limit) { - yield return c; + yield return current; + current = null; } - } - if (current != null) + } + else { - yield return current; + yield return c; } } - /// - /// Imports single chunk. - /// - public static PointSet Chunks(Chunk chunk, ImportConfig config) - => Chunks(new[] { chunk }, config); - - /// - /// Imports single chunk. - /// - public static PointSet Import(Chunk chunk, ImportConfig config) => Chunks(chunk, config); - - /// - /// Imports sequence of chunks. - /// - public static PointSet Chunks(IEnumerable chunks, ImportConfig config) + if (current != null) { - config.ProgressCallback(0.0); + yield return current; + } + } - var partIndicesRange = (Range1i?)null; - var chunkCount = 0; - chunks = chunks.Do(chunk => - { - if (chunk.HasPartIndices) - { - partIndicesRange = PartIndexUtils.ExtendRangeBy(partIndicesRange, chunk.PartIndices); - } + /// + /// Imports single chunk. + /// + public static PointSet Chunks(Chunk chunk, ImportConfig config) + => Chunks([chunk], config); - if (config.Verbose) - { - if (chunk.Count == 0) Report.Warn($"[PointCloud.Chunks] empty chunk"); - Report.Line($"[PointCloud.Chunks] processing chunk {Interlocked.Increment(ref chunkCount)}"); - } - }); + /// + /// Imports single chunk. + /// + public static PointSet Import(Chunk chunk, ImportConfig config) => Chunks(chunk, config); - // reproject positions - if (config.Reproject != null) - { - Chunk map(Chunk x, CancellationToken ct) - { - if (config.Reproject != null) - { - var ps = config.Reproject(x.Positions); - var y = x.WithPositions(ps); - return y; - } - else - { - return x; - } - } + /// + /// Imports sequence of chunks. + /// + public static PointSet Chunks(IEnumerable chunks, ImportConfig config) + { + config.ProgressCallback(0.0); - chunks = chunks.MapParallel(map, config.MaxDegreeOfParallelism, null, config.CancellationToken); + var partIndicesRange = (Range1i?)null; + var chunkCount = 0; + chunks = chunks.Do(chunk => + { + if (chunk.HasPartIndices) + { + partIndicesRange = PartIndexUtils.ExtendRangeBy(partIndicesRange, chunk.PartIndices); } - // deduplicate points - chunks = chunks - .Select(x => x.ImmutableDeduplicate(config.Verbose)) - .Do(chunk => - { - if (chunk.Count == 0) Report.Warn($"[PointCloud.Chunks] empty chunk"); - }) - ; - - // merge small chunks - chunks = MergeSmall(config.MaxChunkPointCount, chunks); + if (config.Verbose) + { + if (chunk.Count == 0) Report.Warn($"[PointCloud.Chunks] empty chunk"); + Report.Line($"[PointCloud.Chunks] processing chunk {Interlocked.Increment(ref chunkCount)}"); + } + }); - // filter minDist - if (config.MinDist > 0.0) + // reproject positions + if (config.Reproject != null) + { + Chunk map(Chunk x, CancellationToken ct) { - if (config.NormalizePointDensityGlobal) + if (config.Reproject != null) { - var smallestPossibleCellExponent = Fun.Log2(config.MinDist).Ceiling(); - chunks = chunks.Select(x => - { - if (x.Count == 0) Report.Warn($"[PointCloud.Chunks] empty chunk"); - var c = new Cell(x.BoundingBox); - while (c.Exponent < smallestPossibleCellExponent) c = c.Parent; - return x.ImmutableFilterMinDistByCell(c, config.ParseConfig); - }); + var ps = config.Reproject(x.Positions); + var y = x.WithPositions(ps); + return y; } else { - chunks = chunks.Select(x => x.ImmutableFilterSequentialMinDistL1(config.MinDist)); + return x; } } - // merge small chunks - chunks = MergeSmall(config.MaxChunkPointCount, chunks); + chunks = chunks.MapParallel(map, config.MaxDegreeOfParallelism, null, config.CancellationToken); + } + + // deduplicate points + chunks = chunks + .Select(x => x.ImmutableDeduplicate(config.Verbose)) + .Do(chunk => + { + if (chunk.Count == 0) Report.Warn($"[PointCloud.Chunks] empty chunk"); + }) + ; + + // merge small chunks + chunks = MergeSmall(config.MaxChunkPointCount, chunks); + + // filter minDist + if (config.MinDist > 0.0) + { + if (config.NormalizePointDensityGlobal) + { + var smallestPossibleCellExponent = Fun.Log2(config.MinDist).Ceiling(); + chunks = chunks.Select(x => + { + if (x.Count == 0) Report.Warn($"[PointCloud.Chunks] empty chunk"); + var c = new Cell(x.BoundingBox); + while (c.Exponent < smallestPossibleCellExponent) c = c.Parent; + return x.ImmutableFilterMinDistByCell(c, config.ParseConfig); + }); + } + else + { + chunks = chunks.Select(x => x.ImmutableFilterSequentialMinDistL1(config.MinDist)); + } + } + + // merge small chunks + chunks = MergeSmall(config.MaxChunkPointCount, chunks); - // EXPERIMENTAL - //Report.BeginTimed("unmix"); - //chunks = chunks.ImmutableUnmixOutOfCore(@"T:\tmp", 1, config); - //Report.End(); + // EXPERIMENTAL + //Report.BeginTimed("unmix"); + //chunks = chunks.ImmutableUnmixOutOfCore(@"T:\tmp", 1, config); + //Report.End(); #if DEBUG #if false - // store chunks before map/reduce - var debugChunkIndex = 0L; - var debugChunkDir = new DirectoryInfo(@"W:\tmp\debugChunks"); - if (!debugChunkDir.Exists) debugChunkDir.Create(); - chunks = chunks - .Do(chunk => - { - var buffer = chunk.ToGenericChunk().Data.DurableEncode(Durable.Primitives.DurableMap, gzipped: false); - var filename = Path.Combine(debugChunkDir.FullName, $"chunk_{debugChunkIndex++:00000}.dur"); - File.WriteAllBytes(filename, buffer); - if (chunk.Count == 0) Report.Warn($"[PointCloud.Chunks] empty chunk"); - }) - ; + // store chunks before map/reduce + var debugChunkIndex = 0L; + var debugChunkDir = new DirectoryInfo(@"W:\tmp\debugChunks"); + if (!debugChunkDir.Exists) debugChunkDir.Create(); + chunks = chunks + .Do(chunk => + { + var buffer = chunk.ToGenericChunk().Data.DurableEncode(Durable.Primitives.DurableMap, gzipped: false); + var filename = Path.Combine(debugChunkDir.FullName, $"chunk_{debugChunkIndex++:00000}.dur"); + File.WriteAllBytes(filename, buffer); + if (chunk.Count == 0) Report.Warn($"[PointCloud.Chunks] empty chunk"); + }) + ; #endif #endif - // reduce all chunks to single PointSet - if (config.Verbose) Report.BeginTimed("map/reduce"); - PointSet final = chunks - .MapReduce(config.WithRandomKey().WithProgressCallback(x => config.ProgressCallback(0.01 + x * 0.65))) - ; - if (config.Verbose) Report.EndTimed(); - - // create LOD data - if (config.Verbose) Report.BeginTimed("generate lod"); - final = final.GenerateLod(config.WithRandomKey().WithProgressCallback(x => config.ProgressCallback(0.66 + x * 0.34))); - if (final.Root.Value != null && final.Root.Value.Id != Guid.Empty && config.Storage?.GetPointCloudNode(final.Root.Value.Id) == null) throw new InvalidOperationException("Invariant 4d633e55-bf84-45d7-b9c3-c534a799242e."); - if (config.Verbose) Report.End(); - - // create final point set with specified key (or random key when no key is specified) - var key = config.Key ?? Guid.NewGuid().ToString(); - final = new PointSet( - storage: config.Storage ?? throw new Exception($"No storage specified. Error 5b4ebfec-d418-4ddc-9c2f-646d270cf78c."), - pointSetId: key, - rootCellId: final.Root.Value!.Id, - splitLimit: config.OctreeSplitLimit - ); - config.Storage.Add(key, final); - return final; - } - - /// - /// Imports sequence of chunks. - /// - public static PointSet Import(IEnumerable chunks, ImportConfig config) => Chunks(chunks, config); + // reduce all chunks to single PointSet + if (config.Verbose) Report.BeginTimed("map/reduce"); + PointSet final = chunks + .MapReduce(config.WithRandomKey().WithProgressCallback(x => config.ProgressCallback(0.01 + x * 0.65))) + ; + if (config.Verbose) Report.EndTimed(); + + // create LOD data + if (config.Verbose) Report.BeginTimed("generate lod"); + final = final.GenerateLod(config.WithRandomKey().WithProgressCallback(x => config.ProgressCallback(0.66 + x * 0.34))); + if (final.Root.Value != null && final.Root.Value.Id != Guid.Empty && config.Storage?.GetPointCloudNode(final.Root.Value.Id) == null) throw new InvalidOperationException("Invariant 4d633e55-bf84-45d7-b9c3-c534a799242e."); + if (config.Verbose) Report.End(); + + // create final point set with specified key (or random key when no key is specified) + var key = config.Key ?? Guid.NewGuid().ToString(); + final = new PointSet( + storage: config.Storage ?? throw new Exception($"No storage specified. Error 5b4ebfec-d418-4ddc-9c2f-646d270cf78c."), + pointSetId: key, + rootCellId: final.Root.Value!.Id, + splitLimit: config.OctreeSplitLimit + ); + config.Storage.Add(key, final); + return final; } + + /// + /// Imports sequence of chunks. + /// + public static PointSet Import(IEnumerable chunks, ImportConfig config) => Chunks(chunks, config); } diff --git a/src/Aardvark.Geometry.PointSet/Import/ImportFile.cs b/src/Aardvark.Geometry.PointSet/Import/ImportFile.cs index 307c4505..7fdfbd49 100644 --- a/src/Aardvark.Geometry.PointSet/Import/ImportFile.cs +++ b/src/Aardvark.Geometry.PointSet/Import/ImportFile.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -17,72 +17,71 @@ You should have received a copy of the GNU Affero General Public License using System.Collections.Generic; using System.IO; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// Importers for various formats. +/// +public static partial class PointCloud { /// - /// Importers for various formats. + /// Gets general info for given point cloud file. + /// + public static PointFileInfo ParseFileInfo(string filename, ParseConfig config) + => PointCloudFileFormat.FromFileName(filename).ParseFileInfo(filename, config); + + /// + /// Parses file. + /// Format is guessed based on file extension. /// - public static partial class PointCloud + public static IEnumerable Parse(string filename, ParseConfig config) { - /// - /// Gets general info for given point cloud file. - /// - public static PointFileInfo ParseFileInfo(string filename, ParseConfig config) - => PointCloudFileFormat.FromFileName(filename).ParseFileInfo(filename, config); - - /// - /// Parses file. - /// Format is guessed based on file extension. - /// - public static IEnumerable Parse(string filename, ParseConfig config) - { - if (filename == null) throw new ArgumentNullException(nameof(filename)); - if (!File.Exists(filename)) throw new FileNotFoundException($"File does not exist ({filename}).", filename); - return PointCloudFileFormat.FromFileName(filename).ParseFile(filename, config); - } + if (filename == null) throw new ArgumentNullException(nameof(filename)); + if (!File.Exists(filename)) throw new FileNotFoundException($"File does not exist ({filename}).", filename); + return PointCloudFileFormat.FromFileName(filename).ParseFile(filename, config); + } - /// - /// Imports file. - /// Format is guessed based on file extension. - /// - public static PointSet Import(string filename, ImportConfig? config = null) - { - if (filename == null) throw new ArgumentNullException(nameof(filename)); - if (!File.Exists(filename)) throw new FileNotFoundException("File does not exist.", filename); + /// + /// Imports file. + /// Format is guessed based on file extension. + /// + public static PointSet Import(string filename, ImportConfig? config = null) + { + if (filename == null) throw new ArgumentNullException(nameof(filename)); + if (!File.Exists(filename)) throw new FileNotFoundException("File does not exist.", filename); - config ??= ImportConfig.Default - .WithInMemoryStore() - .WithKey(FileHelpers.ComputeMd5Hash(filename, true)) - ; + config ??= ImportConfig.Default + .WithInMemoryStore() + .WithKey(FileHelpers.ComputeMd5Hash(filename, true)) + ; - var format = PointCloudFileFormat.FromFileName(filename); - if (format != PointCloudFileFormat.Unknown) - { - return format.ImportFile(filename, config); - } - else - { - throw new Exception($"Did not find parser for file '{filename}'."); - } + var format = PointCloudFileFormat.FromFileName(filename); + if (format != PointCloudFileFormat.Unknown) + { + return format.ImportFile(filename, config); } - - /// - /// Imports file into out-of-core store. - /// Format is guessed based on file extension. - /// - public static PointSet Import(string filename, string storeDirectory, LruDictionary cache) + else { - if (filename == null) throw new ArgumentNullException(nameof(filename)); - if (!File.Exists(filename)) throw new FileNotFoundException("File does not exist.", filename); + throw new Exception($"Did not find parser for file '{filename}'."); + } + } - var config = ImportConfig.Default - .WithStorage(OpenStore(storeDirectory, cache)) - .WithKey(FileHelpers.ComputeMd5Hash(filename, true)) - ; + /// + /// Imports file into out-of-core store. + /// Format is guessed based on file extension. + /// + public static PointSet Import(string filename, string storeDirectory, LruDictionary cache) + { + if (filename == null) throw new ArgumentNullException(nameof(filename)); + if (!File.Exists(filename)) throw new FileNotFoundException("File does not exist.", filename); - var result = PointCloudFileFormat.FromFileName(filename).ImportFile(filename, config); - config.Storage?.Flush(); - return result; - } + var config = ImportConfig.Default + .WithStorage(OpenStore(storeDirectory, cache)) + .WithKey(FileHelpers.ComputeMd5Hash(filename, true)) + ; + + var result = PointCloudFileFormat.FromFileName(filename).ImportFile(filename, config); + config.Storage?.Flush(); + return result; } } diff --git a/src/Aardvark.Geometry.PointSet/Import/ImportGenericChunks.cs b/src/Aardvark.Geometry.PointSet/Import/ImportGenericChunks.cs index 3549a69e..6769c528 100644 --- a/src/Aardvark.Geometry.PointSet/Import/ImportGenericChunks.cs +++ b/src/Aardvark.Geometry.PointSet/Import/ImportGenericChunks.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -18,147 +18,146 @@ You should have received a copy of the GNU Affero General Public License using System.Linq; using System.Threading; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// Importers for various formats. +/// +public static partial class PointCloud { - /// - /// Importers for various formats. - /// - public static partial class PointCloud + private static IEnumerable MergeSmall(int limit, IEnumerable input) { - private static IEnumerable MergeSmall(int limit, IEnumerable input) + var current = default(GenericChunk); + foreach (var c in input) { - var current = default(GenericChunk); - foreach (var c in input) + if (c.Count < limit) { - if (c.Count < limit) - { - if (current != null) current = current.Union(c); - else current = c; - - if (current.Count >= limit) - { - yield return current; - current = null; - } + if (current != null) current = current.Union(c); + else current = c; - } - else + if (current.Count >= limit) { - yield return c; + yield return current; + current = null; } - } - if (current != null) + } + else { - yield return current; + yield return c; } } - /// - /// Imports single chunk. - /// - public static PointSet Chunks(GenericChunk chunk, ImportConfig config) - => Chunks(new[] { chunk }, config); - - /// - /// Imports single chunk. - /// - public static PointSet Import(GenericChunk chunk, ImportConfig config) - => Chunks(chunk, config); - - /// - /// Imports sequence of chunks. - /// - public static PointSet Chunks(IEnumerable chunks, ImportConfig config) + if (current != null) { - config.ProgressCallback(0.0); + yield return current; + } + } - if (config.Verbose) - { - var chunkCount = 0; - chunks = chunks.Do(chunk => { Report.Line($"[PointCloud.Chunks] processing chunk {Interlocked.Increment(ref chunkCount)}"); }); - } + /// + /// Imports single chunk. + /// + public static PointSet Chunks(GenericChunk chunk, ImportConfig config) + => Chunks([chunk], config); - // deduplicate points - chunks = chunks.Select(x => x.ImmutableDeduplicate(config.Verbose)); + /// + /// Imports single chunk. + /// + public static PointSet Import(GenericChunk chunk, ImportConfig config) + => Chunks(chunk, config); - // merge small chunks - chunks = MergeSmall(config.MaxChunkPointCount, chunks); + /// + /// Imports sequence of chunks. + /// + public static PointSet Chunks(IEnumerable chunks, ImportConfig config) + { + config.ProgressCallback(0.0); + + if (config.Verbose) + { + var chunkCount = 0; + chunks = chunks.Do(chunk => { Report.Line($"[PointCloud.Chunks] processing chunk {Interlocked.Increment(ref chunkCount)}"); }); + } + + // deduplicate points + chunks = chunks.Select(x => x.ImmutableDeduplicate(config.Verbose)); - // filter minDist - if (config.MinDist > 0.0) + // merge small chunks + chunks = MergeSmall(config.MaxChunkPointCount, chunks); + + // filter minDist + if (config.MinDist > 0.0) + { + if (config.NormalizePointDensityGlobal) { - if (config.NormalizePointDensityGlobal) - { - var smallestPossibleCellExponent = Fun.Log2(config.MinDist).Ceiling(); - chunks = chunks.Select(x => - { - var c = new Cell(x.BoundingBox); - while (c.Exponent < smallestPossibleCellExponent) c = c.Parent; - return x.ImmutableFilterMinDistByCell(c, config.ParseConfig); - }); - } - else + var smallestPossibleCellExponent = Fun.Log2(config.MinDist).Ceiling(); + chunks = chunks.Select(x => { - chunks = chunks.Select(x => x.ImmutableFilterSequentialMinDistL1(config.MinDist)); - } + var c = new Cell(x.BoundingBox); + while (c.Exponent < smallestPossibleCellExponent) c = c.Parent; + return x.ImmutableFilterMinDistByCell(c, config.ParseConfig); + }); } + else + { + chunks = chunks.Select(x => x.ImmutableFilterSequentialMinDistL1(config.MinDist)); + } + } - // merge small chunks - chunks = MergeSmall(config.MaxChunkPointCount, chunks); + // merge small chunks + chunks = MergeSmall(config.MaxChunkPointCount, chunks); - // EXPERIMENTAL - //Report.BeginTimed("unmix"); - //chunks = chunks.ImmutableUnmixOutOfCore(@"T:\tmp", 1, config); - //Report.End(); + // EXPERIMENTAL + //Report.BeginTimed("unmix"); + //chunks = chunks.ImmutableUnmixOutOfCore(@"T:\tmp", 1, config); + //Report.End(); - // reproject positions and/or estimate normals - if (config.Reproject != null) + // reproject positions and/or estimate normals + if (config.Reproject != null) + { + GenericChunk map(GenericChunk x, CancellationToken ct) { - GenericChunk map(GenericChunk x, CancellationToken ct) + if (config.Reproject != null) { - if (config.Reproject != null) + x = x.Positions switch { - x = x.Positions switch - { - V2f[] ps => x.WithPositions(config.Reproject(ps.Map(p => (V3d)p.XYO)).Map(p => (V2f)p.XY)), - V2d[] ps => x.WithPositions(config.Reproject(ps.Map(p => p.XYO)).Map(p => p.XY)), - V3f[] ps => x.WithPositions(config.Reproject(ps.Map(p => (V3d)p)).Map(p => (V3f)p)), - V3d[] ps => x.WithPositions(config.Reproject(ps)), - _ => throw new Exception($"Unsupported positions type {x.Positions.GetType()}."), - }; - } - - return x; + V2f[] ps => x.WithPositions(config.Reproject(ps.Map(p => (V3d)p.XYO)).Map(p => (V2f)p.XY)), + V2d[] ps => x.WithPositions(config.Reproject(ps.Map(p => p.XYO)).Map(p => p.XY)), + V3f[] ps => x.WithPositions(config.Reproject(ps.Map(p => (V3d)p)).Map(p => (V3f)p)), + V3d[] ps => x.WithPositions(config.Reproject(ps)), + _ => throw new Exception($"Unsupported positions type {x.Positions.GetType()}."), + }; } - chunks = chunks.MapParallel(map, config.MaxDegreeOfParallelism, null, config.CancellationToken); + return x; } - // reduce all chunks to single PointSet - if (config.Verbose) Report.BeginTimed("map/reduce"); - var final = chunks - .MapReduce(config.WithRandomKey().WithProgressCallback(x => config.ProgressCallback(0.01 + x * 0.65))) - ; - if (config.Verbose) Report.EndTimed(); - - // create LOD data - if (config.Verbose) Report.BeginTimed("generate lod"); - final = final.GenerateLod(config.WithRandomKey().WithProgressCallback(x => config.ProgressCallback(0.66 + x * 0.34))); - if (config.Storage.GetPointCloudNode(final.Root.Value.Id) == null) throw new InvalidOperationException("Invariant 4d633e55-bf84-45d7-b9c3-c534a799242e."); - if (config.Verbose) Report.End(); - - // create final point set with specified key (or random key when no key is specified) - var key = config.Key ?? Guid.NewGuid().ToString(); - final = new PointSet(config.Storage, key, final.Root.Value.Id, config.OctreeSplitLimit); - config.Storage.Add(key, final); - - return final; + chunks = chunks.MapParallel(map, config.MaxDegreeOfParallelism, null, config.CancellationToken); } - /// - /// Imports sequence of chunks. - /// - public static PointSet Import(IEnumerable chunks, ImportConfig config) => Chunks(chunks, config); + // reduce all chunks to single PointSet + if (config.Verbose) Report.BeginTimed("map/reduce"); + var final = chunks + .MapReduce(config.WithRandomKey().WithProgressCallback(x => config.ProgressCallback(0.01 + x * 0.65))) + ; + if (config.Verbose) Report.EndTimed(); + + // create LOD data + if (config.Verbose) Report.BeginTimed("generate lod"); + final = final.GenerateLod(config.WithRandomKey().WithProgressCallback(x => config.ProgressCallback(0.66 + x * 0.34))); + if (config.Storage.GetPointCloudNode(final.Root.Value.Id) == null) throw new InvalidOperationException("Invariant 4d633e55-bf84-45d7-b9c3-c534a799242e."); + if (config.Verbose) Report.End(); + + // create final point set with specified key (or random key when no key is specified) + var key = config.Key ?? Guid.NewGuid().ToString(); + final = new PointSet(config.Storage, key, final.Root.Value.Id, config.OctreeSplitLimit); + config.Storage.Add(key, final); + + return final; } + + /// + /// Imports sequence of chunks. + /// + public static PointSet Import(IEnumerable chunks, ImportConfig config) => Chunks(chunks, config); } diff --git a/src/Aardvark.Geometry.PointSet/Import/ImportStore.cs b/src/Aardvark.Geometry.PointSet/Import/ImportStore.cs index 78d62380..6e060183 100644 --- a/src/Aardvark.Geometry.PointSet/Import/ImportStore.cs +++ b/src/Aardvark.Geometry.PointSet/Import/ImportStore.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -16,168 +16,165 @@ You should have received a copy of the GNU Affero General Public License using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Threading; using Uncodium.SimpleStore; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// Importers for various formats. +/// +public static partial class PointCloud { /// - /// Importers for various formats. + /// Gets general info for store dataset. /// - public static partial class PointCloud + public static PointFileInfo StoreInfo(string storePath, string key) { - /// - /// Gets general info for store dataset. - /// - public static PointFileInfo StoreInfo(string storePath, string key) - { - if (!Directory.Exists(storePath)) return new PointFileInfo(storePath, PointCloudFileFormat.Unknown, 0, 0, Box3d.Invalid); + if (!Directory.Exists(storePath)) return new PointFileInfo(storePath, PointCloudFileFormat.Unknown, 0, 0, Box3d.Invalid); - var store = OpenStore(storePath, cache: default); - var pointset = store.GetPointSet(key); - return new PointFileInfo(storePath, PointCloudFileFormat.Store, 0L, pointset?.PointCount ?? 0, pointset?.Bounds ?? Box3d.Invalid); - } + var store = OpenStore(storePath, cache: default); + var pointset = store.GetPointSet(key); + return new PointFileInfo(storePath, PointCloudFileFormat.Store, 0L, pointset?.PointCount ?? 0, pointset?.Bounds ?? Box3d.Invalid); + } - /// - /// Loads point cloud from store. - /// - public static PointSet Load(string key, string storePath, LruDictionary? cache = null) - { - //if (!Directory.Exists(storePath)) throw new InvalidOperationException($"Not a store ({storePath})."); + /// + /// Loads point cloud from store. + /// + public static PointSet Load(string key, string storePath, LruDictionary? cache = null) + { + //if (!Directory.Exists(storePath)) throw new InvalidOperationException($"Not a store ({storePath})."); - var store = OpenStore(storePath, cache); - var result = store.GetPointSet(key); - return result ?? throw new InvalidOperationException($"Key {key} not found in {storePath}."); - } + var store = OpenStore(storePath, cache); + var result = store.GetPointSet(key); + return result ?? throw new InvalidOperationException($"Key {key} not found in {storePath}."); + } - /// - /// Loads point cloud from store. - /// - public static PointSet Load(string key, Storage store) - { - if (store == null) throw new ArgumentNullException(nameof(store)); - var result = store.GetPointSet(key); - return result ?? throw new InvalidOperationException($"Key {key} not found in store."); - } + /// + /// Loads point cloud from store. + /// + public static PointSet Load(string key, Storage store) + { + if (store == null) throw new ArgumentNullException(nameof(store)); + var result = store.GetPointSet(key); + return result ?? throw new InvalidOperationException($"Key {key} not found in store."); + } - /// - /// Opens or creates a store at the specified location. - /// - public static Storage OpenStore(string storePath, LruDictionary? cache = null, Action? logLines = null, bool readOnly = false) + /// + /// Opens or creates a store at the specified location. + /// + public static Storage OpenStore(string storePath, LruDictionary? cache = null, Action? logLines = null, bool readOnly = false) + { + cache ??= new LruDictionary(2L << 30); + + lock (s_stores) { - cache ??= new LruDictionary(2L << 30); + // if we are lucky, then this storePath is already cached + if (s_stores.TryGetValue(storePath, out WeakReference x) && x.TryGetTarget(out Storage cached)) + { + if (!cached.IsDisposed) + { + // cache hit -> we are done + return cached; + } + } - lock (s_stores) + // cache miss ... + ISimpleStore simpleStore; + ISimpleStore initDiskStore() { - // if we are lucky, then this storePath is already cached - if (s_stores.TryGetValue(storePath, out WeakReference x) && x.TryGetTarget(out Storage cached)) + if (logLines == null) { - if (!cached.IsDisposed) + if (readOnly) { - // cache hit -> we are done - return cached; + return SimpleDiskStore.OpenReadOnlySnapshot(storePath); + } + else + { + return new SimpleDiskStore(storePath); } } - - // cache miss ... - ISimpleStore simpleStore; - ISimpleStore initDiskStore() + else { - if (logLines == null) + if (readOnly) { - if (readOnly) - { - return SimpleDiskStore.OpenReadOnlySnapshot(storePath); - } - else - { - return new SimpleDiskStore(storePath); - } + return SimpleDiskStore.OpenReadOnlySnapshot(storePath, logLines); } else { - if (readOnly) - { - return SimpleDiskStore.OpenReadOnlySnapshot(storePath, logLines); - } - else - { - return new SimpleDiskStore(storePath, logLines); - } + return new SimpleDiskStore(storePath, logLines); } - }; - ISimpleStore initFolderStore() => new SimpleFolderStore(storePath); + } + }; + ISimpleStore initFolderStore() => new SimpleFolderStore(storePath); - if (File.Exists(storePath)) - { - // if storePath is a file, then open as Uncodium.SimpleDiskStore v3 - simpleStore = initDiskStore(); + if (File.Exists(storePath)) + { + // if storePath is a file, then open as Uncodium.SimpleDiskStore v3 + simpleStore = initDiskStore(); - // .. or fail - } - else + // .. or fail + } + else + { + // if storePath is a folder, then ... + if (Directory.Exists(storePath)) { - // if storePath is a folder, then ... - if (Directory.Exists(storePath)) + // check for a file named 'data.bin' + var hasDataFile = File.Exists(Path.Combine(storePath, "data.bin")); + if (hasDataFile) { - // check for a file named 'data.bin' - var hasDataFile = File.Exists(Path.Combine(storePath, "data.bin")); - if (hasDataFile) + try { - try - { - // a 'data.bin' file indicates that this could be - // - a pre-v3 store (then there is also an index.bin file) -> this will be automatically upgraded to v3 format - // - or the result of an automatic upgrade from a pre-v3 store (data.bin has been upgraded to v3 format and index.bin has been imported and deleted) - simpleStore = initDiskStore(); - } - catch (Exception e) - { - // mmmh, unfortunately, the 'data.bin' file is not a SimpleDiskStore - // -> probably the intention was to open the folder as a SimpleFolderStore, - // but by accident there is a key/file named 'index.bin' - Report.Warn( - $"Failed to open the folder '{storePath}' (containing a file named index.bin) as an Uncodium.SimpleDiskStore. " + - $"Opening folder as an Uncodium.SimpleFolderStore instead. " + - $"Exception was {e}."); - simpleStore = initFolderStore(); - } + // a 'data.bin' file indicates that this could be + // - a pre-v3 store (then there is also an index.bin file) -> this will be automatically upgraded to v3 format + // - or the result of an automatic upgrade from a pre-v3 store (data.bin has been upgraded to v3 format and index.bin has been imported and deleted) + simpleStore = initDiskStore(); } - else + catch (Exception e) { - // we have a folder without a 'data.bin' file -> open folder as SimpleFolderStore + // mmmh, unfortunately, the 'data.bin' file is not a SimpleDiskStore + // -> probably the intention was to open the folder as a SimpleFolderStore, + // but by accident there is a key/file named 'index.bin' + Report.Warn( + $"Failed to open the folder '{storePath}' (containing a file named index.bin) as an Uncodium.SimpleDiskStore. " + + $"Opening folder as an Uncodium.SimpleFolderStore instead. " + + $"Exception was {e}."); simpleStore = initFolderStore(); } } else { - // storePath does not exist (neither file nor folder) - // -> just create new SimpleDiskStore - simpleStore = initDiskStore(); + // we have a folder without a 'data.bin' file -> open folder as SimpleFolderStore + simpleStore = initFolderStore(); } } - - // insert into cache and return - var store = simpleStore.ToPointCloudStore(cache); - s_stores[storePath] = new WeakReference(store); - return store; + else + { + // storePath does not exist (neither file nor folder) + // -> just create new SimpleDiskStore + simpleStore = initDiskStore(); + } } + + // insert into cache and return + var store = simpleStore.ToPointCloudStore(cache); + s_stores[storePath] = new WeakReference(store); + return store; } - private static readonly Dictionary> s_stores = new(); + } + private static readonly Dictionary> s_stores = []; - /// - /// Creates an in-memory store. - /// - public static Storage CreateInMemoryStore() - => new SimpleMemoryStore().ToPointCloudStore(new LruDictionary(1024 * 1024 * 1024)) - ; + /// + /// Creates an in-memory store. + /// + public static Storage CreateInMemoryStore() + => new SimpleMemoryStore().ToPointCloudStore(new LruDictionary(1024 * 1024 * 1024)) + ; - /// - /// Creates an in-memory store. - /// - public static Storage CreateInMemoryStore(LruDictionary cache) - => new SimpleMemoryStore().ToPointCloudStore(cache ?? new LruDictionary(1024 * 1024 * 1024)) - ; - } + /// + /// Creates an in-memory store. + /// + public static Storage CreateInMemoryStore(LruDictionary cache) + => new SimpleMemoryStore().ToPointCloudStore(cache ?? new LruDictionary(1024 * 1024 * 1024)) + ; } diff --git a/src/Aardvark.Geometry.PointSet/Octrees/Delete.cs b/src/Aardvark.Geometry.PointSet/Octrees/Delete.cs index ed0fad73..1436fe3f 100644 --- a/src/Aardvark.Geometry.PointSet/Octrees/Delete.cs +++ b/src/Aardvark.Geometry.PointSet/Octrees/Delete.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -11,153 +11,252 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ +using Aardvark.Base; +using Aardvark.Data; +using Aardvark.Data.Points; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; -using Aardvark.Base; -using Aardvark.Data; -using Aardvark.Data.Points; -using static Aardvark.Base.SymMapBaseTraversal; -namespace Aardvark.Geometry.Points -{ - public record struct PointDeleteAttributes( - byte? Classification, - int? PartIndex - ); +namespace Aardvark.Geometry.Points; + +public record struct PointDeleteAttributes( + byte? Classification, + int? PartIndex +); +/// +/// +public static class DeleteExtensions +{ /// + /// Returns new pointset without points specified as inside. + /// Returns null, if no points are left. /// - public static class DeleteExtensions + public static PointSet? Delete(this PointSet? pointSet, + Func isNodeFullyInside, + Func isNodeFullyOutside, + Func isPositionInside, + Storage storage, CancellationToken ct + ) { - /// - /// Returns new pointset without points specified as inside. - /// Returns null, if no points are left. - /// - public static PointSet? Delete(this PointSet? pointSet, - Func isNodeFullyInside, - Func isNodeFullyOutside, - Func isPositionInside, - Storage storage, CancellationToken ct - ) - { - if (pointSet == null) return null; + if (pointSet == null) return null; + + var root = Delete(pointSet.Root.Value, isNodeFullyInside, isNodeFullyOutside, isPositionInside, storage, ct, pointSet.SplitLimit); + if (root == null) return null; + + var newId = Guid.NewGuid().ToString(); + var result = new PointSet(pointSet.Storage, newId, root.Id, pointSet.SplitLimit); + pointSet.Storage.Add(newId, result); + return result; + } + + public static PointSet? Delete(this PointSet? pointSet, + Func isNodeFullyInside, + Func isNodeFullyOutside, + Func isPositionInside, + Storage storage, CancellationToken ct + ) + { + return pointSet?.Delete( + isNodeFullyInside, + isNodeFullyOutside, + (V3d p, PointDeleteAttributes _) => isPositionInside(p), + storage, + ct + ); + } - var root = Delete(pointSet.Root.Value, isNodeFullyInside, isNodeFullyOutside, isPositionInside, storage, ct, pointSet.SplitLimit); - if (root == null) return null; + /// + /// Returns new octree with all points deleted which are inside. + /// Returns null, if no points are left. + /// + public static IPointCloudNode? Delete(this IPointCloudNode? root, + Func isNodeFullyInside, + Func isNodeFullyOutside, + Func isPositionInside, + Storage storage, CancellationToken ct, + int splitLimit + ) + { + if (root == null) return null; - var newId = Guid.NewGuid().ToString(); - var result = new PointSet(pointSet.Storage, newId, root.Id, pointSet.SplitLimit); - pointSet.Storage.Add(newId, result); - return result; + if (root is FilteredNode f) + { + if (f.Filter is ISpatialFilter filter) + { + bool remove(IPointCloudNode n) => filter.IsFullyInside(n) && isNodeFullyInside(n); + bool keep(IPointCloudNode n) => filter.IsFullyOutside(n) || isNodeFullyOutside(n); + bool contains(V3d pt, PointDeleteAttributes att) => filter.Contains(pt) && isPositionInside(pt, att); + var res = f.Node.Delete(remove, keep, contains, storage, ct, splitLimit); + if (res == null) return null; + return FilteredNode.Create(res, f.Filter); + } + else + { + throw new Exception("Delete is not supported on PointCloud with non-spatial filter. Error 1885c46f-2eef-4dfb-807b-439c1b9c673d."); + } } - public static PointSet? Delete(this PointSet? pointSet, - Func isNodeFullyInside, - Func isNodeFullyOutside, - Func isPositionInside, - Storage storage, CancellationToken ct - ) + + if (isNodeFullyInside(root)) return null; + if (isNodeFullyOutside(root)) { - return pointSet?.Delete( - isNodeFullyInside, - isNodeFullyOutside, - (V3d p, PointDeleteAttributes _) => isPositionInside(p), - storage, - ct - ); + if (!root.IsMaterialized) + { + root = root.Materialize(); + } + return root; } - /// - /// Returns new octree with all points deleted which are inside. - /// Returns null, if no points are left. - /// - public static IPointCloudNode? Delete(this IPointCloudNode? root, - Func isNodeFullyInside, - Func isNodeFullyOutside, - Func isPositionInside, - Storage storage, CancellationToken ct, - int splitLimit - ) + if (root.IsLeaf) { - if (root == null) return null; + var ps = new List(); + var cs = root.HasColors ? new List() : null; + var ns = root.HasNormals ? new List() : null; + var js = root.HasIntensities ? new List() : null; + var ks = root.HasClassifications ? new List() : null; + var piis = root.HasPartIndices ? new List() : null; //partIndex array indices + var oldPs = root.Positions.Value!; + var oldCs = root.Colors?.Value; + var oldNs = root.Normals?.Value; + var oldIs = root.Intensities?.Value; + var oldKs = root.Classifications?.Value; + var bbabs = Box3d.Invalid; + var bbloc = Box3f.Invalid; + + for (var i = 0; i < oldPs.Length; i++) + { + var pabs = (V3d)oldPs[i] + root.Center; + byte? oldK = oldKs?[i]; + int? oldPi = piis != null ? PartIndexUtils.Get(root.PartIndices, i) : null; + var atts = new PointDeleteAttributes(oldK, oldPi); + if (!isPositionInside(pabs, atts)) + { + ps.Add(oldPs[i]); + if (oldCs != null) cs!.Add(oldCs[i]); + if (oldNs != null) ns!.Add(oldNs[i]); + if (oldIs != null) js!.Add(oldIs[i]); + if (oldKs != null) ks!.Add(oldKs[i]); + if (piis != null) piis!.Add(i); + bbabs.ExtendBy(pabs); + bbloc.ExtendBy(oldPs[i]); + } + } - if (root is FilteredNode f) + if (ps.Count == 0) return null; + + var pis = (piis != null) ? PartIndexUtils.Subset(root.PartIndices, piis) : null; + + + var psa = ps.ToArray(); + var newId = Guid.NewGuid(); + var kd = psa.Length < 1 ? null : psa.BuildKdTree(); + + Guid psId = Guid.NewGuid(); + Guid kdId = kd != null ? Guid.NewGuid() : Guid.Empty; + Guid csId = cs != null ? Guid.NewGuid() : Guid.Empty; + Guid nsId = ns != null ? Guid.NewGuid() : Guid.Empty; + Guid isId = js != null ? Guid.NewGuid() : Guid.Empty; + Guid ksId = ks != null ? Guid.NewGuid() : Guid.Empty; + Guid pisId = pis != null ? Guid.NewGuid() : Guid.Empty; + + storage.Add(psId, psa); + + var data = ImmutableDictionary.Empty + .Add(Durable.Octree.NodeId, newId) + .Add(Durable.Octree.Cell, root.Cell) + .Add(Durable.Octree.BoundingBoxExactGlobal, bbabs) + .Add(Durable.Octree.BoundingBoxExactLocal, bbloc) + .Add(Durable.Octree.PositionsLocal3fReference, psId) + .Add(Durable.Octree.PointCountCell, ps.Count) + .Add(Durable.Octree.PointCountTreeLeafs, (long)ps.Count) + .Add(Durable.Octree.MaxTreeDepth, 0) + .Add(Durable.Octree.MinTreeDepth, 0) + ; + + + if (kd != null) + { + storage.Add(kdId, kd.Data); + data = data.Add(Durable.Octree.PointRkdTreeFDataReference, kdId); + } + if (cs != null) + { + storage.Add(csId, cs.ToArray()); + data = data.Add(Durable.Octree.Colors4bReference, csId); + } + if (ns != null) { - if (f.Filter is ISpatialFilter filter) + storage.Add(nsId, ns.ToArray()); + data = data.Add(Durable.Octree.Normals3fReference, nsId); + } + if (js != null) + { + storage.Add(isId, js.ToArray()); + data = data.Add(Durable.Octree.Intensities1iReference, isId); + } + if (ks != null) + { + storage.Add(ksId, ks.ToArray()); + data = data.Add(Durable.Octree.Classifications1bReference, ksId); + } + if (pis != null) + { + var piRange = PartIndexUtils.GetRange(pis); + data = data.Add(Durable.Octree.PartIndexRange, piRange!); + + if (pis is Array xs) { - bool remove(IPointCloudNode n) => filter.IsFullyInside(n) && isNodeFullyInside(n); - bool keep(IPointCloudNode n) => filter.IsFullyOutside(n) || isNodeFullyOutside(n); - bool contains(V3d pt, PointDeleteAttributes att) => filter.Contains(pt) && isPositionInside(pt, att); - var res = f.Node.Delete(remove, keep, contains, storage, ct, splitLimit); - if (res == null) return null; - return FilteredNode.Create(res, f.Filter); + storage.Add(pisId, xs); + var def = xs switch + { + byte[] => Durable.Octree.PerPointPartIndex1bReference, + short[] => Durable.Octree.PerPointPartIndex1sReference, + int[] => Durable.Octree.PerPointPartIndex1iReference, + _ => throw new Exception("[Delete] Unknown type. Invariant 95811F1A-5FFF-4EED-8C31-8C267CAB85A6.") + }; + data = data.Add(def, pisId); } else { - throw new Exception("Delete is not supported on PointCloud with non-spatial filter. Error 1885c46f-2eef-4dfb-807b-439c1b9c673d."); + data = data + .Add(PartIndexUtils.GetDurableDefForPartIndices(pis), pis) + ; } } + return new PointSetNode(data, storage, writeToStore: true); + } + else + { + var subnodes = root.Subnodes.Map((r) => r?.Value?.Delete(isNodeFullyInside, isNodeFullyOutside, isPositionInside, storage, ct, splitLimit)); - if (isNodeFullyInside(root)) return null; - if (isNodeFullyOutside(root)) + var pointCountTree = subnodes.Sum((n) => n != null ? n.PointCountTree : 0); + if (pointCountTree == 0) { - if (!root.IsMaterialized) - { - root = root.Materialize(); - } - return root; + return null; } - - if (root.IsLeaf) + else if (pointCountTree <= splitLimit) { - var ps = new List(); + var psabs = new List(); var cs = root.HasColors ? new List() : null; var ns = root.HasNormals ? new List() : null; var js = root.HasIntensities ? new List() : null; var ks = root.HasClassifications ? new List() : null; - var piis = root.HasPartIndices ? new List() : null; //partIndex array indices - var oldPs = root.Positions.Value!; - var oldCs = root.Colors?.Value; - var oldNs = root.Normals?.Value; - var oldIs = root.Intensities?.Value; - var oldKs = root.Classifications?.Value; - var bbabs = Box3d.Invalid; - var bbloc = Box3f.Invalid; - - for (var i = 0; i < oldPs.Length; i++) + var pis = (object?)null; + foreach (var c in subnodes) { - var pabs = (V3d)oldPs[i] + root.Center; - byte? oldK = oldKs != null ? oldKs[i] : null; - int? oldPi = piis != null ? PartIndexUtils.Get(root.PartIndices, i) : null; - var atts = new PointDeleteAttributes(oldK, oldPi); - if (!isPositionInside(pabs, atts)) - { - ps.Add(oldPs[i]); - if (oldCs != null) cs!.Add(oldCs[i]); - if (oldNs != null) ns!.Add(oldNs[i]); - if (oldIs != null) js!.Add(oldIs[i]); - if (oldKs != null) ks!.Add(oldKs[i]); - if (piis != null) piis!.Add(i); - bbabs.ExtendBy(pabs); - bbloc.ExtendBy(oldPs[i]); - } + if (c != null) MergeExtensions.CollectEverything(c, psabs, cs, ns, js, ks, ref pis); } + Debug.Assert(psabs.Count == pointCountTree); + var psa = psabs.MapToArray((p) => (V3f)(p - root.Center)); + var kd = psa.Length < 1 ? null : psa.BuildKdTree(); - if (ps.Count == 0) return null; - - var pis = (piis != null) ? PartIndexUtils.Subset(root.PartIndices, piis) : null; - - var psa = ps.ToArray(); - var newId = Guid.NewGuid(); - var kd = psa.Length < 1 ? null : psa.BuildKdTree(); - Guid psId = Guid.NewGuid(); Guid kdId = kd != null ? Guid.NewGuid() : Guid.Empty; Guid csId = cs != null ? Guid.NewGuid() : Guid.Empty; @@ -166,21 +265,22 @@ int splitLimit Guid ksId = ks != null ? Guid.NewGuid() : Guid.Empty; Guid pisId = pis != null ? Guid.NewGuid() : Guid.Empty; + var bbabs = new Box3d(psabs); + + var newId = Guid.NewGuid(); storage.Add(psId, psa); var data = ImmutableDictionary.Empty - .Add(Durable.Octree.NodeId, newId) - .Add(Durable.Octree.Cell, root.Cell) - .Add(Durable.Octree.BoundingBoxExactGlobal, bbabs) - .Add(Durable.Octree.BoundingBoxExactLocal, bbloc) - .Add(Durable.Octree.PositionsLocal3fReference, psId) - .Add(Durable.Octree.PointCountCell, ps.Count) - .Add(Durable.Octree.PointCountTreeLeafs, (long)ps.Count) - .Add(Durable.Octree.MaxTreeDepth, 0) - .Add(Durable.Octree.MinTreeDepth, 0) - ; - - + .Add(Durable.Octree.NodeId, newId) + .Add(Durable.Octree.Cell, root.Cell) + .Add(Durable.Octree.BoundingBoxExactGlobal, bbabs) + .Add(Durable.Octree.BoundingBoxExactLocal, (Box3f)(bbabs - root.Center)) + .Add(Durable.Octree.PositionsLocal3fReference, psId) + .Add(Durable.Octree.PointCountCell, (int)pointCountTree) + .Add(Durable.Octree.PointCountTreeLeafs, pointCountTree) + .Add(Durable.Octree.MaxTreeDepth, 0) + .Add(Durable.Octree.MinTreeDepth, 0) + ; if (kd != null) { storage.Add(kdId, kd.Data); @@ -219,7 +319,7 @@ int splitLimit byte[] => Durable.Octree.PerPointPartIndex1bReference, short[] => Durable.Octree.PerPointPartIndex1sReference, int[] => Durable.Octree.PerPointPartIndex1iReference, - _ => throw new Exception("[Delete] Unknown type. Invariant 95811F1A-5FFF-4EED-8C31-8C267CAB85A6.") + _ => throw new Exception("[Delete] Unknown type. Invariant 97DF0E9E-EE9C-4BC7-9D0C-0C0F262122B0.") }; data = data.Add(def, pisId); } @@ -235,236 +335,134 @@ int splitLimit } else { - var subnodes = root.Subnodes.Map((r) => r?.Value?.Delete(isNodeFullyInside, isNodeFullyOutside, isPositionInside, storage, ct, splitLimit)); + var bbabs = new Box3d(subnodes.Map(n => n != null ? n.BoundingBoxExactGlobal : Box3d.Invalid)); + var subids = subnodes.Map(n => n != null ? n.Id : Guid.Empty); + + var maxDepth = subnodes.Max(n => n != null ? n.MaxTreeDepth + 1 : 0); + var minDepth = subnodes.Min(n => n != null ? n.MinTreeDepth + 1 : 0); + + + var octreeSplitLimit = splitLimit; + var fractions = LodExtensions.ComputeLodFractions(subnodes); + var aggregateCount = Math.Min(octreeSplitLimit, subnodes.Sum(x => x?.PointCountCell) ?? 0); + var counts = LodExtensions.ComputeLodCounts(aggregateCount, fractions); + + // generate LoD data ... + var needsCs = subnodes.Any(x => x != null && x.HasColors); + var needsNs = subnodes.Any(x => x != null && x.HasNormals); + var needsIs = subnodes.Any(x => x != null && x.HasIntensities); + var needsKs = subnodes.Any(x => x != null && x.HasClassifications); + var needsPis = subnodes.Any(x => x != null && x.HasPartIndices); + + var subcenters = subnodes.Map(x => x?.Center); + + var lodPs = LodExtensions.AggregateSubPositions(counts, aggregateCount, root.Center, subcenters, subnodes.Map(x => x?.Positions?.Value)); + var lodCs = needsCs ? LodExtensions.AggregateSubArrays(counts, aggregateCount, subnodes.Map(x => x?.Colors?.Value)) : null; + var lodNs = needsNs ? LodExtensions.AggregateSubArrays(counts, aggregateCount, subnodes.Map(x => x?.Normals?.Value)) : null; + var lodIs = needsIs ? LodExtensions.AggregateSubArrays(counts, aggregateCount, subnodes.Map(x => x?.Intensities?.Value)) : null; + var lodKs = needsKs ? LodExtensions.AggregateSubArrays(counts, aggregateCount, subnodes.Map(x => x?.Classifications?.Value)) : null; + var lodKd = lodPs.Length < 1 ? null : lodPs.BuildKdTree(); + var (lodPis,lodPiRange) = needsPis ? LodExtensions.AggregateSubPartIndices(counts, aggregateCount, subnodes.Map(x => x?.PartIndices)) : (null,null); + + Guid psId = Guid.NewGuid(); + Guid kdId = lodKd != null ? Guid.NewGuid() : Guid.Empty; + Guid csId = lodCs != null ? Guid.NewGuid() : Guid.Empty; + Guid nsId = lodNs != null ? Guid.NewGuid() : Guid.Empty; + Guid isId = lodIs != null ? Guid.NewGuid() : Guid.Empty; + Guid ksId = lodKs != null ? Guid.NewGuid() : Guid.Empty; + Guid pisId = lodPis != null ? Guid.NewGuid() : Guid.Empty; + - var pointCountTree = subnodes.Sum((n) => n != null ? n.PointCountTree : 0); - if (pointCountTree == 0) + var newId = Guid.NewGuid(); + storage.Add(psId, lodPs); + + var bbloc = new Box3f(lodPs); + + // be inner node + var data = ImmutableDictionary.Empty + .Add(Durable.Octree.SubnodesGuids, subids) + .Add(Durable.Octree.NodeId, newId) + .Add(Durable.Octree.Cell, root.Cell) + .Add(Durable.Octree.BoundingBoxExactGlobal, bbabs) + .Add(Durable.Octree.BoundingBoxExactLocal, bbloc) + .Add(Durable.Octree.PositionsLocal3fReference, psId) + .Add(Durable.Octree.PointCountCell, lodPs.Length) + .Add(Durable.Octree.PointCountTreeLeafs, pointCountTree) + .Add(Durable.Octree.MaxTreeDepth, maxDepth) + .Add(Durable.Octree.MinTreeDepth, minDepth) + ; + + + if (lodKd != null) { - return null; + storage.Add(kdId, lodKd.Data); + data = data.Add(Durable.Octree.PointRkdTreeFDataReference, kdId); } - else if (pointCountTree <= splitLimit) + if (lodCs != null) { - var psabs = new List(); - var cs = root.HasColors ? new List() : null; - var ns = root.HasNormals ? new List() : null; - var js = root.HasIntensities ? new List() : null; - var ks = root.HasClassifications ? new List() : null; - var pis = (object?)null; - foreach (var c in subnodes) - { - if (c != null) MergeExtensions.CollectEverything(c, psabs, cs, ns, js, ks, ref pis); - } - Debug.Assert(psabs.Count == pointCountTree); - var psa = psabs.MapToArray((p) => (V3f)(p - root.Center)); - var kd = psa.Length < 1 ? null : psa.BuildKdTree(); - - - Guid psId = Guid.NewGuid(); - Guid kdId = kd != null ? Guid.NewGuid() : Guid.Empty; - Guid csId = cs != null ? Guid.NewGuid() : Guid.Empty; - Guid nsId = ns != null ? Guid.NewGuid() : Guid.Empty; - Guid isId = js != null ? Guid.NewGuid() : Guid.Empty; - Guid ksId = ks != null ? Guid.NewGuid() : Guid.Empty; - Guid pisId = pis != null ? Guid.NewGuid() : Guid.Empty; - - var bbabs = new Box3d(psabs); - - var newId = Guid.NewGuid(); - storage.Add(psId, psa); - - var data = ImmutableDictionary.Empty - .Add(Durable.Octree.NodeId, newId) - .Add(Durable.Octree.Cell, root.Cell) - .Add(Durable.Octree.BoundingBoxExactGlobal, bbabs) - .Add(Durable.Octree.BoundingBoxExactLocal, (Box3f)(bbabs - root.Center)) - .Add(Durable.Octree.PositionsLocal3fReference, psId) - .Add(Durable.Octree.PointCountCell, (int)pointCountTree) - .Add(Durable.Octree.PointCountTreeLeafs, pointCountTree) - .Add(Durable.Octree.MaxTreeDepth, 0) - .Add(Durable.Octree.MinTreeDepth, 0) - ; - if (kd != null) - { - storage.Add(kdId, kd.Data); - data = data.Add(Durable.Octree.PointRkdTreeFDataReference, kdId); - } - if (cs != null) - { - storage.Add(csId, cs.ToArray()); - data = data.Add(Durable.Octree.Colors4bReference, csId); - } - if (ns != null) - { - storage.Add(nsId, ns.ToArray()); - data = data.Add(Durable.Octree.Normals3fReference, nsId); - } - if (js != null) - { - storage.Add(isId, js.ToArray()); - data = data.Add(Durable.Octree.Intensities1iReference, isId); - } - if (ks != null) - { - storage.Add(ksId, ks.ToArray()); - data = data.Add(Durable.Octree.Classifications1bReference, ksId); - } - if (pis != null) - { - var piRange = PartIndexUtils.GetRange(pis); - data = data.Add(Durable.Octree.PartIndexRange, piRange!); - - if (pis is Array xs) - { - storage.Add(pisId, xs); - var def = xs switch - { - byte[] => Durable.Octree.PerPointPartIndex1bReference, - short[] => Durable.Octree.PerPointPartIndex1sReference, - int[] => Durable.Octree.PerPointPartIndex1iReference, - _ => throw new Exception("[Delete] Unknown type. Invariant 97DF0E9E-EE9C-4BC7-9D0C-0C0F262122B0.") - }; - data = data.Add(def, pisId); - } - else - { - data = data - .Add(PartIndexUtils.GetDurableDefForPartIndices(pis), pis) - ; - } - } - - return new PointSetNode(data, storage, writeToStore: true); + storage.Add(csId, lodCs); + data = data.Add(Durable.Octree.Colors4bReference, csId); } - else + if (lodNs != null) { - var bbabs = new Box3d(subnodes.Map(n => n != null ? n.BoundingBoxExactGlobal : Box3d.Invalid)); - var subids = subnodes.Map(n => n != null ? n.Id : Guid.Empty); - - var maxDepth = subnodes.Max(n => n != null ? n.MaxTreeDepth + 1 : 0); - var minDepth = subnodes.Min(n => n != null ? n.MinTreeDepth + 1 : 0); - - - var octreeSplitLimit = splitLimit; - var fractions = LodExtensions.ComputeLodFractions(subnodes); - var aggregateCount = Math.Min(octreeSplitLimit, subnodes.Sum(x => x?.PointCountCell) ?? 0); - var counts = LodExtensions.ComputeLodCounts(aggregateCount, fractions); - - // generate LoD data ... - var needsCs = subnodes.Any(x => x != null && x.HasColors); - var needsNs = subnodes.Any(x => x != null && x.HasNormals); - var needsIs = subnodes.Any(x => x != null && x.HasIntensities); - var needsKs = subnodes.Any(x => x != null && x.HasClassifications); - var needsPis = subnodes.Any(x => x != null && x.HasPartIndices); - - var subcenters = subnodes.Map(x => x?.Center); - - var lodPs = LodExtensions.AggregateSubPositions(counts, aggregateCount, root.Center, subcenters, subnodes.Map(x => x?.Positions?.Value)); - var lodCs = needsCs ? LodExtensions.AggregateSubArrays(counts, aggregateCount, subnodes.Map(x => x?.Colors?.Value)) : null; - var lodNs = needsNs ? LodExtensions.AggregateSubArrays(counts, aggregateCount, subnodes.Map(x => x?.Normals?.Value)) : null; - var lodIs = needsIs ? LodExtensions.AggregateSubArrays(counts, aggregateCount, subnodes.Map(x => x?.Intensities?.Value)) : null; - var lodKs = needsKs ? LodExtensions.AggregateSubArrays(counts, aggregateCount, subnodes.Map(x => x?.Classifications?.Value)) : null; - var lodKd = lodPs.Length < 1 ? null : lodPs.BuildKdTree(); - var (lodPis,lodPiRange) = needsPis ? LodExtensions.AggregateSubPartIndices(counts, aggregateCount, subnodes.Map(x => x?.PartIndices)) : (null,null); - - Guid psId = Guid.NewGuid(); - Guid kdId = lodKd != null ? Guid.NewGuid() : Guid.Empty; - Guid csId = lodCs != null ? Guid.NewGuid() : Guid.Empty; - Guid nsId = lodNs != null ? Guid.NewGuid() : Guid.Empty; - Guid isId = lodIs != null ? Guid.NewGuid() : Guid.Empty; - Guid ksId = lodKs != null ? Guid.NewGuid() : Guid.Empty; - Guid pisId = lodPis != null ? Guid.NewGuid() : Guid.Empty; - - - var newId = Guid.NewGuid(); - storage.Add(psId, lodPs); - - var bbloc = new Box3f(lodPs); - - // be inner node - var data = ImmutableDictionary.Empty - .Add(Durable.Octree.SubnodesGuids, subids) - .Add(Durable.Octree.NodeId, newId) - .Add(Durable.Octree.Cell, root.Cell) - .Add(Durable.Octree.BoundingBoxExactGlobal, bbabs) - .Add(Durable.Octree.BoundingBoxExactLocal, bbloc) - .Add(Durable.Octree.PositionsLocal3fReference, psId) - .Add(Durable.Octree.PointCountCell, lodPs.Length) - .Add(Durable.Octree.PointCountTreeLeafs, pointCountTree) - .Add(Durable.Octree.MaxTreeDepth, maxDepth) - .Add(Durable.Octree.MinTreeDepth, minDepth) - ; - - - if (lodKd != null) - { - storage.Add(kdId, lodKd.Data); - data = data.Add(Durable.Octree.PointRkdTreeFDataReference, kdId); - } - if (lodCs != null) - { - storage.Add(csId, lodCs); - data = data.Add(Durable.Octree.Colors4bReference, csId); - } - if (lodNs != null) - { - storage.Add(nsId, lodNs); - data = data.Add(Durable.Octree.Normals3fReference, nsId); - } - if (lodIs != null) - { - storage.Add(isId, lodIs); - data = data.Add(Durable.Octree.Intensities1iReference, isId); - } - if (lodKs != null) + storage.Add(nsId, lodNs); + data = data.Add(Durable.Octree.Normals3fReference, nsId); + } + if (lodIs != null) + { + storage.Add(isId, lodIs); + data = data.Add(Durable.Octree.Intensities1iReference, isId); + } + if (lodKs != null) + { + storage.Add(ksId, lodKs); + data = data.Add(Durable.Octree.Classifications1bReference, ksId); + } + if (lodPis != null) + { + data = data.Add(Durable.Octree.PartIndexRange, lodPiRange!); + + if (lodPis is Array xs) { - storage.Add(ksId, lodKs); - data = data.Add(Durable.Octree.Classifications1bReference, ksId); + storage.Add(pisId, xs); + var def = xs switch + { + byte[] => Durable.Octree.PerPointPartIndex1bReference, + short[] => Durable.Octree.PerPointPartIndex1sReference, + int[] => Durable.Octree.PerPointPartIndex1iReference, + _ => throw new Exception("[Delete] Unknown type. Invariant CC407F87-5969-4989-9161-B809FDA46840.") + }; + data = data.Add(def, pisId); } - if (lodPis != null) + else { - data = data.Add(Durable.Octree.PartIndexRange, lodPiRange!); - - if (lodPis is Array xs) - { - storage.Add(pisId, xs); - var def = xs switch - { - byte[] => Durable.Octree.PerPointPartIndex1bReference, - short[] => Durable.Octree.PerPointPartIndex1sReference, - int[] => Durable.Octree.PerPointPartIndex1iReference, - _ => throw new Exception("[Delete] Unknown type. Invariant CC407F87-5969-4989-9161-B809FDA46840.") - }; - data = data.Add(def, pisId); - } - else - { - data = data - .Add(PartIndexUtils.GetDurableDefForPartIndices(lodPis), lodPis) - ; - } + data = data + .Add(PartIndexUtils.GetDurableDefForPartIndices(lodPis), lodPis) + ; } - - return new PointSetNode(data, storage, writeToStore: true); } - } // if (root.IsLeaf) - } // Delete - public static IPointCloudNode? Delete(this IPointCloudNode? root, - Func isNodeFullyInside, - Func isNodeFullyOutside, - Func isPositionInside, - Storage storage, CancellationToken ct, - int splitLimit - ) - { - return root?.Delete( - isNodeFullyInside, - isNodeFullyOutside, - (V3d p, PointDeleteAttributes _) => isPositionInside(p), - storage, - ct, - splitLimit - ); - } // Delete - - } // DeleteExtensions -} // namespace + + return new PointSetNode(data, storage, writeToStore: true); + } + } // if (root.IsLeaf) + } // Delete + public static IPointCloudNode? Delete(this IPointCloudNode? root, + Func isNodeFullyInside, + Func isNodeFullyOutside, + Func isPositionInside, + Storage storage, CancellationToken ct, + int splitLimit + ) + { + return root?.Delete( + isNodeFullyInside, + isNodeFullyOutside, + (V3d p, PointDeleteAttributes _) => isPositionInside(p), + storage, + ct, + splitLimit + ); + } // Delete + +} // DeleteExtensions +// namespace diff --git a/src/Aardvark.Geometry.PointSet/Octrees/Export.cs b/src/Aardvark.Geometry.PointSet/Octrees/Export.cs index f27cd7d3..127a1f1d 100644 --- a/src/Aardvark.Geometry.PointSet/Octrees/Export.cs +++ b/src/Aardvark.Geometry.PointSet/Octrees/Export.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -18,196 +18,192 @@ You should have received a copy of the GNU Affero General Public License using System.Collections.Generic; using System.Threading; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static class ExportExtensions { /// /// - public static class ExportExtensions + public class ExportPointSetInfo( + long pointCountTree, + long processedLeafPointCount = 0L + ) { /// + /// Number of points in exported tree (sum of leaf points). /// - public class ExportPointSetInfo - { - /// - /// Number of points in exported tree (sum of leaf points). - /// - public readonly long PointCountTree; - - /// - /// Number of leaf points already processed. - /// - public readonly long ProcessedLeafPointCount; + public readonly long PointCountTree = pointCountTree; - public ExportPointSetInfo(long pointCountTree, long processedLeafPointCount = 0L) - { - PointCountTree = pointCountTree; - ProcessedLeafPointCount = processedLeafPointCount; - } - - /// - /// Progress [0,1]. - /// - public double Progress => (double)ProcessedLeafPointCount / (double)PointCountTree; + /// + /// Number of leaf points already processed. + /// + public readonly long ProcessedLeafPointCount = processedLeafPointCount; - /// - /// Returns new ExportPointSetInfo with ProcessedLeafPointCount incremented by x. - /// - public ExportPointSetInfo AddProcessedLeafPoints(long x) - => new(PointCountTree, ProcessedLeafPointCount + x); - } + /// + /// Progress [0,1]. + /// + public double Progress => (double)ProcessedLeafPointCount / (double)PointCountTree; /// - /// Exports complete pointset (metadata, nodes, referenced blobs) to another store. + /// Returns new ExportPointSetInfo with ProcessedLeafPointCount incremented by x. /// - public static ExportPointSetInfo ExportPointSet( - this Storage self, - string pointSetId, - Storage exportStorage, - Action onProgress, - bool verbose, CancellationToken ct - ) - { - PointSet? pointSet = null; + public ExportPointSetInfo AddProcessedLeafPoints(long x) + => new(PointCountTree, ProcessedLeafPointCount + x); + } - try - { - pointSet = self.GetPointSet(pointSetId); - if (pointSet == null) - { - Report.Warn($"No PointSet with id '{pointSetId}' in store. Trying to load node with this id."); - } - } - catch + /// + /// Exports complete pointset (metadata, nodes, referenced blobs) to another store. + /// + public static ExportPointSetInfo ExportPointSet( + this Storage self, + string pointSetId, + Storage exportStorage, + Action onProgress, + bool verbose, CancellationToken ct + ) + { + PointSet? pointSet = null; + + try + { + pointSet = self.GetPointSet(pointSetId); + if (pointSet == null) { - Report.Warn($"Entry with id '{pointSetId}' is not a PointSet. Trying to load node with this id."); + Report.Warn($"No PointSet with id '{pointSetId}' in store. Trying to load node with this id."); } + } + catch + { + Report.Warn($"Entry with id '{pointSetId}' is not a PointSet. Trying to load node with this id."); + } - if (pointSet == null) + if (pointSet == null) + { + if (self.TryGetPointCloudNode(pointSetId, out var root)) { - if (self.TryGetPointCloudNode(pointSetId, out var root)) - { - var ersatzPointSetKey = Guid.NewGuid().ToString(); - Report.Warn($"Created PointSet with key '{ersatzPointSetKey}'."); - var ersatzPointSet = new PointSet(self, ersatzPointSetKey, root!, root!.PointCountCell); - self.Add(ersatzPointSetKey, ersatzPointSet); + var ersatzPointSetKey = Guid.NewGuid().ToString(); + Report.Warn($"Created PointSet with key '{ersatzPointSetKey}'."); + var ersatzPointSet = new PointSet(self, ersatzPointSetKey, root!, root!.PointCountCell); + self.Add(ersatzPointSetKey, ersatzPointSet); - return ExportPointSet(self, ersatzPointSet, exportStorage, onProgress, verbose, ct); - } - else - { - throw new Exception($"No node with id '{pointSetId}' in store. Giving up. Invariant 48028b00-4538-4169-a2fc-ca009d56e012."); - } + return ExportPointSet(self, ersatzPointSet, exportStorage, onProgress, verbose, ct); } else { - return ExportPointSet(self, pointSet, exportStorage, onProgress, verbose, ct); + throw new Exception($"No node with id '{pointSetId}' in store. Giving up. Invariant 48028b00-4538-4169-a2fc-ca009d56e012."); } } - - /// - /// Exports complete pointset (metadata, nodes, referenced blobs) to another store. - /// - private static ExportPointSetInfo ExportPointSet( - this Storage self, - PointSet pointset, - Storage exportStorage, - Action onProgress, - bool verbose, CancellationToken ct - ) + else { - ct.ThrowIfCancellationRequested(); - onProgress ??= _ => { }; + return ExportPointSet(self, pointSet, exportStorage, onProgress, verbose, ct); + } + } - var info = new ExportPointSetInfo(pointset.Root.Value.PointCountTree); + /// + /// Exports complete pointset (metadata, nodes, referenced blobs) to another store. + /// + private static ExportPointSetInfo ExportPointSet( + this Storage self, + PointSet pointset, + Storage exportStorage, + Action onProgress, + bool verbose, CancellationToken ct + ) + { + ct.ThrowIfCancellationRequested(); + onProgress ??= _ => { }; - var pointSetId = pointset.Id; - var root = pointset.Root.Value; - var totalNodeCount = root.CountNodes(outOfCore: true); - if (verbose) Report.Line($"total node count = {totalNodeCount:N0}"); + var info = new ExportPointSetInfo(pointset.Root.Value.PointCountTree); - // export pointset metainfo - exportStorage.Add(pointSetId, pointset.Encode()); - // Report.Line($"exported {pointSetId} (pointset metainfo, json)"); + var pointSetId = pointset.Id; + var root = pointset.Root.Value; + var totalNodeCount = root.CountNodes(outOfCore: true); + if (verbose) Report.Line($"total node count = {totalNodeCount:N0}"); - // export octree (recursively) - var exportedNodeCount = 0L; - ExportNode(root.Id); - if (verbose) Console.Write("\r"); - return info; + // export pointset metainfo + exportStorage.Add(pointSetId, pointset.Encode()); + // Report.Line($"exported {pointSetId} (pointset metainfo, json)"); - void ExportNode(Guid key) - { - ct.ThrowIfCancellationRequested(); + // export octree (recursively) + var exportedNodeCount = 0L; + ExportNode(root.Id); + if (verbose) Console.Write("\r"); + return info; - // missing subnode (null) is encoded as Guid.Empty - if (key == Guid.Empty) return; + void ExportNode(Guid key) + { + ct.ThrowIfCancellationRequested(); - // try to load node - Durable.Def? def = Durable.Octree.Node; - object? raw = null; - try - { - (def, raw) = self.GetDurable(key); - } - catch - { - var n = self.GetPointCloudNode(key)!; - raw = n.Properties; - } + // missing subnode (null) is encoded as Guid.Empty + if (key == Guid.Empty) return; - if (raw is not IReadOnlyDictionary nodeProps || def == null) return; + // try to load node + Durable.Def? def = Durable.Octree.Node; + object? raw = null; + try + { + (def, raw) = self.GetDurable(key); + } + catch + { + var n = self.GetPointCloudNode(key)!; + raw = n.Properties; + } - exportStorage.Add(key, def, nodeProps, false); - //Report.Line($"exported {key} (node)"); + if (raw is not IReadOnlyDictionary nodeProps || def == null) return; - // references - var rs = GetReferences(nodeProps); - foreach (var kv in rs) - { - var k = (Guid)kv.Value; - var buffer = self.GetByteArray(k)!; - exportStorage.Add(k, buffer); - //Report.Line($"exported {k} (reference)"); - } + exportStorage.Add(key, def, nodeProps, false); + //Report.Line($"exported {key} (node)"); + + // references + var rs = GetReferences(nodeProps); + foreach (var kv in rs) + { + var k = (Guid)kv.Value; + var buffer = self.GetByteArray(k)!; + exportStorage.Add(k, buffer); + //Report.Line($"exported {k} (reference)"); + } - exportedNodeCount++; - if (verbose) Console.Write($"\r{exportedNodeCount}/{totalNodeCount}"); + exportedNodeCount++; + if (verbose) Console.Write($"\r{exportedNodeCount}/{totalNodeCount}"); - // children - nodeProps.TryGetValue(Durable.Octree.SubnodesGuids, out var subnodeGuids); - if (subnodeGuids != null) + // children + nodeProps.TryGetValue(Durable.Octree.SubnodesGuids, out var subnodeGuids); + if (subnodeGuids != null) + { + foreach (var x in (Guid[])subnodeGuids) ExportNode(x); + } + else + { + if (nodeProps.TryGetValue(Durable.Octree.PointCountCell, out var pointCountCell)) { - foreach (var x in (Guid[])subnodeGuids) ExportNode(x); + info = info.AddProcessedLeafPoints((int)pointCountCell); } else { - if (nodeProps.TryGetValue(Durable.Octree.PointCountCell, out var pointCountCell)) - { - info = info.AddProcessedLeafPoints((int)pointCountCell); - } - else - { - Report.Warn("Invariant 2f7bb751-e6d4-4d4a-98a3-eabd6fd9b156."); - } - - onProgress(info); + Report.Warn("Invariant 2f7bb751-e6d4-4d4a-98a3-eabd6fd9b156."); } + + onProgress(info); } + } - IDictionary GetReferences(IReadOnlyDictionary node) + IDictionary GetReferences(IReadOnlyDictionary node) + { + var rs = new Dictionary(); + foreach (var kv in node) { - var rs = new Dictionary(); - foreach (var kv in node) - { - if (kv.Key == Durable.Octree.NodeId) continue; + if (kv.Key == Durable.Octree.NodeId) continue; - if (kv.Key.Type == Durable.Primitives.GuidDef.Id) - { - rs[kv.Key] = kv.Value; - } + if (kv.Key.Type == Durable.Primitives.GuidDef.Id) + { + rs[kv.Key] = kv.Value; } - return rs; } + return rs; } } } diff --git a/src/Aardvark.Geometry.PointSet/Octrees/IPointCloudNodeExtensions.cs b/src/Aardvark.Geometry.PointSet/Octrees/IPointCloudNodeExtensions.cs index 0a6b2a3a..adeebd64 100644 --- a/src/Aardvark.Geometry.PointSet/Octrees/IPointCloudNodeExtensions.cs +++ b/src/Aardvark.Geometry.PointSet/Octrees/IPointCloudNodeExtensions.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -20,433 +20,402 @@ You should have received a copy of the GNU Affero General Public License using System.Runtime.CompilerServices; using System.Threading; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static class IPointCloudNodeExtensions { + #region enumerate nodes + /// + /// Enumerates all nodes depth-first. /// - public static class IPointCloudNodeExtensions + public static IEnumerable EnumerateNodes(this IPointCloudNode root) { - #region enumerate nodes - - /// - /// Enumerates all nodes depth-first. - /// - public static IEnumerable EnumerateNodes(this IPointCloudNode root) + if (root.Subnodes != null) { - if (root.Subnodes != null) + foreach (var subnode in root.Subnodes) { - foreach (var subnode in root.Subnodes) - { - if (subnode == null) continue; - foreach (var n in EnumerateNodes(subnode.Value)) yield return n; - } + if (subnode == null) continue; + foreach (var n in EnumerateNodes(subnode.Value)) yield return n; } - - yield return root; } - #endregion + yield return root; + } - #region ForEach (optionally traversing out-of-core nodes) + #endregion - /// - /// Calls action for each node in this tree. - /// - public static void ForEachNode( - this IPointCloudNode self, bool outOfCore, Action action) - { - action(self); + #region ForEach (optionally traversing out-of-core nodes) - if (self.Subnodes == null) return; + /// + /// Calls action for each node in this tree. + /// + public static void ForEachNode( + this IPointCloudNode self, bool outOfCore, Action action) + { + action(self); - if (outOfCore) + if (self.Subnodes == null) return; + + if (outOfCore) + { + for (var i = 0; i < 8; i++) { - for (var i = 0; i < 8; i++) - { - self.Subnodes[i]?.Value.ForEachNode(outOfCore, action); - } + self.Subnodes[i]?.Value.ForEachNode(outOfCore, action); } - else + } + else + { + for (var i = 0; i < 8; i++) { - for (var i = 0; i < 8; i++) + var n = self.Subnodes[i]; + if (n != null) { - var n = self.Subnodes[i]; - if (n != null) - { - if (n.TryGetFromCache(out var node)) node.ForEachNode(outOfCore, action); - } + if (n.TryGetFromCache(out var node)) node.ForEachNode(outOfCore, action); } } } + } - /// - /// - public static IEnumerable ForEachNode( - this IPointCloudNode self, int minCellExponent = int.MinValue - ) - { - if (self == null) yield break; + /// + /// + public static IEnumerable ForEachNode( + this IPointCloudNode self, int minCellExponent = int.MinValue + ) + { + if (self == null) yield break; - if (self.Cell.Exponent < minCellExponent) yield break; + if (self.Cell.Exponent < minCellExponent) yield break; - yield return self; + yield return self; - if (self.Subnodes != null) + if (self.Subnodes != null) + { + for (var i = 0; i < 8; i++) { - for (var i = 0; i < 8; i++) - { - var n = self.Subnodes[i]; - if (n == null) continue; - foreach (var x in ForEachNode(n.Value, minCellExponent)) yield return x; - } + var n = self.Subnodes[i]; + if (n == null) continue; + foreach (var x in ForEachNode(n.Value, minCellExponent)) yield return x; } } + } - /// - /// Calls action for each (node, fullyInside) in this pointset, that is intersecting the given hull. - /// - public static void ForEachIntersectingNode( - this IPointCloudNode self, bool outOfCore, Hull3d hull, bool doNotTraverseSubnodesWhenFullyInside, - Action action, CancellationToken ct = default - ) - { - ct.ThrowIfCancellationRequested(); + /// + /// Calls action for each (node, fullyInside) in this pointset, that is intersecting the given hull. + /// + public static void ForEachIntersectingNode( + this IPointCloudNode self, bool outOfCore, Hull3d hull, bool doNotTraverseSubnodesWhenFullyInside, + Action action, CancellationToken ct = default + ) + { + ct.ThrowIfCancellationRequested(); - for (var i = 0; i < hull.PlaneCount; i++) - { - if (!self.IntersectsNegativeHalfSpace(hull.PlaneArray[i])) return; - } + for (var i = 0; i < hull.PlaneCount; i++) + { + if (!self.IntersectsNegativeHalfSpace(hull.PlaneArray[i])) return; + } - bool fullyInside = true; - for (var i = 0; i < hull.PlaneCount; i++) + bool fullyInside = true; + for (var i = 0; i < hull.PlaneCount; i++) + { + if (!self.InsideNegativeHalfSpace(hull.PlaneArray[i])) { - if (!self.InsideNegativeHalfSpace(hull.PlaneArray[i])) - { - fullyInside = false; - break; - } + fullyInside = false; + break; } + } - action(self, fullyInside); + action(self, fullyInside); - if (fullyInside && doNotTraverseSubnodesWhenFullyInside) return; + if (fullyInside && doNotTraverseSubnodesWhenFullyInside) return; - if (self.Subnodes == null) return; + if (self.Subnodes == null) return; - if (outOfCore) + if (outOfCore) + { + for (var i = 0; i < 8; i++) { - for (var i = 0; i < 8; i++) - { - var n = self.Subnodes[i]; - n?.Value.ForEachIntersectingNode(outOfCore, hull, doNotTraverseSubnodesWhenFullyInside, action, ct); - } + var n = self.Subnodes[i]; + n?.Value.ForEachIntersectingNode(outOfCore, hull, doNotTraverseSubnodesWhenFullyInside, action, ct); } - else + } + else + { + for (var i = 0; i < 8; i++) { - for (var i = 0; i < 8; i++) + var n = self.Subnodes[i]; + if (n != null) { - var n = self.Subnodes[i]; - if (n != null) + if (n.TryGetFromCache(out var node)) { - if (n.TryGetFromCache(out var node)) - { - node.ForEachIntersectingNode(outOfCore, hull, doNotTraverseSubnodesWhenFullyInside, action, ct); - } + node.ForEachIntersectingNode(outOfCore, hull, doNotTraverseSubnodesWhenFullyInside, action, ct); } } } } + } - /// - /// Calls action for each (node, fullyInside) in this tree that is intersecting the given hull. - /// - public static IEnumerable ForEachNodeIntersecting( - this IPointCloudNode self, - Hull3d hull, bool doNotTraverseSubnodesWhenFullyInside, int minCellExponent = int.MinValue - ) - { - if (self == null) yield break; + /// + /// Calls action for each (node, fullyInside) in this tree that is intersecting the given hull. + /// + public static IEnumerable ForEachNodeIntersecting( + this IPointCloudNode self, + Hull3d hull, bool doNotTraverseSubnodesWhenFullyInside, int minCellExponent = int.MinValue + ) + { + if (self == null) yield break; - if (self.Cell.Exponent < minCellExponent) yield break; + if (self.Cell.Exponent < minCellExponent) yield break; - for (var i = 0; i < hull.PlaneCount; i++) - { - if (!self.IntersectsNegativeHalfSpace(hull.PlaneArray[i])) yield break; - } + for (var i = 0; i < hull.PlaneCount; i++) + { + if (!self.IntersectsNegativeHalfSpace(hull.PlaneArray[i])) yield break; + } - bool fullyInside = true; - for (var i = 0; i < hull.PlaneCount; i++) + bool fullyInside = true; + for (var i = 0; i < hull.PlaneCount; i++) + { + if (!self.InsideNegativeHalfSpace(hull.PlaneArray[i])) { - if (!self.InsideNegativeHalfSpace(hull.PlaneArray[i])) - { - fullyInside = false; - break; - } + fullyInside = false; + break; } + } - yield return new CellQueryResult(self, fullyInside); + yield return new CellQueryResult(self, fullyInside); - if (fullyInside && doNotTraverseSubnodesWhenFullyInside) yield break; + if (fullyInside && doNotTraverseSubnodesWhenFullyInside) yield break; - if (self.Subnodes == null) yield break; - for (var i = 0; i < 8; i++) - { - var n = self.Subnodes[i]; - if (n == null) continue; - var xs = ForEachNodeIntersecting(n.Value, hull, doNotTraverseSubnodesWhenFullyInside, minCellExponent); - foreach (var x in xs) yield return x; - } + if (self.Subnodes == null) yield break; + for (var i = 0; i < 8; i++) + { + var n = self.Subnodes[i]; + if (n == null) continue; + var xs = ForEachNodeIntersecting(n.Value, hull, doNotTraverseSubnodesWhenFullyInside, minCellExponent); + foreach (var x in xs) yield return x; } + } - #endregion + #endregion - #region Intersections, inside/outside, ... + #region Intersections, inside/outside, ... - /// - /// Index of subnode for given point. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetSubIndex(this IPointCloudNode self, in V3d p) - { - var i = 0; - var c = self.Center; - if (p.X > c.X) i = 1; - if (p.Y > c.Y) i += 2; - if (p.Z > c.Z) i += 4; - return i; - } + /// + /// Index of subnode for given point. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetSubIndex(this IPointCloudNode self, in V3d p) + { + var i = 0; + var c = self.Center; + if (p.X > c.X) i = 1; + if (p.Y > c.Y) i += 2; + if (p.Z > c.Z) i += 4; + return i; + } - /// - /// Returns true if this node intersects the space within a given distance to a plane. - /// - public static bool Intersects(this IPointCloudNode self, Plane3d plane, double distance) - => self.BoundingBoxExactGlobal.Intersects(plane, distance); + /// + /// Returns true if this node intersects the space within a given distance to a plane. + /// + public static bool Intersects(this IPointCloudNode self, Plane3d plane, double distance) + => self.BoundingBoxExactGlobal.Intersects(plane, distance); - /// - /// Returns true if this node intersects the positive halfspace defined by given plane. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IntersectsPositiveHalfSpace(this IPointCloudNode self, in Plane3d plane) + /// + /// Returns true if this node intersects the positive halfspace defined by given plane. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IntersectsPositiveHalfSpace(this IPointCloudNode self, in Plane3d plane) + { + var corners = self.BoundingBoxExactGlobal.ComputeCorners(); + for (var i = 0; i < 8; i++) { - var corners = self.BoundingBoxExactGlobal.ComputeCorners(); - for (var i = 0; i < 8; i++) - { - if (plane.Height(corners[i]) > 0) return true; - } - return false; + if (plane.Height(corners[i]) > 0) return true; } + return false; + } - /// - /// Returns true if this node intersects the negative halfspace defined by given plane. - /// - public static bool IntersectsNegativeHalfSpace(this IPointCloudNode self, in Plane3d plane) + /// + /// Returns true if this node intersects the negative halfspace defined by given plane. + /// + public static bool IntersectsNegativeHalfSpace(this IPointCloudNode self, in Plane3d plane) + { + var corners = self.BoundingBoxExactGlobal.ComputeCorners(); + for (var i = 0; i < 8; i++) { - var corners = self.BoundingBoxExactGlobal.ComputeCorners(); - for (var i = 0; i < 8; i++) - { - if (plane.Height(corners[i]) < 0) return true; - } - return false; + if (plane.Height(corners[i]) < 0) return true; } + return false; + } - /// - /// Returns true if this node is fully inside the positive halfspace defined by given plane. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool InsidePositiveHalfSpace(this IPointCloudNode self, in Plane3d plane) - { - self.BoundingBoxExactGlobal.GetMinMaxInDirection(plane.Normal, out V3d min, out V3d _); - return plane.Height(min) > 0; - } + /// + /// Returns true if this node is fully inside the positive halfspace defined by given plane. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool InsidePositiveHalfSpace(this IPointCloudNode self, in Plane3d plane) + { + self.BoundingBoxExactGlobal.GetMinMaxInDirection(plane.Normal, out V3d min, out V3d _); + return plane.Height(min) > 0; + } - /// - /// Returns true if this node is fully inside the negative halfspace defined by given plane. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool InsideNegativeHalfSpace(this IPointCloudNode self, in Plane3d plane) - { - self.BoundingBoxExactGlobal.GetMinMaxInDirection(-plane.Normal, out V3d min, out V3d _); - return plane.Height(min) < 0; - } + /// + /// Returns true if this node is fully inside the negative halfspace defined by given plane. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool InsideNegativeHalfSpace(this IPointCloudNode self, in Plane3d plane) + { + self.BoundingBoxExactGlobal.GetMinMaxInDirection(-plane.Normal, out V3d min, out V3d _); + return plane.Height(min) < 0; + } - #endregion + #endregion - #region Counts (optionally traversing out-of-core nodes) + #region Counts (optionally traversing out-of-core nodes) - /// - /// Eagerly counts all points in octree (without using PointCountTree property). - /// - public static long CountPoints(this IPointCloudNode self) - { - if (self == null) return 0; + /// + /// Eagerly counts all points in octree (without using PointCountTree property). + /// + public static long CountPoints(this IPointCloudNode self) + { + if (self == null) return 0; - if (self.IsLeaf) - { - return self.Positions.Value.Length; - } - else - { - var count = 0L; - if (self.Subnodes != null) - { - for (var i = 0; i < 8; i++) - { - var n = self.Subnodes[i]; - if (n != null) count += CountPoints(n.Value); - } - } - return count; - } + if (self.IsLeaf) + { + return self.Positions.Value.Length; } - - /// - /// Gets minimum point count of all tree nodes (eager). - /// - public static long GetMinimumNodePointCount(this IPointCloudNode self) + else { - if (self == null) return 0; - - long min = self.PointCountCell; + var count = 0L; if (self.Subnodes != null) { for (var i = 0; i < 8; i++) { var n = self.Subnodes[i]; - if (n != null) - { - var x = GetMinimumNodePointCount(n.Value); - if (x < min) min = x; - } + if (n != null) count += CountPoints(n.Value); } } - return min; + return count; } + } - /// - /// Gets maximum point count of all tree nodes (eager). - /// - public static long GetMaximumNodePointCount(this IPointCloudNode self) - { - if (self == null) return 0; + /// + /// Gets minimum point count of all tree nodes (eager). + /// + public static long GetMinimumNodePointCount(this IPointCloudNode self) + { + if (self == null) return 0; - long max = self.PointCountCell; - if (self.Subnodes != null) + long min = self.PointCountCell; + if (self.Subnodes != null) + { + for (var i = 0; i < 8; i++) { - for (var i = 0; i < 8; i++) + var n = self.Subnodes[i]; + if (n != null) { - var n = self.Subnodes[i]; - if (n != null) - { - var x = GetMaximumNodePointCount(n.Value); - if (x > max) max = x; - } + var x = GetMinimumNodePointCount(n.Value); + if (x < min) min = x; } } - return max; } + return min; + } - /// - /// Gets average point count of all tree nodes (eager). - /// - public static double GetAverageNodePointCount(this IPointCloudNode self) - { - if (self == null) return 0; - long sum = 0, count = 0; - GetAverageNodePointCountImpl(self, ref sum, ref count); - return sum / (double)count; - } - private static void GetAverageNodePointCountImpl(this IPointCloudNode self, ref long sum, ref long count) - { - sum += self.PointCountCell; - count++; - - if (self.Subnodes == null) return; + /// + /// Gets maximum point count of all tree nodes (eager). + /// + public static long GetMaximumNodePointCount(this IPointCloudNode self) + { + if (self == null) return 0; + long max = self.PointCountCell; + if (self.Subnodes != null) + { for (var i = 0; i < 8; i++) { var n = self.Subnodes[i]; - if (n != null) GetAverageNodePointCountImpl(n.Value, ref sum, ref count); + if (n != null) + { + var x = GetMaximumNodePointCount(n.Value); + if (x > max) max = x; + } } } + return max; + } + + /// + /// Gets average point count of all tree nodes (eager). + /// + public static double GetAverageNodePointCount(this IPointCloudNode self) + { + if (self == null) return 0; + long sum = 0, count = 0; + GetAverageNodePointCountImpl(self, ref sum, ref count); + return sum / (double)count; + } + private static void GetAverageNodePointCountImpl(this IPointCloudNode self, ref long sum, ref long count) + { + sum += self.PointCountCell; + count++; + + if (self.Subnodes == null) return; + + for (var i = 0; i < 8; i++) + { + var n = self.Subnodes[i]; + if (n != null) GetAverageNodePointCountImpl(n.Value, ref sum, ref count); + } + } - /// - /// Total number of nodes. - /// - public static long CountNodes(this IPointCloudNode self, bool outOfCore) + /// + /// Total number of nodes. + /// + public static long CountNodes(this IPointCloudNode self, bool outOfCore) + { + var count = 1L; + if (self.Subnodes != null) { - var count = 1L; - if (self.Subnodes != null) + if (outOfCore && self is not FilteredNode) { - if (outOfCore && self is not FilteredNode) + long FastCount(Guid key) { - long FastCount(Guid key) - { - if (key == Guid.Empty) return 0L; + if (key == Guid.Empty) return 0L; - var acc = 1L; + var acc = 1L; - var (def, obj) = self.Storage.GetDurable(key); - if (def == Durable.Octree.Node) - { - var data = (IDictionary)obj!; - if (data.TryGetValue(Durable.Octree.SubnodesGuids, out var o)) - { - var snids = (Guid[])o; - foreach(var snid in snids) - { - if (snid == Guid.Empty) continue; - acc += FastCount(snid); - } - } - } - else + var (def, obj) = self.Storage.GetDurable(key); + if (def == Durable.Octree.Node) + { + var data = (IDictionary)obj!; + if (data.TryGetValue(Durable.Octree.SubnodesGuids, out var o)) { - var n = self.Storage.GetPointCloudNode(key); - if (n!.Subnodes != null) + var snids = (Guid[])o; + foreach(var snid in snids) { - foreach (var sn in n.Subnodes) - { - if (sn == null) continue; - acc += FastCount(sn.Value.Id); - } + if (snid == Guid.Empty) continue; + acc += FastCount(snid); } } - - return acc; } - - return FastCount(self.Id); - } - else - { - for (var i = 0; i < 8; i++) + else { - var n = self.Subnodes[i]; - if (n != null) + var n = self.Storage.GetPointCloudNode(key); + if (n!.Subnodes != null) { - if (n.TryGetFromCache(out var node)) count += node.CountNodes(outOfCore); + foreach (var sn in n.Subnodes) + { + if (sn == null) continue; + acc += FastCount(sn.Value.Id); + } } } + + return acc; } - } - return count; - } - /// - /// Number of leaf nodes. - /// - public static long CountLeafNodes(this IPointCloudNode self, bool outOfCore) - { - if (self.Subnodes == null) return 1; - - var count = 0L; - if (outOfCore) - { - for (var i = 0; i < 8; i++) - { - var n = self.Subnodes[i]; - if (n != null) count += n.Value.CountLeafNodes(outOfCore); - } + return FastCount(self.Id); } else { @@ -455,108 +424,52 @@ public static long CountLeafNodes(this IPointCloudNode self, bool outOfCore) var n = self.Subnodes[i]; if (n != null) { - if (n.TryGetFromCache(out var node)) count += node.CountLeafNodes(outOfCore); + if (n.TryGetFromCache(out var node)) count += node.CountNodes(outOfCore); } } } - return count; } + return count; + } - /// - /// Gets minimum point count of leaf nodes. - /// - public static long GetMinimumLeafPointCount(this IPointCloudNode self, bool outOfCore) + /// + /// Number of leaf nodes. + /// + public static long CountLeafNodes(this IPointCloudNode self, bool outOfCore) + { + if (self.Subnodes == null) return 1; + + var count = 0L; + if (outOfCore) { - var min = long.MaxValue; - if (self.Subnodes != null) + for (var i = 0; i < 8; i++) { - if (outOfCore) - { - for (var i = 0; i < 8; i++) - { - var n = self.Subnodes[i]; - if (n != null) - { - var x = n.Value.GetMinimumLeafPointCount(outOfCore); - if (x < min) min = x; - } - } - } - else - { - for (var i = 0; i < 8; i++) - { - var n = self.Subnodes[i]; - if (n != null) - { - if (n.TryGetFromCache(out var node)) - { - var x = node.GetMinimumLeafPointCount(outOfCore); - if (x < min) min = x; - } - } - } - } + var n = self.Subnodes[i]; + if (n != null) count += n.Value.CountLeafNodes(outOfCore); } - return min; } - - /// - /// Gets maximum point count of leaf nodes. - /// - public static long GetMaximumLeafPointCount(this IPointCloudNode self, bool outOfCore) + else { - var max = long.MinValue; - if (self.Subnodes != null) + for (var i = 0; i < 8; i++) { - if (outOfCore) - { - for (var i = 0; i < 8; i++) - { - var n = self.Subnodes[i]; - if (n != null) - { - var x = n.Value.GetMinimumLeafPointCount(outOfCore); - if (x > max) max = x; - } - } - } - else + var n = self.Subnodes[i]; + if (n != null) { - for (var i = 0; i < 8; i++) - { - var n = self.Subnodes[i]; - if (n != null) - { - if (n.TryGetFromCache(out var node)) - { - var x = node.GetMinimumLeafPointCount(outOfCore); - if (x > max) max = x; - } - } - } + if (n.TryGetFromCache(out var node)) count += node.CountLeafNodes(outOfCore); } } - return max; - } - - /// - /// Gets average point count of leaf nodes. - /// - public static double GetAverageLeafPointCount(this IPointCloudNode self, bool outOfCore) - { - return self.PointCountTree / (double)self.CountNodes(outOfCore); } + return count; + } - /// - /// Depth of tree (minimum). - /// - public static int GetMinimumTreeDepth(this IPointCloudNode self, bool outOfCore) + /// + /// Gets minimum point count of leaf nodes. + /// + public static long GetMinimumLeafPointCount(this IPointCloudNode self, bool outOfCore) + { + var min = long.MaxValue; + if (self.Subnodes != null) { - if (self.Subnodes == null) return 1; - - var min = int.MaxValue; - if (outOfCore) { for (var i = 0; i < 8; i++) @@ -564,7 +477,7 @@ public static int GetMinimumTreeDepth(this IPointCloudNode self, bool outOfCore) var n = self.Subnodes[i]; if (n != null) { - var x = n.Value.GetMinimumTreeDepth(outOfCore); + var x = n.Value.GetMinimumLeafPointCount(outOfCore); if (x < min) min = x; } } @@ -578,24 +491,24 @@ public static int GetMinimumTreeDepth(this IPointCloudNode self, bool outOfCore) { if (n.TryGetFromCache(out var node)) { - var x = node.GetMinimumTreeDepth(outOfCore); + var x = node.GetMinimumLeafPointCount(outOfCore); if (x < min) min = x; } } } } - return 1 + (min != int.MaxValue ? min : 0); } + return min; + } - /// - /// Depth of tree (maximum). - /// - public static int GetMaximiumTreeDepth(this IPointCloudNode self, bool outOfCore) + /// + /// Gets maximum point count of leaf nodes. + /// + public static long GetMaximumLeafPointCount(this IPointCloudNode self, bool outOfCore) + { + var max = long.MinValue; + if (self.Subnodes != null) { - if (self.Subnodes == null) return 1; - - var max = 0; - if (outOfCore) { for (var i = 0; i < 8; i++) @@ -603,7 +516,7 @@ public static int GetMaximiumTreeDepth(this IPointCloudNode self, bool outOfCore var n = self.Subnodes[i]; if (n != null) { - var x = n.Value.GetMaximiumTreeDepth(outOfCore); + var x = n.Value.GetMinimumLeafPointCount(outOfCore); if (x > max) max = x; } } @@ -617,322 +530,408 @@ public static int GetMaximiumTreeDepth(this IPointCloudNode self, bool outOfCore { if (n.TryGetFromCache(out var node)) { - var x = node.GetMaximiumTreeDepth(outOfCore); + var x = node.GetMinimumLeafPointCount(outOfCore); if (x > max) max = x; } } } } - return 1 + max; } + return max; + } - /// - /// Depth of tree (average). - /// - public static double GetAverageTreeDepth(this IPointCloudNode self, bool outOfCore) + /// + /// Gets average point count of leaf nodes. + /// + public static double GetAverageLeafPointCount(this IPointCloudNode self, bool outOfCore) + { + return self.PointCountTree / (double)self.CountNodes(outOfCore); + } + + /// + /// Depth of tree (minimum). + /// + public static int GetMinimumTreeDepth(this IPointCloudNode self, bool outOfCore) + { + if (self.Subnodes == null) return 1; + + var min = int.MaxValue; + + if (outOfCore) { - long sum = 0, count = 0; - self.GetAverageTreeDepth(outOfCore, 1, ref sum, ref count); - return sum / (double)count; + for (var i = 0; i < 8; i++) + { + var n = self.Subnodes[i]; + if (n != null) + { + var x = n.Value.GetMinimumTreeDepth(outOfCore); + if (x < min) min = x; + } + } } - private static void GetAverageTreeDepth(this IPointCloudNode self, bool outOfCore, int depth, ref long sum, ref long count) + else { - if (self.Subnodes == null) + for (var i = 0; i < 8; i++) { - sum += depth; count++; - return; + var n = self.Subnodes[i]; + if (n != null) + { + if (n.TryGetFromCache(out var node)) + { + var x = node.GetMinimumTreeDepth(outOfCore); + if (x < min) min = x; + } + } } + } + return 1 + (min != int.MaxValue ? min : 0); + } - ++depth; + /// + /// Depth of tree (maximum). + /// + public static int GetMaximiumTreeDepth(this IPointCloudNode self, bool outOfCore) + { + if (self.Subnodes == null) return 1; - if (outOfCore) + var max = 0; + + if (outOfCore) + { + for (var i = 0; i < 8; i++) { - for (var i = 0; i < 8; i++) + var n = self.Subnodes[i]; + if (n != null) { - var n = self.Subnodes[i]; - n?.Value.GetAverageTreeDepth(outOfCore, depth, ref sum, ref count); + var x = n.Value.GetMaximiumTreeDepth(outOfCore); + if (x > max) max = x; } } - else + } + else + { + for (var i = 0; i < 8; i++) { - for (var i = 0; i < 8; i++) + var n = self.Subnodes[i]; + if (n != null) { - var n = self.Subnodes[i]; - if (n != null) + if (n.TryGetFromCache(out var node)) { - if (n.TryGetFromCache(out var node)) node.GetAverageTreeDepth(outOfCore, depth, ref sum, ref count); + var x = node.GetMaximiumTreeDepth(outOfCore); + if (x > max) max = x; } } } } + return 1 + max; + } - #endregion + /// + /// Depth of tree (average). + /// + public static double GetAverageTreeDepth(this IPointCloudNode self, bool outOfCore) + { + long sum = 0, count = 0; + self.GetAverageTreeDepth(outOfCore, 1, ref sum, ref count); + return sum / (double)count; + } + private static void GetAverageTreeDepth(this IPointCloudNode self, bool outOfCore, int depth, ref long sum, ref long count) + { + if (self.Subnodes == null) + { + sum += depth; count++; + return; + } - #region TryGet* + ++depth; - /// Returns null if node has no colors. - public static PersistentRef? TryGetColors4b(this IPointCloudNode self) - => self.HasColors ? self.Colors : null; + if (outOfCore) + { + for (var i = 0; i < 8; i++) + { + var n = self.Subnodes[i]; + n?.Value.GetAverageTreeDepth(outOfCore, depth, ref sum, ref count); + } + } + else + { + for (var i = 0; i < 8; i++) + { + var n = self.Subnodes[i]; + if (n != null) + { + if (n.TryGetFromCache(out var node)) node.GetAverageTreeDepth(outOfCore, depth, ref sum, ref count); + } + } + } + } + + #endregion + + #region TryGet* + + /// Returns null if node has no colors. + public static PersistentRef? TryGetColors4b(this IPointCloudNode self) + => self.HasColors ? self.Colors : null; - /// Returns null if node has no normals. - public static PersistentRef? TryGetNormals3f(this IPointCloudNode self) - => self.HasNormals ? self.Normals : null; + /// Returns null if node has no normals. + public static PersistentRef? TryGetNormals3f(this IPointCloudNode self) + => self.HasNormals ? self.Normals : null; - /// Returns null if node has no intensities. - public static PersistentRef? TryGetIntensities(this IPointCloudNode self) - => self.HasIntensities ? self.Intensities : null; + /// Returns null if node has no intensities. + public static PersistentRef? TryGetIntensities(this IPointCloudNode self) + => self.HasIntensities ? self.Intensities : null; - /// Returns null if node has no classifications. - public static PersistentRef? TryGetClassifications(this IPointCloudNode self) - => self.HasClassifications ? self.Classifications : null; + /// Returns null if node has no classifications. + public static PersistentRef? TryGetClassifications(this IPointCloudNode self) + => self.HasClassifications ? self.Classifications : null; - #endregion + #endregion - #region Collect points from cells and cell columns + #region Collect points from cells and cell columns - /// - /// Collects all points from nodes for which predicate is true. - /// Subnodes of nodes for which predicate is true are not traversed. - /// - public static IEnumerable Collect(this IPointCloudNode self, Func predicate) + /// + /// Collects all points from nodes for which predicate is true. + /// Subnodes of nodes for which predicate is true are not traversed. + /// + public static IEnumerable Collect(this IPointCloudNode self, Func predicate) + { + if (self == null) yield break; + if (self.IsLeaf) + { + yield return self.ToChunk(); + } + else + { + var chunks = CollectRec(self, predicate); + foreach (var chunk in chunks) yield return chunk; + } + + static IEnumerable CollectRec(IPointCloudNode n, Func _collectMe) { - if (self == null) yield break; - if (self.IsLeaf) + if (n == null) yield break; + + if (_collectMe(n)) { - yield return self.ToChunk(); + yield return n.ToChunk(); } else { - var chunks = CollectRec(self, predicate); - foreach (var chunk in chunks) yield return chunk; - } - - static IEnumerable CollectRec(IPointCloudNode n, Func _collectMe) - { - if (n == null) yield break; - - if (_collectMe(n)) - { - yield return n.ToChunk(); - } - else + foreach (var x in n.Subnodes!) { - foreach (var x in n.Subnodes!) + if (x != null) { - if (x != null) - { - var chunks = CollectRec(x.Value, _collectMe); - foreach (var chunk in chunks) yield return chunk; - } + var chunks = CollectRec(x.Value, _collectMe); + foreach (var chunk in chunks) yield return chunk; } } } } + } + + /// + /// Collects all points from nodes at given relative depth. + /// E.g. 0 returns points from self, 1 gets points from children, aso. + /// + public static IEnumerable Collect(this IPointCloudNode self, int fromRelativeDepth) + { + var d = self.Cell.Exponent - fromRelativeDepth; + return self.Collect(x => x.IsLeaf || x.Cell.Exponent <= d); + } - /// - /// Collects all points from nodes at given relative depth. - /// E.g. 0 returns points from self, 1 gets points from children, aso. - /// - public static IEnumerable Collect(this IPointCloudNode self, int fromRelativeDepth) + /// + /// Returns points in cells column along z-axis at given xy-position. + /// + public static IEnumerable CollectColumnXY(this IPointCloudNode root, Cell2d columnXY, int fromRelativeDepth) + { + if (root == null) { - var d = self.Cell.Exponent - fromRelativeDepth; - return self.Collect(x => x.IsLeaf || x.Cell.Exponent <= d); + throw new ArgumentNullException(nameof(root)); } - /// - /// Returns points in cells column along z-axis at given xy-position. - /// - public static IEnumerable CollectColumnXY(this IPointCloudNode root, Cell2d columnXY, int fromRelativeDepth) - { - if (root == null) - { - throw new ArgumentNullException(nameof(root)); - } + if (columnXY.IsCenteredAtOrigin) throw new InvalidOperationException( + "Column centered at origin is not supported. Invariant bf3eb487-72d7-4a4a-9203-69c54490f608." + ); - if (columnXY.IsCenteredAtOrigin) throw new InvalidOperationException( - "Column centered at origin is not supported. Invariant bf3eb487-72d7-4a4a-9203-69c54490f608." - ); + if (fromRelativeDepth < 0) throw new ArgumentException( + $"Parameter 'fromRelativeDepth' must not be negative (but is {fromRelativeDepth}). " + + "Invariant c8f409cd-c8a0-4b3e-ac9b-03d23843ff8b.", + nameof(fromRelativeDepth) + ); - if (fromRelativeDepth < 0) throw new ArgumentException( - $"Parameter 'fromRelativeDepth' must not be negative (but is {fromRelativeDepth}). " - + "Invariant c8f409cd-c8a0-4b3e-ac9b-03d23843ff8b.", - nameof(fromRelativeDepth) - ); + var cloudXY = new Cell2d(root.Cell.X, root.Cell.Y, root.Cell.Exponent); - var cloudXY = new Cell2d(root.Cell.X, root.Cell.Y, root.Cell.Exponent); + // column fully includes point cloud + if (columnXY.Contains(cloudXY)) + { + return root.Collect(fromRelativeDepth); + } - // column fully includes point cloud - if (columnXY.Contains(cloudXY)) - { - return root.Collect(fromRelativeDepth); - } + // column is fully outside point cloud + if (!cloudXY.Contains(columnXY)) + { + return []; + } + + return QueryRec(root); - // column is fully outside point cloud - if (!cloudXY.Contains(columnXY)) + IEnumerable QueryRec(IPointCloudNode n) + { + if (n.Cell.Exponent < columnXY.Exponent) { - return Enumerable.Empty(); + // recursion should have stopped at column size ?! + throw new InvalidOperationException("Invariant 4d8cbedf-a86c-43e0-a3d0-75335fa1fadf."); } - return QueryRec(root); - - IEnumerable QueryRec(IPointCloudNode n) + // node is same size as column + if (n.Cell.Exponent == columnXY.Exponent) { - if (n.Cell.Exponent < columnXY.Exponent) + if (n.Cell.X == columnXY.X && n.Cell.Y == columnXY.Y) { - // recursion should have stopped at column size ?! - throw new InvalidOperationException("Invariant 4d8cbedf-a86c-43e0-a3d0-75335fa1fadf."); + var xs = n.Collect(fromRelativeDepth); + foreach (var x in xs) if (x.Count > 0) yield return x; } - - // node is same size as column - if (n.Cell.Exponent == columnXY.Exponent) + else { - if (n.Cell.X == columnXY.X && n.Cell.Y == columnXY.Y) - { - var xs = n.Collect(fromRelativeDepth); - foreach (var x in xs) if (x.Count > 0) yield return x; - } - else - { - yield break; - } + yield break; } + } - // or, node is a leaf, but still bigger than column - else if (n.IsLeaf) - { - var b = n.Cell.BoundingBox; - var c = columnXY.BoundingBox; - var f = new Box3d(new V3d(c.Min.X, c.Min.Y, b.Min.Z), new V3d(c.Max.X, c.Max.Y, b.Max.Z)); - var x = n.ToChunk().ImmutableFilterByBox3d(f); - if (x.Count > 0) yield return x; else yield break; - } + // or, node is a leaf, but still bigger than column + else if (n.IsLeaf) + { + var b = n.Cell.BoundingBox; + var c = columnXY.BoundingBox; + var f = new Box3d(new V3d(c.Min.X, c.Min.Y, b.Min.Z), new V3d(c.Max.X, c.Max.Y, b.Max.Z)); + var x = n.ToChunk().ImmutableFilterByBox3d(f); + if (x.Count > 0) yield return x; else yield break; + } - // or finally query subnodes inside column recursively ... - else + // or finally query subnodes inside column recursively ... + else + { + foreach (var subnode in n.Subnodes!) { - foreach (var subnode in n.Subnodes!) + if (subnode == null) continue; + var c = subnode.Value.Cell; + if (columnXY.Intersects(new Cell2d(c.X, c.Y, c.Exponent))) { - if (subnode == null) continue; - var c = subnode.Value.Cell; - if (columnXY.Intersects(new Cell2d(c.X, c.Y, c.Exponent))) - { - var xs = QueryRec(subnode.Value); - foreach (var x in xs) yield return x; - } + var xs = QueryRec(subnode.Value); + foreach (var x in xs) yield return x; } } } } + } - #endregion + #endregion - /// - public static bool IsLeaf(this IPointCloudNode self) => self.Subnodes == null; + /// + public static bool IsLeaf(this IPointCloudNode self) => self.Subnodes == null; - /// - public static bool IsNotLeaf(this IPointCloudNode self) => self.Subnodes != null; + /// + public static bool IsNotLeaf(this IPointCloudNode self) => self.Subnodes != null; + + /// + /// Throws if node misses any standard derived attributes. + /// + public static void CheckDerivedAttributes(this IPointCloudNode self) + { + var isTmpNode = self.Has(PointSetNode.TemporaryImportNode); - /// - /// Throws if node misses any standard derived attributes. - /// - public static void CheckDerivedAttributes(this IPointCloudNode self) + if (self.HasPositions) { - var isTmpNode = self.Has(PointSetNode.TemporaryImportNode); + if (!self.HasKdTree && !isTmpNode) throw new InvalidOperationException( + "Missing KdTree. Invariant ef8b6f10-a5ce-4dfd-826e-78319acd9faa." + ); + if (!self.HasBoundingBoxExactLocal) throw new InvalidOperationException( + "Missing BoundingBoxExactLocal. Invariant f91b261b-2aa2-41a0-9ada-4d03cbaf0507." + ); + if (!self.HasBoundingBoxExactGlobal) throw new InvalidOperationException( + "Missing BoundingBoxExactGlobal. Invariant 9bb641c6-ce54-4a1e-9cf9-943b85e3bf81." + ); + if (!self.HasCentroidLocal) throw new InvalidOperationException( + "Missing CentroidLocal. Invariant 72b18b6e-4c95-4a79-b9dd-11902c097649." + ); + if (!self.HasCentroidLocalStdDev) throw new InvalidOperationException( + "Missing CentroidLocalStdDev. Invariant 429bb1c1-9a52-4e4c-bab1-f3635e143061." + ); + //if (!self.HasPointDistanceAverage && !isTmpNode) throw new InvalidOperationException( + // "Missing PointDistanceAverage. Invariant e52003ff-72ca-4fc2-8242-f20d7f039473." + // ); + //if (!self.HasPointDistanceStandardDeviation && !isTmpNode) throw new InvalidOperationException( + // "Missing PointDistanceStandardDeviation. Invariant 4a03c3f5-a625-4124-91f6-6f79fd1b5d0e." + // ); + } - if (self.HasPositions) - { - if (!self.HasKdTree && !isTmpNode) throw new InvalidOperationException( - "Missing KdTree. Invariant ef8b6f10-a5ce-4dfd-826e-78319acd9faa." - ); - if (!self.HasBoundingBoxExactLocal) throw new InvalidOperationException( - "Missing BoundingBoxExactLocal. Invariant f91b261b-2aa2-41a0-9ada-4d03cbaf0507." - ); - if (!self.HasBoundingBoxExactGlobal) throw new InvalidOperationException( - "Missing BoundingBoxExactGlobal. Invariant 9bb641c6-ce54-4a1e-9cf9-943b85e3bf81." - ); - if (!self.HasCentroidLocal) throw new InvalidOperationException( - "Missing CentroidLocal. Invariant 72b18b6e-4c95-4a79-b9dd-11902c097649." - ); - if (!self.HasCentroidLocalStdDev) throw new InvalidOperationException( - "Missing CentroidLocalStdDev. Invariant 429bb1c1-9a52-4e4c-bab1-f3635e143061." - ); - //if (!self.HasPointDistanceAverage && !isTmpNode) throw new InvalidOperationException( - // "Missing PointDistanceAverage. Invariant e52003ff-72ca-4fc2-8242-f20d7f039473." - // ); - //if (!self.HasPointDistanceStandardDeviation && !isTmpNode) throw new InvalidOperationException( - // "Missing PointDistanceStandardDeviation. Invariant 4a03c3f5-a625-4124-91f6-6f79fd1b5d0e." - // ); - } + if (!self.HasMaxTreeDepth) throw new InvalidOperationException( + "Missing MaxTreeDepth. Invariant c7c3c337-5404-4773-aae3-01d213e575b0." + ); + if (!self.HasMinTreeDepth) throw new InvalidOperationException( + "Missing MinTreeDepth. Invariant 2df9fb7b-684a-4103-8f14-07785607d2f4." + ); - if (!self.HasMaxTreeDepth) throw new InvalidOperationException( - "Missing MaxTreeDepth. Invariant c7c3c337-5404-4773-aae3-01d213e575b0." + if (!self.IsTemporaryImportNode && self.HasPartIndexRange && !self.HasPartIndices) throw new InvalidOperationException( + "Missing part indices. Invariant fc4ff983-151f-4956-b882-54d649245049." ); - if (!self.HasMinTreeDepth) throw new InvalidOperationException( - "Missing MinTreeDepth. Invariant 2df9fb7b-684a-4103-8f14-07785607d2f4." + + if (self.HasPartIndices) + { + if (!self.HasPartIndexRange) throw new InvalidOperationException( + "Missing PartIndexRange. Invariant 90571267-b593-4458-83d6-5448dd9c5257." ); - if (!self.IsTemporaryImportNode && self.HasPartIndexRange && !self.HasPartIndices) throw new InvalidOperationException( - "Missing part indices. Invariant fc4ff983-151f-4956-b882-54d649245049." - ); + if (self.Has(Durable.Octree.PerPointPartIndex1b)) throw new InvalidOperationException( + "PerPointPartIndex1b should be PerPointPartIndex1bReference. Invariant 85879fd8-7ee8-45a0-b221-67f826bc42df." + ); + if (self.Has(Durable.Octree.PerPointPartIndex1s)) throw new InvalidOperationException( + "PerPointPartIndex1s should be PerPointPartIndex1sReference. Invariant 598f615b-289a-4bfe-b5cf-b61e4bd380d5." + ); + if (self.Has(Durable.Octree.PerPointPartIndex1i)) throw new InvalidOperationException( + "PerPointPartIndex1i should be PerPointPartIndex1iReference. Invariant d5391b75-bbc1-4c83-b963-50fd0f409931." + ); + } + } - if (self.HasPartIndices) - { - if (!self.HasPartIndexRange) throw new InvalidOperationException( - "Missing PartIndexRange. Invariant 90571267-b593-4458-83d6-5448dd9c5257." - ); + /// + /// Converts node to Chunk. + /// + public static Chunk ToChunk(this IPointCloudNode self) + { + var cs = self.HasColors ? self.Colors.Value : null; + var ns = self.HasNormals ? self.Normals.Value : null; + var js = self.HasIntensities ? self.Intensities.Value : null; + var ks = self.HasClassifications ? self.Classifications!.Value : null; + var qs = self.HasPartIndices ? self.PartIndices : null; + return new Chunk(self.PositionsAbsolute, cs, ns, js, ks, qs, partIndexRange: null, bbox: null); + } - if (self.Has(Durable.Octree.PerPointPartIndex1b)) throw new InvalidOperationException( - "PerPointPartIndex1b should be PerPointPartIndex1bReference. Invariant 85879fd8-7ee8-45a0-b221-67f826bc42df." - ); - if (self.Has(Durable.Octree.PerPointPartIndex1s)) throw new InvalidOperationException( - "PerPointPartIndex1s should be PerPointPartIndex1sReference. Invariant 598f615b-289a-4bfe-b5cf-b61e4bd380d5." - ); - if (self.Has(Durable.Octree.PerPointPartIndex1i)) throw new InvalidOperationException( - "PerPointPartIndex1i should be PerPointPartIndex1iReference. Invariant d5391b75-bbc1-4c83-b963-50fd0f409931." - ); - } - } + /// + /// Converts node to Chunks (by collecting subnodes from given relative depth). + /// + public static IEnumerable ToChunk(this IPointCloudNode self, int fromRelativeDepth) + { + if (fromRelativeDepth < 0) throw new InvalidOperationException( + $"FromRelativeDepth must be positive, but is {fromRelativeDepth}. Invariant f94781c3-cad7-4f46-b2e1-573cd1df227b." + ); - /// - /// Converts node to Chunk. - /// - public static Chunk ToChunk(this IPointCloudNode self) + if (fromRelativeDepth == 0 || self.IsLeaf) { var cs = self.HasColors ? self.Colors.Value : null; var ns = self.HasNormals ? self.Normals.Value : null; var js = self.HasIntensities ? self.Intensities.Value : null; var ks = self.HasClassifications ? self.Classifications!.Value : null; var qs = self.HasPartIndices ? self.PartIndices : null; - return new Chunk(self.PositionsAbsolute, cs, ns, js, ks, qs, partIndexRange: null, bbox: null); + yield return new Chunk(self.PositionsAbsolute, cs, ns, js, ks, qs, partIndexRange: null, bbox: null); } - - /// - /// Converts node to Chunks (by collecting subnodes from given relative depth). - /// - public static IEnumerable ToChunk(this IPointCloudNode self, int fromRelativeDepth) + else { - if (fromRelativeDepth < 0) throw new InvalidOperationException( - $"FromRelativeDepth must be positive, but is {fromRelativeDepth}. Invariant f94781c3-cad7-4f46-b2e1-573cd1df227b." - ); - - if (fromRelativeDepth == 0 || self.IsLeaf) + foreach (var x in self.Subnodes!) { - var cs = self.HasColors ? self.Colors.Value : null; - var ns = self.HasNormals ? self.Normals.Value : null; - var js = self.HasIntensities ? self.Intensities.Value : null; - var ks = self.HasClassifications ? self.Classifications!.Value : null; - var qs = self.HasPartIndices ? self.PartIndices : null; - yield return new Chunk(self.PositionsAbsolute, cs, ns, js, ks, qs, partIndexRange: null, bbox: null); - } - else - { - foreach (var x in self.Subnodes!) + if (x != null) { - if (x != null) - { - foreach (var y in x.Value.ToChunk(fromRelativeDepth - 1)) yield return y; - } + foreach (var y in x.Value.ToChunk(fromRelativeDepth - 1)) yield return y; } } } diff --git a/src/Aardvark.Geometry.PointSet/Octrees/ImportConfig.cs b/src/Aardvark.Geometry.PointSet/Octrees/ImportConfig.cs index a66be02d..e2aa8743 100644 --- a/src/Aardvark.Geometry.PointSet/Octrees/ImportConfig.cs +++ b/src/Aardvark.Geometry.PointSet/Octrees/ImportConfig.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -16,147 +16,146 @@ You should have received a copy of the GNU Affero General Public License using System.Collections.Generic; using System.Threading; -namespace Aardvark.Data.Points +namespace Aardvark.Data.Points; + +/// +/// General info for a point cloud data file. +/// +public class ImportConfig { /// - /// General info for a point cloud data file. + /// Default configuration. /// - public class ImportConfig - { - /// - /// Default configuration. - /// - public static readonly ImportConfig Default = new(); + public static readonly ImportConfig Default = new(); - #region Properties + #region Properties - /// - public ParseConfig ParseConfig { get; private set; } = ParseConfig.Default; - - /// - /// Store imported point cloud with this key. - /// - public string? Key { get; private set; } = null; + /// + public ParseConfig ParseConfig { get; private set; } = ParseConfig.Default; + + /// + /// Store imported point cloud with this key. + /// + public string? Key { get; private set; } = null; - /// - public CancellationToken CancellationToken => ParseConfig.CancellationToken; + /// + public CancellationToken CancellationToken => ParseConfig.CancellationToken; - /// - public int MaxDegreeOfParallelism => ParseConfig.MaxDegreeOfParallelism; + /// + public int MaxDegreeOfParallelism => ParseConfig.MaxDegreeOfParallelism; - /// - /// Remove points on import with less than this distance to previous point. - /// - public double MinDist => ParseConfig.MinDist; - /// - /// Large files should be read in chunks with this maximum size. - /// - public int ReadBufferSizeInBytes => ParseConfig.ReadBufferSizeInBytes; + /// + /// Remove points on import with less than this distance to previous point. + /// + public double MinDist => ParseConfig.MinDist; + /// + /// Large files should be read in chunks with this maximum size. + /// + public int ReadBufferSizeInBytes => ParseConfig.ReadBufferSizeInBytes; - /// Normalizes point density globally using MinDist distance. - public bool NormalizePointDensityGlobal { get; private set; } = false; + /// Normalizes point density globally using MinDist distance. + public bool NormalizePointDensityGlobal { get; private set; } = false; - /// - /// Max number of points in octree cell. - /// - public int OctreeSplitLimit { get; private set; } = 8192; + /// + /// Max number of points in octree cell. + /// + public int OctreeSplitLimit { get; private set; } = 8192; - /// - public Action ProgressCallback { get; private set; } = _ => { }; + /// + public Action ProgressCallback { get; private set; } = _ => { }; - /// - public Func, IList>? Reproject { get; private set; } = null; + /// + public Func, IList>? Reproject { get; private set; } = null; - /// - public Storage Storage { get; init; } = null!; + /// + public Storage Storage { get; init; } = null!; - /// - public bool Verbose => ParseConfig.Verbose; + /// + public bool Verbose => ParseConfig.Verbose; - /// - public int MaxChunkPointCount => ParseConfig.MaxChunkPointCount; + /// + public int MaxChunkPointCount => ParseConfig.MaxChunkPointCount; - #endregion + #endregion - #region Immutable updates + #region Immutable updates - private ImportConfig() { } + private ImportConfig() { } - /// - public ImportConfig(ImportConfig x) - { - Key = x.Key; - NormalizePointDensityGlobal = x.NormalizePointDensityGlobal; - OctreeSplitLimit = x.OctreeSplitLimit; - ProgressCallback = x.ProgressCallback; - ParseConfig = x.ParseConfig; - Reproject = x.Reproject; - Storage = x.Storage; - } + /// + public ImportConfig(ImportConfig x) + { + Key = x.Key; + NormalizePointDensityGlobal = x.NormalizePointDensityGlobal; + OctreeSplitLimit = x.OctreeSplitLimit; + ProgressCallback = x.ProgressCallback; + ParseConfig = x.ParseConfig; + Reproject = x.Reproject; + Storage = x.Storage; + } - /// - public ImportConfig WithCancellationToken(CancellationToken x) => new(this) { ParseConfig = ParseConfig.WithCancellationToken(x) }; + /// + public ImportConfig WithCancellationToken(CancellationToken x) => new(this) { ParseConfig = ParseConfig.WithCancellationToken(x) }; - /// - public ImportConfig WithKey(string x) => new(this) { Key = x }; + /// + public ImportConfig WithKey(string x) => new(this) { Key = x }; - /// - public ImportConfig WithKey(Guid x) => new(this) { Key = x.ToString() }; + /// + public ImportConfig WithKey(Guid x) => new(this) { Key = x.ToString() }; - /// - public ImportConfig WithRandomKey() => WithKey(Guid.NewGuid().ToString()); + /// + public ImportConfig WithRandomKey() => WithKey(Guid.NewGuid().ToString()); - /// - public ImportConfig WithMaxDegreeOfParallelism(int x) => new(this) { ParseConfig = ParseConfig.WithMaxDegreeOfParallelism(x) }; + /// + public ImportConfig WithMaxDegreeOfParallelism(int x) => new(this) { ParseConfig = ParseConfig.WithMaxDegreeOfParallelism(x) }; - /// - public ImportConfig WithMinDist(double x) => new(this) { ParseConfig = ParseConfig.WithMinDist(x) }; + /// + public ImportConfig WithMinDist(double x) => new(this) { ParseConfig = ParseConfig.WithMinDist(x) }; - /// - public ImportConfig WithNormalizePointDensityGlobal(bool x) => new(this) { NormalizePointDensityGlobal = x }; + /// + public ImportConfig WithNormalizePointDensityGlobal(bool x) => new(this) { NormalizePointDensityGlobal = x }; - /// - public ImportConfig WithOctreeSplitLimit(int x) => new(this) { OctreeSplitLimit = x }; + /// + public ImportConfig WithOctreeSplitLimit(int x) => new(this) { OctreeSplitLimit = x }; - /// - public ImportConfig WithProgressCallback(Action x) => new(this) { ProgressCallback = x ?? throw new(nameof(x)) }; + /// + public ImportConfig WithProgressCallback(Action x) => new(this) { ProgressCallback = x ?? throw new(nameof(x)) }; - /// - public ImportConfig WithReadBufferSizeInBytes(int x) => new(this) { ParseConfig = ParseConfig.WithReadBufferSizeInBytes(x) }; + /// + public ImportConfig WithReadBufferSizeInBytes(int x) => new(this) { ParseConfig = ParseConfig.WithReadBufferSizeInBytes(x) }; - /// - public ImportConfig WithMaxChunkPointCount(int x) => new(this) { ParseConfig = ParseConfig.WithMaxChunkPointCount(Math.Max(x, 1)) }; + /// + public ImportConfig WithMaxChunkPointCount(int x) => new(this) { ParseConfig = ParseConfig.WithMaxChunkPointCount(Math.Max(x, 1)) }; - /// - public ImportConfig WithReproject(Func, IList> x) => new(this) { Reproject = x }; + /// + public ImportConfig WithReproject(Func, IList> x) => new(this) { Reproject = x }; - /// - public ImportConfig WithVerbose(bool x) => new(this) { ParseConfig = ParseConfig.WithVerbose(x) }; + /// + public ImportConfig WithVerbose(bool x) => new(this) { ParseConfig = ParseConfig.WithVerbose(x) }; - /// - public ImportConfig WithStorage(Storage x) => new(this) { Storage = x }; + /// + public ImportConfig WithStorage(Storage x) => new(this) { Storage = x }; - /// - public ImportConfig WithPartIndexOffset(int x) => new(this) { ParseConfig = ParseConfig.WithPartIndexOffset(x) }; + /// + public ImportConfig WithPartIndexOffset(int x) => new(this) { ParseConfig = ParseConfig.WithPartIndexOffset(x) }; - /// - public ImportConfig WithEnabledProperties(EnabledProperties x) => new(this) { ParseConfig = ParseConfig.WithEnabledProperties(x) }; + /// + public ImportConfig WithEnabledProperties(EnabledProperties x) => new(this) { ParseConfig = ParseConfig.WithEnabledProperties(x) }; - /// - public ImportConfig WithEnabledClassifications(bool enabled) => new(this) { ParseConfig = ParseConfig.WithEnabledProperties(ParseConfig.EnabledProperties.WithClassifications(enabled)) }; + /// + public ImportConfig WithEnabledClassifications(bool enabled) => new(this) { ParseConfig = ParseConfig.WithEnabledProperties(ParseConfig.EnabledProperties.WithClassifications(enabled)) }; - /// - public ImportConfig WithEnabledColors(bool enabled) => new(this) { ParseConfig = ParseConfig.WithEnabledProperties(ParseConfig.EnabledProperties.WithColors(enabled)) }; + /// + public ImportConfig WithEnabledColors(bool enabled) => new(this) { ParseConfig = ParseConfig.WithEnabledProperties(ParseConfig.EnabledProperties.WithColors(enabled)) }; - /// - public ImportConfig WithEnabledIntensities(bool enabled) => new(this) { ParseConfig = ParseConfig.WithEnabledProperties(ParseConfig.EnabledProperties.WithIntensities(enabled)) }; + /// + public ImportConfig WithEnabledIntensities(bool enabled) => new(this) { ParseConfig = ParseConfig.WithEnabledProperties(ParseConfig.EnabledProperties.WithIntensities(enabled)) }; - /// - public ImportConfig WithEnabledNormals(bool enabled) => new(this) { ParseConfig = ParseConfig.WithEnabledProperties(ParseConfig.EnabledProperties.WithNormals(enabled)) }; + /// + public ImportConfig WithEnabledNormals(bool enabled) => new(this) { ParseConfig = ParseConfig.WithEnabledProperties(ParseConfig.EnabledProperties.WithNormals(enabled)) }; - /// - public ImportConfig WithEnabledPartIndices(bool enabled) => new(this) { ParseConfig = ParseConfig.WithEnabledProperties(ParseConfig.EnabledProperties.WithPartIndices(enabled)) }; + /// + public ImportConfig WithEnabledPartIndices(bool enabled) => new(this) { ParseConfig = ParseConfig.WithEnabledProperties(ParseConfig.EnabledProperties.WithPartIndices(enabled)) }; - #endregion - } + #endregion } diff --git a/src/Aardvark.Geometry.PointSet/Octrees/InMemoryPointSet.cs b/src/Aardvark.Geometry.PointSet/Octrees/InMemoryPointSet.cs index 60b35458..3b7cb88c 100644 --- a/src/Aardvark.Geometry.PointSet/Octrees/InMemoryPointSet.cs +++ b/src/Aardvark.Geometry.PointSet/Octrees/InMemoryPointSet.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -16,385 +16,383 @@ You should have received a copy of the GNU Affero General Public License using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.IO; using System.Linq; using static Aardvark.Data.Durable; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +public class InMemoryPointSet { - public class InMemoryPointSet - { - private readonly ImmutableDictionary m_data; - private readonly int m_splitLimit; - private readonly Node m_root; - private readonly IList m_ps; - //private readonly bool m_hasPartIndices = false; + private readonly ImmutableDictionary m_data; + private readonly int m_splitLimit; + private readonly Node m_root; + private readonly IList m_ps; + //private readonly bool m_hasPartIndices = false; - public static InMemoryPointSet Build(GenericChunk chunk, int octreeSplitLimit) - => new(chunk.Data, new Cell(chunk.BoundingBox), octreeSplitLimit); + public static InMemoryPointSet Build(GenericChunk chunk, int octreeSplitLimit) + => new(chunk.Data, new Cell(chunk.BoundingBox), octreeSplitLimit); - public static InMemoryPointSet Build(Chunk chunk, int octreeSplitLimit) - => Build(chunk.Positions, chunk.Colors, chunk.Normals, chunk.Intensities, chunk.Classifications, chunk.PartIndices, new Cell(chunk.BoundingBox), octreeSplitLimit); + public static InMemoryPointSet Build(Chunk chunk, int octreeSplitLimit) + => Build(chunk.Positions, chunk.Colors, chunk.Normals, chunk.Intensities, chunk.Classifications, chunk.PartIndices, new Cell(chunk.BoundingBox), octreeSplitLimit); - public static InMemoryPointSet Build(Chunk chunk, Cell rootBounds, int octreeSplitLimit) - => Build(chunk.Positions, chunk.Colors, chunk.Normals, chunk.Intensities, chunk.Classifications, chunk.PartIndices, rootBounds, octreeSplitLimit); + public static InMemoryPointSet Build(Chunk chunk, Cell rootBounds, int octreeSplitLimit) + => Build(chunk.Positions, chunk.Colors, chunk.Normals, chunk.Intensities, chunk.Classifications, chunk.PartIndices, rootBounds, octreeSplitLimit); - public static InMemoryPointSet Build(IList ps, IList? cs, IList? ns, IList? js, IList? ks, object? partIndices, Cell rootBounds, int octreeSplitLimit) - { - if (ps == null) throw new ArgumentNullException(nameof(ps)); + public static InMemoryPointSet Build(IList ps, IList? cs, IList? ns, IList? js, IList? ks, object? partIndices, Cell rootBounds, int octreeSplitLimit) + { + if (ps == null) throw new ArgumentNullException(nameof(ps)); - var data = ImmutableDictionary.Empty - .Add(Octree.PositionsGlobal3d, ps.ToArray()) - ; - if (cs != null) data = data.Add(Octree.Colors4b, cs.ToArray()); - if (ns != null) data = data.Add(Octree.Normals3f, ns.ToArray()); - if (js != null) data = data.Add(Octree.Intensities1i, js.ToArray()); - if (ks != null) data = data.Add(Octree.Classifications1b, ks.ToArray()); + var data = ImmutableDictionary.Empty + .Add(Octree.PositionsGlobal3d, ps.ToArray()) + ; + if (cs != null) data = data.Add(Octree.Colors4b, cs.ToArray()); + if (ns != null) data = data.Add(Octree.Normals3f, ns.ToArray()); + if (js != null) data = data.Add(Octree.Intensities1i, js.ToArray()); + if (ks != null) data = data.Add(Octree.Classifications1b, ks.ToArray()); - switch (partIndices) - { - case null : break; - case int x : data = data.Add(Octree.PerCellPartIndex1i , x ).Add(Octree.PartIndexRange, new Range1i( x)); break; - case uint x : data = data.Add(Octree.PerCellPartIndex1i , (int)x ).Add(Octree.PartIndexRange, new Range1i((int)x)); break; - case byte[] xs: data = data.Add(Octree.PerPointPartIndex1b, xs ).Add(Octree.PartIndexRange, new Range1i(xs.Select(x => (int)x))); break; - case IList xs: data = data.Add(Octree.PerPointPartIndex1b, xs.ToArray()).Add(Octree.PartIndexRange, new Range1i(xs.Select(x => (int)x))); break; - case IEnumerable xs: data = data.Add(Octree.PerPointPartIndex1b, xs.ToArray()).Add(Octree.PartIndexRange, new Range1i(xs.Select(x => (int)x))); break; - case short[] xs: data = data.Add(Octree.PerPointPartIndex1s, xs ).Add(Octree.PartIndexRange, new Range1i(xs.Select(x => (int)x))); break; - case IList xs: data = data.Add(Octree.PerPointPartIndex1s, xs.ToArray()).Add(Octree.PartIndexRange, new Range1i(xs.Select(x => (int)x))); break; - case IEnumerable xs: data = data.Add(Octree.PerPointPartIndex1s, xs.ToArray()).Add(Octree.PartIndexRange, new Range1i(xs.Select(x => (int)x))); break; - case int[] xs: data = data.Add(Octree.PerPointPartIndex1i, xs ).Add(Octree.PartIndexRange, new Range1i(xs)); break; - case IList xs: data = data.Add(Octree.PerPointPartIndex1i, xs.ToArray()).Add(Octree.PartIndexRange, new Range1i(xs)); break; - case IEnumerable xs: data = data.Add(Octree.PerPointPartIndex1i, xs.ToArray()).Add(Octree.PartIndexRange, new Range1i(xs)); break; - - default: throw new Exception( - $"Unknown part indices type {partIndices.GetType().FullName}. Error 9869c0df-de22-4385-b39f-2b55a8c64f13." - ); - } + switch (partIndices) + { + case null : break; + case int x : data = data.Add(Octree.PerCellPartIndex1i , x ).Add(Octree.PartIndexRange, new Range1i( x)); break; + case uint x : data = data.Add(Octree.PerCellPartIndex1i , (int)x ).Add(Octree.PartIndexRange, new Range1i((int)x)); break; + case byte[] xs: data = data.Add(Octree.PerPointPartIndex1b, xs ).Add(Octree.PartIndexRange, new Range1i(xs.Select(x => (int)x))); break; + case IList xs: data = data.Add(Octree.PerPointPartIndex1b, xs.ToArray()).Add(Octree.PartIndexRange, new Range1i(xs.Select(x => (int)x))); break; + case IEnumerable xs: data = data.Add(Octree.PerPointPartIndex1b, xs.ToArray()).Add(Octree.PartIndexRange, new Range1i(xs.Select(x => (int)x))); break; + case short[] xs: data = data.Add(Octree.PerPointPartIndex1s, xs ).Add(Octree.PartIndexRange, new Range1i(xs.Select(x => (int)x))); break; + case IList xs: data = data.Add(Octree.PerPointPartIndex1s, xs.ToArray()).Add(Octree.PartIndexRange, new Range1i(xs.Select(x => (int)x))); break; + case IEnumerable xs: data = data.Add(Octree.PerPointPartIndex1s, xs.ToArray()).Add(Octree.PartIndexRange, new Range1i(xs.Select(x => (int)x))); break; + case int[] xs: data = data.Add(Octree.PerPointPartIndex1i, xs ).Add(Octree.PartIndexRange, new Range1i(xs)); break; + case IList xs: data = data.Add(Octree.PerPointPartIndex1i, xs.ToArray()).Add(Octree.PartIndexRange, new Range1i(xs)); break; + case IEnumerable xs: data = data.Add(Octree.PerPointPartIndex1i, xs.ToArray()).Add(Octree.PartIndexRange, new Range1i(xs)); break; + + default: throw new Exception( + $"Unknown part indices type {partIndices.GetType().FullName}. Error 9869c0df-de22-4385-b39f-2b55a8c64f13." + ); + } - return new InMemoryPointSet(data, rootBounds, octreeSplitLimit); + return new InMemoryPointSet(data, rootBounds, octreeSplitLimit); + } + + /// + /// Constructor. + /// + private InMemoryPointSet(ImmutableDictionary data, Cell cell, int octreeSplitLimit) + { + if (data == null) throw new ArgumentNullException(nameof(data)); + + foreach (var kv in data) + { + if (kv.Key == Octree.PerCellPartIndex1i || kv.Key == Octree.PerCellPartIndex1ui) continue; + if (kv.Key == Octree.PartIndexRange) continue; + if (kv.Value is not Array) throw new ArgumentException($"Entry {kv.Key} must be array."); } - /// - /// Constructor. - /// - private InMemoryPointSet(ImmutableDictionary data, Cell cell, int octreeSplitLimit) + void TryRename(Def from, Def to) { - if (data == null) throw new ArgumentNullException(nameof(data)); - - foreach (var kv in data) + if (data.TryGetValue(from, out var obj)) { - if (kv.Key == Octree.PerCellPartIndex1i || kv.Key == Octree.PerCellPartIndex1ui) continue; - if (kv.Key == Octree.PartIndexRange) continue; - if (kv.Value is not Array) throw new ArgumentException($"Entry {kv.Key} must be array."); + data = data.Remove(from).Add(to, obj); } + } + TryRename(GenericChunk.Defs.Positions3f, Octree.PositionsGlobal3f); + TryRename(GenericChunk.Defs.Positions3d, Octree.PositionsGlobal3d); + TryRename(GenericChunk.Defs.Classifications1b, Octree.Classifications1b); + TryRename(GenericChunk.Defs.Classifications1i, Octree.Classifications1i); + TryRename(GenericChunk.Defs.Classifications1s, Octree.Classifications1s); + TryRename(GenericChunk.Defs.Colors3b, Octree.Colors3b ); + TryRename(GenericChunk.Defs.Colors4b, Octree.Colors4b ); + TryRename(GenericChunk.Defs.Intensities1i, Octree.Intensities1i ); + TryRename(GenericChunk.Defs.Intensities1f, Octree.Intensities1f ); + TryRename(GenericChunk.Defs.Normals3f, Octree.Normals3f ); + + // extract positions ... + if (data.TryGetValue(Octree.PositionsGlobal3d, out var ps3d)) + { + m_ps = (V3d[])ps3d; + data = data.Remove(Octree.PositionsGlobal3d); + } + else if (data.TryGetValue(Octree.PositionsGlobal3f, out var ps3f)) + { + m_ps = ((V3f[])ps3f).Map(p => (V3d)p); + data = data.Remove(Octree.PositionsGlobal3f); + } + else + { + throw new Exception("Could not find positions. Please add one of the following entries to 'data': Octree.PositionsGlobal3[df], GenericChunk.Defs.Positions3[df]."); + } - void TryRename(Def from, Def to) - { - if (data.TryGetValue(from, out var obj)) - { - data = data.Remove(from).Add(to, obj); - } - } - TryRename(GenericChunk.Defs.Positions3f, Octree.PositionsGlobal3f); - TryRename(GenericChunk.Defs.Positions3d, Octree.PositionsGlobal3d); - TryRename(GenericChunk.Defs.Classifications1b, Octree.Classifications1b); - TryRename(GenericChunk.Defs.Classifications1i, Octree.Classifications1i); - TryRename(GenericChunk.Defs.Classifications1s, Octree.Classifications1s); - TryRename(GenericChunk.Defs.Colors3b, Octree.Colors3b ); - TryRename(GenericChunk.Defs.Colors4b, Octree.Colors4b ); - TryRename(GenericChunk.Defs.Intensities1i, Octree.Intensities1i ); - TryRename(GenericChunk.Defs.Intensities1f, Octree.Intensities1f ); - TryRename(GenericChunk.Defs.Normals3f, Octree.Normals3f ); - - // extract positions ... - if (data.TryGetValue(Octree.PositionsGlobal3d, out var ps3d)) - { - m_ps = (V3d[])ps3d; - data = data.Remove(Octree.PositionsGlobal3d); - } - else if (data.TryGetValue(Octree.PositionsGlobal3f, out var ps3f)) - { - m_ps = ((V3f[])ps3f).Map(p => (V3d)p); - data = data.Remove(Octree.PositionsGlobal3f); - } - else - { - throw new Exception("Could not find positions. Please add one of the following entries to 'data': Octree.PositionsGlobal3[df], GenericChunk.Defs.Positions3[df]."); - } + m_data = data; + m_splitLimit = octreeSplitLimit; + m_root = new Node(this, cell); + for (var i = 0; i < m_ps.Count; i++) m_root.Insert(i); + } - m_data = data; - m_splitLimit = octreeSplitLimit; - m_root = new Node(this, cell); - for (var i = 0; i < m_ps.Count; i++) m_root.Insert(i); - } + public PointSetNode ToPointSetNode(Storage storage, bool isTemporaryImportNode) + { + var result = m_root.ToPointSetNode(storage, isTemporaryImportNode); + return result; + } - public PointSetNode ToPointSetNode(Storage storage, bool isTemporaryImportNode) + private class Node + { + private readonly InMemoryPointSet _octree; + private readonly Cell _cell; + private readonly double _centerX, _centerY, _centerZ; + private Node[]? _subnodes; + private List? _ia; + public Box3d BoundingBox => _cell.BoundingBox; + + public Node(InMemoryPointSet octree, Cell cell) { - var result = m_root.ToPointSetNode(storage, isTemporaryImportNode); - return result; + _octree = octree; + _cell = cell; + var c = cell.BoundingBox.Center; + _centerX = c.X; _centerY = c.Y; _centerZ = c.Z; } - private class Node + + /// + /// The following arrays will be stored as separate entries in the store and referenced via id. + /// + private static readonly Dictionary StoreAsReference = new() { - private readonly InMemoryPointSet _octree; - private readonly Cell _cell; - private readonly double _centerX, _centerY, _centerZ; - private Node[]? _subnodes; - private List? _ia; - public Box3d BoundingBox => _cell.BoundingBox; - - public Node(InMemoryPointSet octree, Cell cell) - { - _octree = octree; - _cell = cell; - var c = cell.BoundingBox.Center; - _centerX = c.X; _centerY = c.Y; _centerZ = c.Z; - } + { Octree.PositionsLocal3f , Octree.PositionsLocal3fReference }, + { Octree.Colors4b , Octree.Colors4bReference }, + { Octree.Normals3f , Octree.Normals3fReference }, + { Octree.Intensities1i , Octree.Intensities1iReference }, + { Octree.Classifications1b , Octree.Classifications1bReference }, + { Octree.PerPointPartIndex1b, Octree.PerPointPartIndex1bReference }, + { Octree.PerPointPartIndex1s, Octree.PerPointPartIndex1sReference }, + { Octree.PerPointPartIndex1i, Octree.PerPointPartIndex1iReference }, + }; + + + internal PointSetNode ToPointSetNode(Storage storage, bool isTemporaryImportNode) + { + // final result data (which we will use to create a PointSetNode) + var resultData = ImmutableDictionary.Empty + .Add(Octree.NodeId, Guid.NewGuid()) + .Add(Octree.Cell, _cell) + ; + if (isTemporaryImportNode) resultData = resultData.Add(PointSetNode.TemporaryImportNode, 0); - /// - /// The following arrays will be stored as separate entries in the store and referenced via id. - /// - private static readonly Dictionary StoreAsReference = new() - { - { Octree.PositionsLocal3f , Octree.PositionsLocal3fReference }, - { Octree.Colors4b , Octree.Colors4bReference }, - { Octree.Normals3f , Octree.Normals3fReference }, - { Octree.Intensities1i , Octree.Intensities1iReference }, - { Octree.Classifications1b , Octree.Classifications1bReference }, - { Octree.PerPointPartIndex1b, Octree.PerPointPartIndex1bReference }, - { Octree.PerPointPartIndex1s, Octree.PerPointPartIndex1sReference }, - { Octree.PerPointPartIndex1i, Octree.PerPointPartIndex1iReference }, - }; - - - internal PointSetNode ToPointSetNode(Storage storage, bool isTemporaryImportNode) - { - // final result data (which we will use to create a PointSetNode) - var resultData = ImmutableDictionary.Empty - .Add(Octree.NodeId, Guid.NewGuid()) - .Add(Octree.Cell, _cell) - ; + // keep track whether we really convert all existing data entries to a PointSetNode + // (so we can fail if something is left over at the end, something we did not expect or handle correctly) + var notHandled = new HashSet(_octree.m_data.Keys); - if (isTemporaryImportNode) resultData = resultData.Add(PointSetNode.TemporaryImportNode, 0); + var center = new V3d(_centerX, _centerY, _centerZ); - // keep track whether we really convert all existing data entries to a PointSetNode - // (so we can fail if something is left over at the end, something we did not expect or handle correctly) - var notHandled = new HashSet(_octree.m_data.Keys); + if (_subnodes == null) + { + // leaf node ... - var center = new V3d(_centerX, _centerY, _centerZ); + #region positions - if (_subnodes == null) + // transform global float64 positions to local float32 positions (relative to cell center) to save space + var psGlobal = _octree.m_ps; + V3f[] psLocal; + if (_ia != null) + { + psLocal = new V3f[_ia.Count]; + for (var i = 0; i < _ia.Count; i++) psLocal[i] = (V3f)(psGlobal[_ia[i]] - center); // relative to center + } + else { - // leaf node ... + psLocal = new V3f[psGlobal.Count]; + for (var i = 0; i < psGlobal.Count; i++) psLocal[i] = (V3f)(psGlobal[i] - center); // relative to center + } - #region positions + var bbExactLocal = new Box3f(psLocal); - // transform global float64 positions to local float32 positions (relative to cell center) to save space - var psGlobal = _octree.m_ps; - V3f[] psLocal; - if (_ia != null) - { - psLocal = new V3f[_ia.Count]; - for (var i = 0; i < _ia.Count; i++) psLocal[i] = (V3f)(psGlobal[_ia[i]] - center); // relative to center - } - else - { - psLocal = new V3f[psGlobal.Count]; - for (var i = 0; i < psGlobal.Count; i++) psLocal[i] = (V3f)(psGlobal[i] - center); // relative to center - } + resultData = resultData + .Add(Octree.PositionsLocal3f, psLocal) + .Add(Octree.PointCountCell, psLocal.Length) + .Add(Octree.PointCountTreeLeafs, (long)psLocal.Length) + .Add(Octree.BoundingBoxExactLocal, bbExactLocal) + .Add(Octree.BoundingBoxExactGlobal, (Box3d)bbExactLocal + center) + ; - var bbExactLocal = new Box3f(psLocal); + #endregion - resultData = resultData - .Add(Octree.PositionsLocal3f, psLocal) - .Add(Octree.PointCountCell, psLocal.Length) - .Add(Octree.PointCountTreeLeafs, (long)psLocal.Length) - .Add(Octree.BoundingBoxExactLocal, bbExactLocal) - .Add(Octree.BoundingBoxExactGlobal, (Box3d)bbExactLocal + center) - ; + #region per-point attribute arrays - #endregion + var handledPerPointAttributeArrays = new List(); + foreach (var k in notHandled) + { + if (StoreAsReference.TryGetValue(k, out var refdef)) + { + var xs = (Array)_octree.m_data[k]; + if (_ia != null) xs = xs.Subset(_ia); - #region per-point attribute arrays + // store separately and reference by id ... + var id = Guid.NewGuid(); + storage.Add(id, xs); + resultData = resultData.Add(refdef, id); - var handledPerPointAttributeArrays = new List(); - foreach (var k in notHandled) - { - if (StoreAsReference.TryGetValue(k, out var refdef)) - { - var xs = (Array)_octree.m_data[k]; - if (_ia != null) xs = xs.Subset(_ia); - - // store separately and reference by id ... - var id = Guid.NewGuid(); - storage.Add(id, xs); - resultData = resultData.Add(refdef, id); - - handledPerPointAttributeArrays.Add(k); - } + handledPerPointAttributeArrays.Add(k); } - foreach (var k in handledPerPointAttributeArrays) notHandled.Remove(k); + } + foreach (var k in handledPerPointAttributeArrays) notHandled.Remove(k); - #endregion + #endregion - #region other well-known entries + #region other well-known entries - void copy(Def def) + void copy(Def def) + { + if (_octree.m_data.TryGetValue(def, out var o)) { - if (_octree.m_data.TryGetValue(def, out var o)) - { - resultData = resultData.Add(def, o); - notHandled.Remove(def); - } + resultData = resultData.Add(def, o); + notHandled.Remove(def); } + } - copy(Octree.PartIndexRange); - copy(Octree.PerCellPartIndex1i); - copy(Octree.PerCellPartIndex1ui); + copy(Octree.PartIndexRange); + copy(Octree.PerCellPartIndex1i); + copy(Octree.PerCellPartIndex1ui); - #endregion + #endregion - if (notHandled.Count > 0) throw new Exception( - $"Unhandled entries {string.Join(", ", notHandled.Select(x => x.ToString()))}. " + - $"Invariant 42656f92-f5ac-43ca-a1b7-c7ec95fe9cb3." - ); + if (notHandled.Count > 0) throw new Exception( + $"Unhandled entries {string.Join(", ", notHandled.Select(x => x.ToString()))}. " + + $"Invariant 42656f92-f5ac-43ca-a1b7-c7ec95fe9cb3." + ); - // create and store result - var result = new PointSetNode(resultData, storage, writeToStore: true); - if (storage.GetPointCloudNode(result.Id) == null) throw new InvalidOperationException("Invariant 9e863bc5-e9f4-4d39-bd53-3d81e12af6b1."); - return result; - } - else - { - // inner node ... + // create and store result + var result = new PointSetNode(resultData, storage, writeToStore: true); + if (storage.GetPointCloudNode(result.Id) == null) throw new InvalidOperationException("Invariant 9e863bc5-e9f4-4d39-bd53-3d81e12af6b1."); + return result; + } + else + { + // inner node ... - var subcells = _subnodes?.Map(x => x?.ToPointSetNode(storage, isTemporaryImportNode)); - var subcellIds = subcells?.Map(x => x?.Id); + var subcells = _subnodes?.Map(x => x?.ToPointSetNode(storage, isTemporaryImportNode)); + var subcellIds = subcells?.Map(x => x?.Id); - var pointCountTreeLeafs = subcells.Sum(n => n != null ? n.PointCountTree : 0); - var bbExactGlobal = new Box3d(subcells.Where(x => x != null).Select(x => x!.BoundingBoxExactGlobal)); - var bbExactLocal = (Box3f)(bbExactGlobal - center); + var pointCountTreeLeafs = subcells.Sum(n => n != null ? n.PointCountTree : 0); + var bbExactGlobal = new Box3d(subcells.Where(x => x != null).Select(x => x!.BoundingBoxExactGlobal)); + var bbExactLocal = (Box3f)(bbExactGlobal - center); - resultData = resultData - .Add(Octree.PointCountTreeLeafs, pointCountTreeLeafs) - .Add(Octree.BoundingBoxExactLocal, bbExactLocal) - .Add(Octree.BoundingBoxExactGlobal, bbExactGlobal) - .Add(Octree.SubnodesGuids, subcellIds.Map(x => x ?? Guid.Empty)) - ; + resultData = resultData + .Add(Octree.PointCountTreeLeafs, pointCountTreeLeafs) + .Add(Octree.BoundingBoxExactLocal, bbExactLocal) + .Add(Octree.BoundingBoxExactGlobal, bbExactGlobal) + .Add(Octree.SubnodesGuids, subcellIds.Map(x => x ?? Guid.Empty)) + ; #if DEBUG - // check if subnodes exist in store - for (var i = 0; i < 8; i++) + // check if subnodes exist in store + for (var i = 0; i < 8; i++) + { + var x = subcellIds![i]; + if (x.HasValue) { - var x = subcellIds![i]; - if (x.HasValue) - { - var id = x.Value; - if (storage.GetPointCloudNode(id) == null) throw new InvalidOperationException("Invariant 01830b8b-3c0e-4a8b-a1bd-bfd1b1be1844."); - } + var id = x.Value; + if (storage.GetPointCloudNode(id) == null) throw new InvalidOperationException("Invariant 01830b8b-3c0e-4a8b-a1bd-bfd1b1be1844."); } + } #endif - // collect part-index-ranges from subnodes + // collect part-index-ranges from subnodes + { + var subRanges = subcells.Select(x => PartIndexUtils.GetRange(x?.Properties)); + var partIndexRange = PartIndexUtils.MergeRanges(subRanges); + if (partIndexRange.HasValue) { - var subRanges = subcells.Select(x => PartIndexUtils.GetRange(x?.Properties)); - var partIndexRange = PartIndexUtils.MergeRanges(subRanges); - if (partIndexRange.HasValue) - { - resultData = resultData.Add(Octree.PartIndexRange, partIndexRange.Value); - } + resultData = resultData.Add(Octree.PartIndexRange, partIndexRange.Value); } - - // create and store result - var result = new PointSetNode(resultData, storage, writeToStore: true); - if (storage.GetPointCloudNode(result.Id) == null) throw new InvalidOperationException("Invariant 7b09eccb-b6a0-4b99-be7a-eeff53b6a98b."); - return result; } + + // create and store result + var result = new PointSetNode(resultData, storage, writeToStore: true); + if (storage.GetPointCloudNode(result.Id) == null) throw new InvalidOperationException("Invariant 7b09eccb-b6a0-4b99-be7a-eeff53b6a98b."); + return result; + } + } + + public Node Insert(int index) + { + if (_subnodes != null) + { + var p = _octree.m_ps[index]; + var si = GetSubIndex(p); + if (_subnodes[si] == null) _subnodes[si] = new Node(_octree, _cell.GetOctant(si)); + return _subnodes[si].Insert(index); } - - public Node Insert(int index) + else { - if (_subnodes != null) + if (_ia == null) { - var p = _octree.m_ps[index]; - var si = GetSubIndex(p); - if (_subnodes[si] == null) _subnodes[si] = new Node(_octree, _cell.GetOctant(si)); - return _subnodes[si].Insert(index); + _ia = []; } else { - if (_ia == null) - { - _ia = new List(); - } - else + if (_octree.m_ps[index] == _octree.m_ps[_ia[0]]) { - if (_octree.m_ps[index] == _octree.m_ps[_ia[0]]) - { - // duplicate -> do not add - return this; - } + // duplicate -> do not add + return this; } - - _ia.Add(index); - - if (_ia.Count > _octree.m_splitLimit) - { - Split(); - } - - return this; } + + _ia.Add(index); + + if (_ia.Count > _octree.m_splitLimit) + { + Split(); + } + + return this; } - - private Node Split() - { - if (_ia == null) throw new Exception($"Expected index array. Error 809d08b1-e5d8-4ef2-9c46-67a4415173a3."); + } + + private Node Split() + { + if (_ia == null) throw new Exception($"Expected index array. Error 809d08b1-e5d8-4ef2-9c46-67a4415173a3."); #if DEBUG - var ps = _ia.Map(i => _octree.m_ps[i]).ToArray(); - foreach (var p in ps) + var ps = _ia.Map(i => _octree.m_ps[i]).ToArray(); + foreach (var p in ps) + { + if (!BoundingBox.Contains(p)) { - if (!BoundingBox.Contains(p)) - { - throw new InvalidDataException($"{p} is not contained in {BoundingBox}"); - } + throw new InvalidDataException($"{p} is not contained in {BoundingBox}"); } + } #endif - var imax = _ia.Count; - if (imax <= _octree.m_splitLimit) throw new InvalidOperationException(); - if (_subnodes != null) throw new InvalidOperationException(); + var imax = _ia.Count; + if (imax <= _octree.m_splitLimit) throw new InvalidOperationException(); + if (_subnodes != null) throw new InvalidOperationException(); - _subnodes = new Node[8]; + _subnodes = new Node[8]; - for (var i = 0; i < imax; i++) - { - var pointIndex = _ia[i]; - var si = GetSubIndex(_octree.m_ps[pointIndex]); - if (_subnodes[si] == null) _subnodes[si] = new Node(_octree, _cell.GetOctant(si)); - _subnodes[si].Insert(pointIndex); - } + for (var i = 0; i < imax; i++) + { + var pointIndex = _ia[i]; + var si = GetSubIndex(_octree.m_ps[pointIndex]); + if (_subnodes[si] == null) _subnodes[si] = new Node(_octree, _cell.GetOctant(si)); + _subnodes[si].Insert(pointIndex); + } #if DEBUG - var subnodeCount = _subnodes.Count(x => x != null); - if (subnodeCount == 0) throw new InvalidOperationException(); + var subnodeCount = _subnodes.Count(x => x != null); + if (subnodeCount == 0) throw new InvalidOperationException(); #endif - _ia = null; - return this; - } + _ia = null; + return this; + } - private int GetSubIndex(V3d p) - { - var i = 0; - if (p.X >= _centerX) i = 1; - if (p.Y >= _centerY) i |= 2; - if (p.Z >= _centerZ) i |= 4; - return i; - } + private int GetSubIndex(V3d p) + { + var i = 0; + if (p.X >= _centerX) i = 1; + if (p.Y >= _centerY) i |= 2; + if (p.Z >= _centerZ) i |= 4; + return i; } } } diff --git a/src/Aardvark.Geometry.PointSet/Octrees/Inline.cs b/src/Aardvark.Geometry.PointSet/Octrees/Inline.cs index 48866103..ce2834cf 100644 --- a/src/Aardvark.Geometry.PointSet/Octrees/Inline.cs +++ b/src/Aardvark.Geometry.PointSet/Octrees/Inline.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -20,507 +20,506 @@ You should have received a copy of the GNU Affero General Public License #nullable enable -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +public class InlineConfig { - public class InlineConfig - { - /// - /// Collapse child nodes making each node appr. 8 times as big. - /// E.g. for an octree with split limit 8192, this would result in an octree with split limit 65536. - /// - public bool Collapse { get; } - - /// - /// GZip inlined node. - /// - public bool GZipped { get; } - - /// - /// Optionally round positions to given number of digits. - /// - public int? PositionsRoundedToNumberOfDigits { get; } - - /// - /// Progress callback [0,1]. - /// - public Action? Progress { get; } - - /// - /// Collapse child nodes making each node appr. 8 times as big. - /// GZip inlined node. - /// Optionally round positions to given number of digits. - /// Progress callback [0,1]. - public InlineConfig(bool collapse, bool gzipped, int? positionsRoundedToNumberOfDigits, Action? progress) - { - Collapse = collapse; - GZipped = gzipped; - PositionsRoundedToNumberOfDigits = positionsRoundedToNumberOfDigits; - Progress = progress; - } + /// + /// Collapse child nodes making each node appr. 8 times as big. + /// E.g. for an octree with split limit 8192, this would result in an octree with split limit 65536. + /// + public bool Collapse { get; } - /// - /// Collapse child nodes making each node appr. 8 times as big. - /// GZip inlined node. - /// Progress callback [0,1]. - public InlineConfig(bool collapse, bool gzipped, Action? progress) : this(collapse, gzipped, null, progress) { } + /// + /// GZip inlined node. + /// + public bool GZipped { get; } - /// - /// Collapse child nodes making each node appr. 8 times as big. - /// GZip inlined node. - public InlineConfig(bool collapse, bool gzipped) : this(collapse, gzipped, null, null) { } - } + /// + /// Optionally round positions to given number of digits. + /// + public int? PositionsRoundedToNumberOfDigits { get; } /// - /// Compact representation of an octree node, without references to external data (except subnodes). + /// Progress callback [0,1]. /// - public class InlinedNode + public Action? Progress { get; } + + /// + /// Collapse child nodes making each node appr. 8 times as big. + /// GZip inlined node. + /// Optionally round positions to given number of digits. + /// Progress callback [0,1]. + public InlineConfig(bool collapse, bool gzipped, int? positionsRoundedToNumberOfDigits, Action? progress) { - public Guid NodeId { get; } - public Cell Cell { get; } - public Box3d BoundingBoxExactGlobal { get; } - public Guid[]? SubnodesGuids { get; } - public int PointCountCell { get; } - public long PointCountTreeLeafs { get; } - public V3f[] PositionsLocal3f { get; } - public C3b[]? Colors3b { get; } - public byte[]? Classifications1b { get; } - public byte[]? Intensities1b { get; } - - public InlinedNode( - Guid nodeId, Cell cell, Box3d boundingBoxExactGlobal, - Guid[]? subnodesGuids, - int pointCountCell, long pointCountTreeLeafs, - V3f[] positionsLocal3f, C3b[]? colors3b, byte[]? classifications1b, byte[]? intensities1b - ) - { - NodeId = nodeId; - Cell = cell; - BoundingBoxExactGlobal = boundingBoxExactGlobal; - SubnodesGuids = subnodesGuids; - PointCountCell = pointCountCell; - PointCountTreeLeafs = pointCountTreeLeafs; - PositionsLocal3f = positionsLocal3f; - Colors3b = colors3b; - Classifications1b = classifications1b; - Intensities1b = intensities1b; - } + Collapse = collapse; + GZipped = gzipped; + PositionsRoundedToNumberOfDigits = positionsRoundedToNumberOfDigits; + Progress = progress; + } - public InlinedNode(byte[] buffer, bool gzipped) - { - if (gzipped) buffer = StorageExtensions.UnGZip(buffer); - var map = buffer.DurableDecode>(); - - NodeId = (Guid)map[Durable.Octree.NodeId]; - Cell = (Cell)map[Durable.Octree.Cell]; - BoundingBoxExactGlobal = (Box3d)map[Durable.Octree.BoundingBoxExactGlobal]; - SubnodesGuids = map.TryGetValue(Durable.Octree.SubnodesGuids, out var gs) ? (Guid[]?)gs : null; - PointCountCell = (int)map[Durable.Octree.PointCountCell]; - PointCountTreeLeafs = (long)map[Durable.Octree.PointCountTreeLeafs]; - PositionsLocal3f = (V3f[])map[Durable.Octree.PositionsLocal3f]; - Colors3b = map.TryGetValue(Durable.Octree.Colors3b, out var cs) ? (C3b[]?)cs : null; - Classifications1b = map.TryGetValue(Durable.Octree.Classifications1b, out var ks) ? (byte[]?)ks : null; - Intensities1b = map.TryGetValue(Durable.Octree.Intensities1b, out var js) ? (byte[]?)js : null; - } + /// + /// Collapse child nodes making each node appr. 8 times as big. + /// GZip inlined node. + /// Progress callback [0,1]. + public InlineConfig(bool collapse, bool gzipped, Action? progress) : this(collapse, gzipped, null, progress) { } - // DO NOT REMOVE -> backwards compatibility - [Obsolete("Use other constructor instead.")] - public InlinedNode( - Guid nodeId, Cell cell, Box3d boundingBoxExactGlobal, - Guid[]? subnodesGuids, - int pointCountCell, long pointCountTreeLeafs, - V3f[] positionsLocal3f, C3b[]? colors3b - ) - : this(nodeId, cell, boundingBoxExactGlobal, subnodesGuids, pointCountCell, pointCountTreeLeafs, positionsLocal3f, colors3b, classifications1b: null, intensities1b: null) - { } - - public V3d[] PositionsGlobal3d - { - get - { - var c = Cell.GetCenter(); - return PositionsLocal3f.Map(p => c + (V3d)p); - } - } + /// + /// Collapse child nodes making each node appr. 8 times as big. + /// GZip inlined node. + public InlineConfig(bool collapse, bool gzipped) : this(collapse, gzipped, null, null) { } +} + +/// +/// Compact representation of an octree node, without references to external data (except subnodes). +/// +public class InlinedNode +{ + public Guid NodeId { get; } + public Cell Cell { get; } + public Box3d BoundingBoxExactGlobal { get; } + public Guid[]? SubnodesGuids { get; } + public int PointCountCell { get; } + public long PointCountTreeLeafs { get; } + public V3f[] PositionsLocal3f { get; } + public C3b[]? Colors3b { get; } + public byte[]? Classifications1b { get; } + public byte[]? Intensities1b { get; } + + public InlinedNode( + Guid nodeId, Cell cell, Box3d boundingBoxExactGlobal, + Guid[]? subnodesGuids, + int pointCountCell, long pointCountTreeLeafs, + V3f[] positionsLocal3f, C3b[]? colors3b, byte[]? classifications1b, byte[]? intensities1b + ) + { + NodeId = nodeId; + Cell = cell; + BoundingBoxExactGlobal = boundingBoxExactGlobal; + SubnodesGuids = subnodesGuids; + PointCountCell = pointCountCell; + PointCountTreeLeafs = pointCountTreeLeafs; + PositionsLocal3f = positionsLocal3f; + Colors3b = colors3b; + Classifications1b = classifications1b; + Intensities1b = intensities1b; + } - public List> ToDurableMap() + public InlinedNode(byte[] buffer, bool gzipped) + { + if (gzipped) buffer = StorageExtensions.UnGZip(buffer); + var map = buffer.DurableDecode>(); + + NodeId = (Guid)map[Durable.Octree.NodeId]; + Cell = (Cell)map[Durable.Octree.Cell]; + BoundingBoxExactGlobal = (Box3d)map[Durable.Octree.BoundingBoxExactGlobal]; + SubnodesGuids = map.TryGetValue(Durable.Octree.SubnodesGuids, out var gs) ? (Guid[]?)gs : null; + PointCountCell = (int)map[Durable.Octree.PointCountCell]; + PointCountTreeLeafs = (long)map[Durable.Octree.PointCountTreeLeafs]; + PositionsLocal3f = (V3f[])map[Durable.Octree.PositionsLocal3f]; + Colors3b = map.TryGetValue(Durable.Octree.Colors3b, out var cs) ? (C3b[]?)cs : null; + Classifications1b = map.TryGetValue(Durable.Octree.Classifications1b, out var ks) ? (byte[]?)ks : null; + Intensities1b = map.TryGetValue(Durable.Octree.Intensities1b, out var js) ? (byte[]?)js : null; + } + + // DO NOT REMOVE -> backwards compatibility + [Obsolete("Use other constructor instead.")] + public InlinedNode( + Guid nodeId, Cell cell, Box3d boundingBoxExactGlobal, + Guid[]? subnodesGuids, + int pointCountCell, long pointCountTreeLeafs, + V3f[] positionsLocal3f, C3b[]? colors3b + ) + : this(nodeId, cell, boundingBoxExactGlobal, subnodesGuids, pointCountCell, pointCountTreeLeafs, positionsLocal3f, colors3b, classifications1b: null, intensities1b: null) + { } + + public V3d[] PositionsGlobal3d + { + get { - var result = new List>(); + var c = Cell.GetCenter(); + return PositionsLocal3f.Map(p => c + (V3d)p); + } + } - void AddResultEntry(Durable.Def def, object o) => result.Add(new KeyValuePair(def, o)); + public List> ToDurableMap() + { + var result = new List>(); - AddResultEntry(Durable.Octree.NodeId, NodeId); - AddResultEntry(Durable.Octree.Cell, Cell); - AddResultEntry(Durable.Octree.BoundingBoxExactGlobal, BoundingBoxExactGlobal); + void AddResultEntry(Durable.Def def, object o) => result.Add(new KeyValuePair(def, o)); - if (SubnodesGuids != null) - { - AddResultEntry(Durable.Octree.SubnodesGuids, SubnodesGuids); - } + AddResultEntry(Durable.Octree.NodeId, NodeId); + AddResultEntry(Durable.Octree.Cell, Cell); + AddResultEntry(Durable.Octree.BoundingBoxExactGlobal, BoundingBoxExactGlobal); - AddResultEntry(Durable.Octree.PointCountCell, PointCountCell); - AddResultEntry(Durable.Octree.PointCountTreeLeafs, PointCountTreeLeafs); - AddResultEntry(Durable.Octree.PointCountTreeLeafsFloat64, (double)PointCountTreeLeafs); - AddResultEntry(Durable.Octree.PositionsLocal3f, PositionsLocal3f); + if (SubnodesGuids != null) + { + AddResultEntry(Durable.Octree.SubnodesGuids, SubnodesGuids); + } - //if (node.TryGetValue(Durable.Octree.Normals3fReference, out var nsRef)) - //{ - // var ns = storage.GetV3fArray(((Guid)nsRef).ToString()); - // yield return Entry(Durable.Octree.Normals3f, ns); - //} + AddResultEntry(Durable.Octree.PointCountCell, PointCountCell); + AddResultEntry(Durable.Octree.PointCountTreeLeafs, PointCountTreeLeafs); + AddResultEntry(Durable.Octree.PointCountTreeLeafsFloat64, (double)PointCountTreeLeafs); + AddResultEntry(Durable.Octree.PositionsLocal3f, PositionsLocal3f); - if (Colors3b != null) - { - AddResultEntry(Durable.Octree.Colors3b, Colors3b); - } + //if (node.TryGetValue(Durable.Octree.Normals3fReference, out var nsRef)) + //{ + // var ns = storage.GetV3fArray(((Guid)nsRef).ToString()); + // yield return Entry(Durable.Octree.Normals3f, ns); + //} - if (Classifications1b != null) - { - AddResultEntry(Durable.Octree.Classifications1b, Classifications1b); - } + if (Colors3b != null) + { + AddResultEntry(Durable.Octree.Colors3b, Colors3b); + } - if (Intensities1b != null) - { - AddResultEntry(Durable.Octree.Intensities1b, Intensities1b); - } + if (Classifications1b != null) + { + AddResultEntry(Durable.Octree.Classifications1b, Classifications1b); + } - return result; + if (Intensities1b != null) + { + AddResultEntry(Durable.Octree.Intensities1b, Intensities1b); } - /// - /// Binary encodes (and optionally gzips) this InlinedNode as a Durable.Octree.Node. - /// - public byte[] Encode(bool gzip) => this.ToDurableMap().DurableEncode(Durable.Octree.Node, gzip); + return result; } /// - /// Set of inlined nodes and related metadata. + /// Binary encodes (and optionally gzips) this InlinedNode as a Durable.Octree.Node. /// - public class InlinedNodes + public byte[] Encode(bool gzip) => this.ToDurableMap().DurableEncode(Durable.Octree.Node, gzip); +} + +/// +/// Set of inlined nodes and related metadata. +/// +public class InlinedNodes +{ + public InlineConfig Config { get; } + public InlinedNode Root { get; } + public IEnumerable Nodes { get; } + public long TotalNodeCount { get; } + + public Box3d BoundingBoxExactGlobal => Root.BoundingBoxExactGlobal; + public Cell Cell => Root.Cell; + public long PointCountTreeLeafs => Root.PointCountTreeLeafs; + public IPointCloudNode NativeRootNode { get; } + public Guid RootId => Root.NodeId; + public V3d Centroid { get; } + public double CentroidStdDev { get; } + + public InlinedNodes(InlineConfig config, IPointCloudNode nativeRootNode, InlinedNode root, IEnumerable nodes, long totalNodeCount) { - public InlineConfig Config { get; } - public InlinedNode Root { get; } - public IEnumerable Nodes { get; } - public long TotalNodeCount { get; } - - public Box3d BoundingBoxExactGlobal => Root.BoundingBoxExactGlobal; - public Cell Cell => Root.Cell; - public long PointCountTreeLeafs => Root.PointCountTreeLeafs; - public IPointCloudNode NativeRootNode { get; } - public Guid RootId => Root.NodeId; - public V3d Centroid { get; } - public double CentroidStdDev { get; } - - public InlinedNodes(InlineConfig config, IPointCloudNode nativeRootNode, InlinedNode root, IEnumerable nodes, long totalNodeCount) - { - Config = config; - NativeRootNode = nativeRootNode; - Root = root; - Nodes = nodes; - TotalNodeCount = totalNodeCount; - - var center = root.Cell.GetCenter(); - var centroid = root.PositionsLocal3f.ComputeCentroid(); - Centroid = (V3d)centroid + center; - CentroidStdDev = (root.PositionsLocal3f.Sum(p => (p - centroid).LengthSquared) / root.PositionsLocal3f.Length).Sqrt(); - } + Config = config; + NativeRootNode = nativeRootNode; + Root = root; + Nodes = nodes; + TotalNodeCount = totalNodeCount; + + var center = root.Cell.GetCenter(); + var centroid = root.PositionsLocal3f.ComputeCentroid(); + Centroid = (V3d)centroid + center; + CentroidStdDev = (root.PositionsLocal3f.Sum(p => (p - centroid).LengthSquared) / root.PositionsLocal3f.Length).Sqrt(); } +} +/// +/// +public static class InlineExtensions +{ /// + /// Enumerate inlined (self-contained, no external data is referenced) octree nodes. /// - public static class InlineExtensions + public static InlinedNodes EnumerateOctreeInlined( + this Storage storage, string key, InlineConfig config + ) { - /// - /// Enumerate inlined (self-contained, no external data is referenced) octree nodes. - /// - public static InlinedNodes EnumerateOctreeInlined( - this Storage storage, string key, InlineConfig config - ) + if (storage.TryGetOctree(key, out var root)) { - if (storage.TryGetOctree(key, out var root)) - { - return root.EnumerateOctreeInlined(config); - } - else - { - throw new InvalidOperationException( - $"Key {key} not found in store. Invariant bca69ffa-8a8e-430d-a588-9f2fbbf1c43d." - ); - } + return root.EnumerateOctreeInlined(config); } - - /// - /// Enumerate inlined (self-contained, no external data is referenced) octree nodes. - /// - public static InlinedNodes EnumerateOctreeInlined( - this Storage storage, Guid key, InlineConfig config - ) - => EnumerateOctreeInlined(storage, key.ToString(), config); - - - /// - /// Enumerate inlined (self-contained, no external data is referenced) octree nodes. - /// - public static InlinedNodes EnumerateOctreeInlined( - this PointSet pointset, InlineConfig config - ) - => pointset.Root.Value!.EnumerateOctreeInlined(config); - - /// - /// Enumerate inlined (self-contained, no external data is referenced) octree nodes. - /// - public static InlinedNodes EnumerateOctreeInlined( - this IPointCloudNode root, InlineConfig config - ) + else { - if (root == null) throw new ArgumentNullException(nameof(root)); + throw new InvalidOperationException( + $"Key {key} not found in store. Invariant bca69ffa-8a8e-430d-a588-9f2fbbf1c43d." + ); + } + } - var inlinedRoot = root.ConvertToInline(config, new HashSet { root.Id }); + /// + /// Enumerate inlined (self-contained, no external data is referenced) octree nodes. + /// + public static InlinedNodes EnumerateOctreeInlined( + this Storage storage, Guid key, InlineConfig config + ) + => EnumerateOctreeInlined(storage, key.ToString(), config); - return new InlinedNodes( - config, - nativeRootNode: root, - root: inlinedRoot, - nodes: EnumerateRec(root, config), - -1 - ); - static IEnumerable EnumerateRec(IPointCloudNode root, InlineConfig config) - { - var survive = new HashSet { root.Id }; - foreach (var x in EnumerateRecImpl(root, survive, config, 0L)) yield return x; - } + /// + /// Enumerate inlined (self-contained, no external data is referenced) octree nodes. + /// + public static InlinedNodes EnumerateOctreeInlined( + this PointSet pointset, InlineConfig config + ) + => pointset.Root.Value!.EnumerateOctreeInlined(config); - static IEnumerable EnumerateRecImpl(IPointCloudNode node, HashSet survive, InlineConfig config, long processedNodeCount) - { - var isLeafNode = node.IsLeaf; + /// + /// Enumerate inlined (self-contained, no external data is referenced) octree nodes. + /// + public static InlinedNodes EnumerateOctreeInlined( + this IPointCloudNode root, InlineConfig config + ) + { + if (root == null) throw new ArgumentNullException(nameof(root)); + + var inlinedRoot = root.ConvertToInline(config, [root.Id]); + + return new InlinedNodes( + config, + nativeRootNode: root, + root: inlinedRoot, + nodes: EnumerateRec(root, config), + -1 + ); + + static IEnumerable EnumerateRec(IPointCloudNode root, InlineConfig config) + { + var survive = new HashSet { root.Id }; + foreach (var x in EnumerateRecImpl(root, survive, config, 0L)) yield return x; + } - config.Progress?.Invoke(++processedNodeCount); + static IEnumerable EnumerateRecImpl(IPointCloudNode node, HashSet survive, InlineConfig config, long processedNodeCount) + { + var isLeafNode = node.IsLeaf; + + config.Progress?.Invoke(++processedNodeCount); - if (config.Collapse && isLeafNode && !survive.Contains(node.Id)) yield break; + if (config.Collapse && isLeafNode && !survive.Contains(node.Id)) yield break; - var inline = node.ConvertToInline(config, survive); - survive.Remove(node.Id); - yield return inline; + var inline = node.ConvertToInline(config, survive); + survive.Remove(node.Id); + yield return inline; - if (node.Subnodes != null) + if (node.Subnodes != null) + { + foreach (var x in node.Subnodes) { - foreach (var x in node.Subnodes) + if (x != null && x.TryGetFromCache(out var subnode)) { - if (x != null && x.TryGetFromCache(out var subnode)) - { - foreach (var n in EnumerateRecImpl(subnode, survive, config, processedNodeCount)) yield return n; - } + foreach (var n in EnumerateRecImpl(subnode, survive, config, processedNodeCount)) yield return n; } } } } + } - #region ExportInlinedPointCloud + #region ExportInlinedPointCloud - /// - /// Inlines and exports point cloud to another store. - /// - public static void ExportInlinedPointCloud(this Storage sourceStore, string key, Storage targetStore, InlineConfig config) + /// + /// Inlines and exports point cloud to another store. + /// + public static void ExportInlinedPointCloud(this Storage sourceStore, string key, Storage targetStore, InlineConfig config) + { + if (sourceStore.TryGetOctree(key, out var root)) { - if (sourceStore.TryGetOctree(key, out var root)) - { - ExportInlinedPointCloud(root, targetStore, config); - } - else - { - throw new InvalidOperationException( - $"Key {key} not found in store. Invariant 0f7a7e31-f6c7-494b-a734-18c00dee3383." - ); - } + ExportInlinedPointCloud(root, targetStore, config); + } + else + { + throw new InvalidOperationException( + $"Key {key} not found in store. Invariant 0f7a7e31-f6c7-494b-a734-18c00dee3383." + ); } + } - /// - /// Inlines and exports pointset to another store. - /// - public static void ExportInlinedPointCloud(this Storage sourceStore, Guid key, Storage targetStore, InlineConfig config) - => ExportInlinedPointCloud(sourceStore, key.ToString(), targetStore, config); + /// + /// Inlines and exports pointset to another store. + /// + public static void ExportInlinedPointCloud(this Storage sourceStore, Guid key, Storage targetStore, InlineConfig config) + => ExportInlinedPointCloud(sourceStore, key.ToString(), targetStore, config); - /// - /// Inlines and exports pointset to another store. - /// - public static void ExportInlinedPointCloud(this IPointCloudNode root, Storage targetStore, InlineConfig config) + /// + /// Inlines and exports pointset to another store. + /// + public static void ExportInlinedPointCloud(this IPointCloudNode root, Storage targetStore, InlineConfig config) + { + Report.BeginTimed("inlining octree"); + + var totalNodeCount = root.CountNodes(outOfCore: true); + var newSplitLimit = config.Collapse ? root.PointCountCell * 8 : root.PointCountCell; + Report.Line($"root = {root.Id}"); + Report.Line($"split limit = {root.PointCountCell,36:N0}"); + Report.Line($"split limit (new) = {newSplitLimit,36:N0}"); + Report.Line($"total node count = {totalNodeCount,36:N0}"); + + // export octree + var exported = root.EnumerateOctreeInlined(config); + foreach (var x in exported.Nodes) { - Report.BeginTimed("inlining octree"); - - var totalNodeCount = root.CountNodes(outOfCore: true); - var newSplitLimit = config.Collapse ? root.PointCountCell * 8 : root.PointCountCell; - Report.Line($"root = {root.Id}"); - Report.Line($"split limit = {root.PointCountCell,36:N0}"); - Report.Line($"split limit (new) = {newSplitLimit,36:N0}"); - Report.Line($"total node count = {totalNodeCount,36:N0}"); - - // export octree - var exported = root.EnumerateOctreeInlined(config); - foreach (var x in exported.Nodes) - { - var inlined = x.ToDurableMap(); - targetStore.Add(x.NodeId, Durable.Octree.Node, inlined, config.GZipped); - } - - Report.EndTimed(); + var inlined = x.ToDurableMap(); + targetStore.Add(x.NodeId, Durable.Octree.Node, inlined, config.GZipped); } - #endregion + Report.EndTimed(); + } + + #endregion - #region Helpers + #region Helpers - private static InlinedNode ConvertToInline( - this IPointCloudNode node, - InlineConfig config, - HashSet survive - ) + private static InlinedNode ConvertToInline( + this IPointCloudNode node, + InlineConfig config, + HashSet survive + ) + { + var id = node.Id; + var cell = node.Cell; + var cellCenter = cell.GetCenter(); + var bbExactGlobal = node.BoundingBoxExactGlobal; + var pointCountCell = node.PointCountCell; + var pointCountTree = node.PointCountTree; + var hasColors = node.HasColors; + var hasClassifications = node.HasClassifications; + var hasIntensities = node.HasIntensities; + var subnodes = node.Subnodes?.Map(x => x?.Value); + var isNotLeaf = !node.IsLeaf; + + var ps = default(V3f[]); + var cs = default(C4b[]); + var ks = default(byte[]); + var js = default(byte[]); + + static byte[] rescaleIntensities(int[] js32) { - var id = node.Id; - var cell = node.Cell; - var cellCenter = cell.GetCenter(); - var bbExactGlobal = node.BoundingBoxExactGlobal; - var pointCountCell = node.PointCountCell; - var pointCountTree = node.PointCountTree; - var hasColors = node.HasColors; - var hasClassifications = node.HasClassifications; - var hasIntensities = node.HasIntensities; - var subnodes = node.Subnodes?.Map(x => x?.Value); - var isNotLeaf = !node.IsLeaf; - - var ps = default(V3f[]); - var cs = default(C4b[]); - var ks = default(byte[]); - var js = default(byte[]); - - static byte[] rescaleIntensities(int[] js32) + if (js32.Length == 0) return []; + + var min = js32.Min(); + var max = js32.Max(); + + if (min >= 0 && max <= 255) + { + return js32.Map(x => (byte)x); + } + else if (min == max) { - if (js32.Length == 0) return Array.Empty(); + return js32.Map(_ => (byte)255); + } + else + { + var f = 255.999 / (max - min); + return js32.Map(x => (byte)((x - min) * f)); + } + } - var min = js32.Min(); - var max = js32.Max(); + Guid[]? subnodeGuids = null; + if (config.Collapse && isNotLeaf) + { + if (subnodes == null) throw new Exception("Assertion failed. Error 42565d4a-2e91-4961-a310-095b503fe6f1."); + var nonEmptySubNodes = subnodes.Where(x => x != null).Select(x => x!).ToArray(); - if (min >= 0 && max <= 255) + ps = nonEmptySubNodes + .SelectMany(n => { - return js32.Map(x => (byte)x); - } - else if (min == max) - { - return js32.Map(_ => (byte)255); - } - else - { - var f = 255.999 / (max - min); - return js32.Map(x => (byte)((x - min) * f)); - } + var nCell = n.Cell; + var nCenter = nCell.GetCenter(); + var delta = nCenter - cellCenter; + var xs = n.Positions.Value.Map(x => (V3f)((V3d)x + delta)); + return xs; + }) + .ToArray(); + + if (hasColors) + { + cs = nonEmptySubNodes + .SelectMany(n => n.Colors!.Value) + .ToArray(); } - Guid[]? subnodeGuids = null; - if (config.Collapse && isNotLeaf) + if (hasClassifications) { - if (subnodes == null) throw new Exception("Assertion failed. Error 42565d4a-2e91-4961-a310-095b503fe6f1."); - var nonEmptySubNodes = subnodes.Where(x => x != null).Select(x => x!).ToArray(); - - ps = nonEmptySubNodes - .SelectMany(n => - { - var nCell = n.Cell; - var nCenter = nCell.GetCenter(); - var delta = nCenter - cellCenter; - var xs = n.Positions.Value.Map(x => (V3f)((V3d)x + delta)); - return xs; - }) + ks = nonEmptySubNodes + .SelectMany(n => n.Classifications!.Value) .ToArray(); + } - if (hasColors) - { - cs = nonEmptySubNodes - .SelectMany(n => n.Colors!.Value) - .ToArray(); - } + if (hasIntensities) + { + var js32 = nonEmptySubNodes + .SelectMany(n => n.Intensities!.Value) + .ToArray(); - if (hasClassifications) - { - ks = nonEmptySubNodes - .SelectMany(n => n.Classifications!.Value) - .ToArray(); - } + js = rescaleIntensities(js32); + } - if (hasIntensities) + var guids2 = subnodes + .Map(nref => { - var js32 = nonEmptySubNodes - .SelectMany(n => n.Intensities!.Value) - .ToArray(); - - js = rescaleIntensities(js32); - } - - var guids2 = subnodes - .Map(nref => + if (nref != null) { - if (nref != null) - { - var n = nref!; - return !n.IsLeaf ? n.Id : Guid.Empty; - } - else - { - return Guid.Empty; - } - }); - - var isNewLeaf = guids2.All(k => k == Guid.Empty); - if (!isNewLeaf) - { - subnodeGuids = subnodes.Map(x => x != null ? x.Id : Guid.Empty); - foreach (var g in nonEmptySubNodes) survive.Add(g.Id); - } + var n = nref!; + return !n.IsLeaf ? n.Id : Guid.Empty; + } + else + { + return Guid.Empty; + } + }); + + var isNewLeaf = guids2.All(k => k == Guid.Empty); + if (!isNewLeaf) + { + subnodeGuids = subnodes.Map(x => x != null ? x.Id : Guid.Empty); + foreach (var g in nonEmptySubNodes) survive.Add(g.Id); } - else + } + else + { + if (isNotLeaf) { - if (isNotLeaf) - { - subnodeGuids = subnodes.Map(x => x != null ? x.Id : Guid.Empty); - } - - ps = node.Positions.Value!; - if (hasColors) cs = node.Colors!.Value; - if (hasClassifications) ks = node.Classifications!.Value; - if (hasIntensities) js = rescaleIntensities(node.Intensities!.Value!); - if (isNotLeaf) subnodeGuids = subnodes.Map(x => x != null ? x.Id : Guid.Empty); + subnodeGuids = subnodes.Map(x => x != null ? x.Id : Guid.Empty); } + ps = node.Positions.Value!; + if (hasColors) cs = node.Colors!.Value; + if (hasClassifications) ks = node.Classifications!.Value; + if (hasIntensities) js = rescaleIntensities(node.Intensities!.Value!); + if (isNotLeaf) subnodeGuids = subnodes.Map(x => x != null ? x.Id : Guid.Empty); + } - // fix color array if it has inconsistent length - // (might have been created by an old Aardvark.Geometry.PointSet version) - if (hasColors && cs!.Length != ps.Length) - { - Report.ErrorNoPrefix($"[ConvertToInline] inconsistent length: {ps.Length} positions, but {cs.Length} colors."); - var csFixed = new C4b[ps.Length]; - if (csFixed.Length > 0) - { - var lastColor = cs[cs.Length - 1]; - var imax = Math.Min(ps.Length, cs.Length); - for (var i = 0; i < imax; i++) csFixed[i] = cs[i]; - for (var i = imax; i < ps.Length; i++) csFixed[i] = lastColor; - } - cs = csFixed; - } + // fix color array if it has inconsistent length + // (might have been created by an old Aardvark.Geometry.PointSet version) + if (hasColors && cs!.Length != ps.Length) + { + Report.ErrorNoPrefix($"[ConvertToInline] inconsistent length: {ps.Length} positions, but {cs.Length} colors."); - // optionally round positions - if (config.PositionsRoundedToNumberOfDigits.HasValue) + var csFixed = new C4b[ps.Length]; + if (csFixed.Length > 0) { - ps = ps.Map(x => x.Round(config.PositionsRoundedToNumberOfDigits.Value)); + var lastColor = cs[cs.Length - 1]; + var imax = Math.Min(ps.Length, cs.Length); + for (var i = 0; i < imax; i++) csFixed[i] = cs[i]; + for (var i = imax; i < ps.Length; i++) csFixed[i] = lastColor; } + cs = csFixed; + } - // result - pointCountCell = ps.Length; - var cs3b = cs?.Map(x => new C3b(x)); - var result = new InlinedNode(id, cell, bbExactGlobal, subnodeGuids, pointCountCell, pointCountTree, ps, cs3b, ks, js); - return result; + // optionally round positions + if (config.PositionsRoundedToNumberOfDigits.HasValue) + { + ps = ps.Map(x => x.Round(config.PositionsRoundedToNumberOfDigits.Value)); } - #endregion + // result + pointCountCell = ps.Length; + var cs3b = cs?.Map(x => new C3b(x)); + var result = new InlinedNode(id, cell, bbExactGlobal, subnodeGuids, pointCountCell, pointCountTree, ps, cs3b, ks, js); + return result; } + + #endregion } diff --git a/src/Aardvark.Geometry.PointSet/Octrees/Lod.cs b/src/Aardvark.Geometry.PointSet/Octrees/Lod.cs index ea4fd250..2f5ba709 100644 --- a/src/Aardvark.Geometry.PointSet/Octrees/Lod.cs +++ b/src/Aardvark.Geometry.PointSet/Octrees/Lod.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -22,539 +22,538 @@ You should have received a copy of the GNU Affero General Public License using System.Threading; using System.Threading.Tasks; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +public static class LodExtensions { - public static class LodExtensions + private static double[] ComputeLodFractions(long[] counts) { - private static double[] ComputeLodFractions(long[] counts) - { - if (counts.Length != 8) throw new ArgumentOutOfRangeException(nameof(counts)); + if (counts.Length != 8) throw new ArgumentOutOfRangeException(nameof(counts)); - var sum = 0L; - for (var i = 0; i < 8; i++) sum += counts[i]; + var sum = 0L; + for (var i = 0; i < 8; i++) sum += counts[i]; - var fractions = new double[8]; - for (var i = 0; i < 8; i++) fractions[i] = counts[i] / (double)sum; + var fractions = new double[8]; + for (var i = 0; i < 8; i++) fractions[i] = counts[i] / (double)sum; - return fractions; - } + return fractions; + } - internal static double[] ComputeLodFractions(IPointCloudNode?[] subnodes) - { - if (subnodes.Length != 8) throw new ArgumentOutOfRangeException(nameof(subnodes)); + internal static double[] ComputeLodFractions(IPointCloudNode?[] subnodes) + { + if (subnodes.Length != 8) throw new ArgumentOutOfRangeException(nameof(subnodes)); - var counts = new long[8]; - for (var i = 0; i < 8; i++) counts[i] = subnodes[i] != null ? subnodes[i]!.PointCountTree : 0; - return ComputeLodFractions(counts); - } + var counts = new long[8]; + for (var i = 0; i < 8; i++) counts[i] = subnodes[i] != null ? subnodes[i]!.PointCountTree : 0; + return ComputeLodFractions(counts); + } + + internal static int[] ComputeLodCounts(int aggregateCount, double[] fractions) + { + //if (fractions == null) return null; + if (fractions.Length != 8) throw new ArgumentOutOfRangeException(nameof(fractions)); - internal static int[] ComputeLodCounts(int aggregateCount, double[] fractions) + var remainder = 0.1; + var counts = new int[8]; + for (var i = 0; i < 8; i++) { - //if (fractions == null) return null; - if (fractions.Length != 8) throw new ArgumentOutOfRangeException(nameof(fractions)); + var fn = aggregateCount * fractions[i] + remainder; + var n = (int)fn; + remainder = fn - n; + counts[i] = n; + }; - var remainder = 0.1; - var counts = new int[8]; - for (var i = 0; i < 8; i++) - { - var fn = aggregateCount * fractions[i] + remainder; - var n = (int)fn; - remainder = fn - n; - counts[i] = n; - }; + var e = aggregateCount - counts.Sum(); + if (e != 0) throw new InvalidOperationException(); - var e = aggregateCount - counts.Sum(); - if (e != 0) throw new InvalidOperationException(); + return counts; + } - return counts; + internal static V3f[] AggregateSubPositions(int[] counts, int aggregateCount, V3d center, V3d?[] subCenters, V3f[]?[] xss) + { + var rs = new V3f[aggregateCount]; + var i = 0; + for (var ci = 0; ci < 8; ci++) + { + if (counts[ci] == 0) continue; + var xs = xss[ci]!; + var c = subCenters[ci]!.Value; + + var jmax = xs.Length; + var dj = (jmax + 0.49) / counts[ci]; + for (var j = 0.0; j < jmax; j += dj) + { + var _p = new V3f((V3d)xs[(int)j] + c - center); + //if (_p.IsNaN) throw new Exception("Position is NaN."); + rs[i++] = _p; + } } + return rs; + } - internal static V3f[] AggregateSubPositions(int[] counts, int aggregateCount, V3d center, V3d?[] subCenters, V3f[]?[] xss) + internal static T[] AggregateSubArrays(int[] counts, int aggregateCount, T[]?[] xss) + { + var rs = new T[aggregateCount]; + var i = 0; + for (var ci = 0; ci < 8; ci++) { - var rs = new V3f[aggregateCount]; - var i = 0; - for (var ci = 0; ci < 8; ci++) - { - if (counts[ci] == 0) continue; - var xs = xss[ci]!; - var c = subCenters[ci]!.Value; + if (counts[ci] == 0) continue; + var xs = xss[ci]!; - var jmax = xs.Length; + var jmax = xs.Length; + if (jmax <= counts[ci]) + { + xs.CopyTo(0, xs.Length, rs, i); + i += xs.Length; + } + else + { var dj = (jmax + 0.49) / counts[ci]; for (var j = 0.0; j < jmax; j += dj) { - var _p = new V3f((V3d)xs[(int)j] + c - center); - //if (_p.IsNaN) throw new Exception("Position is NaN."); - rs[i++] = _p; + rs[i++] = xs[(int)j]; } } - return rs; } - internal static T[] AggregateSubArrays(int[] counts, int aggregateCount, T[]?[] xss) + if (i < aggregateCount) { - var rs = new T[aggregateCount]; - var i = 0; - for (var ci = 0; ci < 8; ci++) - { - if (counts[ci] == 0) continue; - var xs = xss[ci]!; + Array.Resize(ref rs, i); + return rs; + } + else return rs; + } - var jmax = xs.Length; - if (jmax <= counts[ci]) - { - xs.CopyTo(0, xs.Length, rs, i); - i += xs.Length; - } - else - { - var dj = (jmax + 0.49) / counts[ci]; - for (var j = 0.0; j < jmax; j += dj) - { - rs[i++] = xs[(int)j]; - } - } - } + internal static Array AggregateSubArrays(int[] counts, int splitLimit, object[] arrays) + { + //var t = arrays.First(x => x != null).GetType().GetElementType(); + return arrays.First(x => x != null) switch + { + bool[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (bool[])x)), + Guid[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (Guid[])x)), + string[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (string[])x)), + byte[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (byte[])x)), + sbyte[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (sbyte[])x)), + short[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (short[])x)), + ushort[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (ushort[])x)), + int[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (int[])x)), + uint[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (uint[])x)), + long[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (long[])x)), + ulong[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (ulong[])x)), + float[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (float[])x)), + double[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (double[])x)), + decimal[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (decimal[])x)), + V2d[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V2d[])x)), + V2f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V2f[])x)), + V2i[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V2i[])x)), + V2l[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V2l[])x)), + V3d[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V3d[])x)), + V3f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V3f[])x)), + V3i[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V3i[])x)), + V3l[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V3l[])x)), + V4d[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V4d[])x)), + V4f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V4f[])x)), + V4i[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V4i[])x)), + V4l[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V4l[])x)), + M22d[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M22d[])x)), + M22f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M22f[])x)), + M22i[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M22i[])x)), + M22l[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M22l[])x)), + M33d[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M33d[])x)), + M33f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M33f[])x)), + M33i[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M33i[])x)), + M33l[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M33l[])x)), + M44d[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M44d[])x)), + M44f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M44f[])x)), + M44i[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M44i[])x)), + M44l[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M44l[])x)), + Trafo2d[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (Trafo2d[])x)), + Trafo2f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (Trafo2f[])x)), + Trafo3d[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (Trafo3d[])x)), + Trafo3f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (Trafo3f[])x)), + C3b[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (C3b[])x)), + C3f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (C3f[])x)), + C4b[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (C4b[])x)), + C4f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (C4f[])x)), + _ => throw new Exception($"LoD aggregation for type {arrays.First(x => x != null)} not supported. Error 13c77814-f323-41fb-a7b6-c164973b7b02.") + }; + } - if (i < aggregateCount) - { - Array.Resize(ref rs, i); - return rs; - } - else return rs; - } + /// + /// + /// + /// Number of points to take from each subnode. + /// + /// + /// + internal static (object? lodQs, Range1i? lodQsRange) AggregateSubPartIndices(int[] counts, int aggregateCount, object?[] xss) + { + var result = default(object?); + var resultRange = (Range1i?)null; + var ias = new int[]?[8]; - internal static Array AggregateSubArrays(int[] counts, int splitLimit, object[] arrays) + // special case: all subnodes have identical per-cell index + if (xss.All(xs => xs is not Array)) { - //var t = arrays.First(x => x != null).GetType().GetElementType(); - return arrays.First(x => x != null) switch - { - bool[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (bool[])x)), - Guid[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (Guid[])x)), - string[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (string[])x)), - byte[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (byte[])x)), - sbyte[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (sbyte[])x)), - short[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (short[])x)), - ushort[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (ushort[])x)), - int[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (int[])x)), - uint[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (uint[])x)), - long[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (long[])x)), - ulong[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (ulong[])x)), - float[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (float[])x)), - double[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (double[])x)), - decimal[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (decimal[])x)), - V2d[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V2d[])x)), - V2f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V2f[])x)), - V2i[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V2i[])x)), - V2l[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V2l[])x)), - V3d[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V3d[])x)), - V3f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V3f[])x)), - V3i[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V3i[])x)), - V3l[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V3l[])x)), - V4d[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V4d[])x)), - V4f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V4f[])x)), - V4i[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V4i[])x)), - V4l[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (V4l[])x)), - M22d[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M22d[])x)), - M22f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M22f[])x)), - M22i[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M22i[])x)), - M22l[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M22l[])x)), - M33d[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M33d[])x)), - M33f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M33f[])x)), - M33i[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M33i[])x)), - M33l[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M33l[])x)), - M44d[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M44d[])x)), - M44f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M44f[])x)), - M44i[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M44i[])x)), - M44l[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (M44l[])x)), - Trafo2d[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (Trafo2d[])x)), - Trafo2f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (Trafo2f[])x)), - Trafo3d[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (Trafo3d[])x)), - Trafo3f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (Trafo3f[])x)), - C3b[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (C3b[])x)), - C3f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (C3f[])x)), - C4b[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (C4b[])x)), - C4f[] _ => AggregateSubArrays(counts, splitLimit, arrays.Map(x => (C4f[])x)), - _ => throw new Exception($"LoD aggregation for type {arrays.First(x => x != null)} not supported. Error 13c77814-f323-41fb-a7b6-c164973b7b02.") - }; + var perCellIndices = xss + .Where(xs => xs != null) + .Select((object? xs) => xs switch { + null => default(int?), + int x => x, + uint x => (int)x , + _ => throw new Exception($"Unexpected type {xs.GetType().FullName}. Error 41874217-05a1-482f-abb5-565cebe6a402.") + }) + .ToArray() + ; + if (perCellIndices.Length == 0) return (null, null); + var allIdentical = true; + for (var i = 1; i < perCellIndices.Length; i++) if (perCellIndices[i] != perCellIndices[0]) { allIdentical = false; break; } + if (allIdentical) return (perCellIndices[0], PartIndexUtils.GetRange(perCellIndices[0])); } - /// - /// - /// - /// Number of points to take from each subnode. - /// - /// - /// - internal static (object? lodQs, Range1i? lodQsRange) AggregateSubPartIndices(int[] counts, int aggregateCount, object?[] xss) + // standard case: + var resultLengthSoFar = 0; + for (var ci = 0; ci < 8; ci++) { - var result = default(object?); - var resultRange = (Range1i?)null; - var ias = new int[]?[8]; + if (counts[ci] == 0) continue; + var xs = xss[ci]!; - // special case: all subnodes have identical per-cell index - if (xss.All(xs => xs is not Array)) - { - var perCellIndices = xss - .Where(xs => xs != null) - .Select((object? xs) => xs switch { - null => default(int?), - int x => x, - uint x => (int)x , - _ => throw new Exception($"Unexpected type {xs.GetType().FullName}. Error 41874217-05a1-482f-abb5-565cebe6a402.") - }) - .ToArray() - ; - if (perCellIndices.Length == 0) return (null, null); - var allIdentical = true; - for (var i = 1; i < perCellIndices.Length; i++) if (perCellIndices[i] != perCellIndices[0]) { allIdentical = false; break; } - if (allIdentical) return (perCellIndices[0], PartIndexUtils.GetRange(perCellIndices[0])); - } + resultRange = PartIndexUtils.ExtendRangeBy(in resultRange, xs); - // standard case: - var resultLengthSoFar = 0; - for (var ci = 0; ci < 8; ci++) + var xsLength = xs switch { - if (counts[ci] == 0) continue; - var xs = xss[ci]!; - - resultRange = PartIndexUtils.ExtendRangeBy(in resultRange, xs); - - var xsLength = xs switch - { - null => throw new Exception("Invariant 0a65ab38-4b69-4f6d-aa54-36536c86d8d3."), - int => counts[ci], - uint => counts[ci], - byte[] ys => ys.Length, - short[] ys => ys.Length, - int[] ys => ys.Length, - _ => throw new Exception($"Unexpected type {xs.GetType().FullName}. Error 8e44dd14-b984-4d7f-8be4-c8e0d4f43189.") - }; + null => throw new Exception("Invariant 0a65ab38-4b69-4f6d-aa54-36536c86d8d3."), + int => counts[ci], + uint => counts[ci], + byte[] ys => ys.Length, + short[] ys => ys.Length, + int[] ys => ys.Length, + _ => throw new Exception($"Unexpected type {xs.GetType().FullName}. Error 8e44dd14-b984-4d7f-8be4-c8e0d4f43189.") + }; #if DEBUG - if (xsLength < counts[ci]) Debugger.Break(); // TODO "PARTINDICES" remove + if (xsLength < counts[ci]) Debugger.Break(); // TODO "PARTINDICES" remove #endif - if (xsLength == counts[ci]) - { - result = PartIndexUtils.ConcatIndices(result, resultLengthSoFar, xs, xsLength); - } - else if (xsLength > counts[ci]) - { - var ia = new List(); - var dj = (xsLength + 0.49) / counts[ci]; - for (var j = 0.0; j < xsLength; j += dj) ia.Add((int)j); + if (xsLength == counts[ci]) + { + result = PartIndexUtils.ConcatIndices(result, resultLengthSoFar, xs, xsLength); + } + else if (xsLength > counts[ci]) + { + var ia = new List(); + var dj = (xsLength + 0.49) / counts[ci]; + for (var j = 0.0; j < xsLength; j += dj) ia.Add((int)j); #if DEBUG - if (ia.Count != counts[ci]) Debugger.Break(); // TODO "PARTINDICES" remove + if (ia.Count != counts[ci]) Debugger.Break(); // TODO "PARTINDICES" remove #endif - var subset = PartIndexUtils.Subset(xs, ia); - var newResult = PartIndexUtils.ConcatIndices(result, resultLengthSoFar, subset, ia.Count); + var subset = PartIndexUtils.Subset(xs, ia); + var newResult = PartIndexUtils.ConcatIndices(result, resultLengthSoFar, subset, ia.Count); #if DEBUG - if (newResult is Array a1 && resultLengthSoFar + counts[ci] != a1.Length) Debugger.Break(); // TODO "PARTINDICES" remove + if (newResult is Array a1 && resultLengthSoFar + counts[ci] != a1.Length) Debugger.Break(); // TODO "PARTINDICES" remove #endif - result = newResult; - } - else - { - throw new Exception( - $"Expected number of sub-indices {xsLength} to be greater or equal than {counts[ci]}. " + - $"Invariant 3a9eeb72-8e56-404a-8def-7001b02c960d." - ); - } - - resultLengthSoFar += counts[ci]; + result = newResult; + } + else + { + throw new Exception( + $"Expected number of sub-indices {xsLength} to be greater or equal than {counts[ci]}. " + + $"Invariant 3a9eeb72-8e56-404a-8def-7001b02c960d." + ); } + resultLengthSoFar += counts[ci]; + } + #if DEBUG - if (result is Array a && a.Length != aggregateCount) Debugger.Break(); // TODO "PARTINDICES" remove + if (result is Array a && a.Length != aggregateCount) Debugger.Break(); // TODO "PARTINDICES" remove #endif - result = PartIndexUtils.Compact(result); + result = PartIndexUtils.Compact(result); - return (result, resultRange); - } + return (result, resultRange); + } - private static async Task GenerateLod(this PointSet self, string? key, Action callback, CancellationToken ct) + private static async Task GenerateLod(this PointSet self, string? key, Action callback, CancellationToken ct) + { + try { - try - { - key ??= Guid.NewGuid().ToString(); + key ??= Guid.NewGuid().ToString(); - if (self.IsEmpty) return self; + if (self.IsEmpty) return self; - if (self.Root.Value is not PointSetNode root) throw new InvalidOperationException( - "GenerateLod is only valid for PointSetNodes. Invariant 1d530d98-9ea4-4281-894f-bb91a6b8a2cf." - ); + if (self.Root.Value is not PointSetNode root) throw new InvalidOperationException( + "GenerateLod is only valid for PointSetNodes. Invariant 1d530d98-9ea4-4281-894f-bb91a6b8a2cf." + ); - var lod = await root.GenerateLod(self.SplitLimit, callback, ct); - var result = new PointSet(self.Storage, key, lod.Id, self.SplitLimit); - self.Storage.Add(key, result); - return result; - } - catch (Exception e) - { - Report.Error(e.ToString()); - throw; - } + var lod = await root.GenerateLod(self.SplitLimit, callback, ct); + var result = new PointSet(self.Storage, key, lod.Id, self.SplitLimit); + self.Storage.Add(key, result); + return result; + } + catch (Exception e) + { + Report.Error(e.ToString()); + throw; } + } + + /// + /// Returns new octree with LOD data created. + /// + public static PointSet GenerateLod(this PointSet self, ImportConfig config) + { + if (self.Root == null || self.Root.Value.IsEmpty) return self; - /// - /// Returns new octree with LOD data created. - /// - public static PointSet GenerateLod(this PointSet self, ImportConfig config) + var nodeCount = self.Root.Value.CountNodes(true); + var loddedNodesCount = 0L; + if (config.Verbose) Console.WriteLine(); + var result = self.GenerateLod(config.Key, () => { - if (self.Root == null || self.Root.Value.IsEmpty) return self; + config.CancellationToken.ThrowIfCancellationRequested(); + var i = Interlocked.Increment(ref loddedNodesCount); + if (config.Verbose) Console.Write($"[Lod] {i}/{nodeCount}\r"); + if (i % 100 == 0) config.ProgressCallback(loddedNodesCount / (double)nodeCount); + }, config.CancellationToken); - var nodeCount = self.Root.Value.CountNodes(true); - var loddedNodesCount = 0L; - if (config.Verbose) Console.WriteLine(); - var result = self.GenerateLod(config.Key, () => - { - config.CancellationToken.ThrowIfCancellationRequested(); - var i = Interlocked.Increment(ref loddedNodesCount); - if (config.Verbose) Console.Write($"[Lod] {i}/{nodeCount}\r"); - if (i % 100 == 0) config.ProgressCallback(loddedNodesCount / (double)nodeCount); - }, config.CancellationToken); + result.Wait(); - result.Wait(); + config.ProgressCallback(1.0); - config.ProgressCallback(1.0); + return result.Result; + } - return result.Result; - } + internal static async Task GenerateLod(this PointSetNode self, + int octreeSplitLimit, Action callback, + CancellationToken ct) + { + if (self == null) throw new ArgumentNullException(nameof(self)); + ct.ThrowIfCancellationRequested(); - internal static async Task GenerateLod(this PointSetNode self, - int octreeSplitLimit, Action callback, - CancellationToken ct) + try { - if (self == null) throw new ArgumentNullException(nameof(self)); - ct.ThrowIfCancellationRequested(); - - try + var store = self.Storage; + var originalId = self.Id; + var upsertData = ImmutableDictionary.Empty; + + ////////////////////////////////////////////////////////////////// + // for leafs we need no LoD generation (contain original data) + // just build kd-tree and estimate normals ... + if (self.IsLeaf) { - var store = self.Storage; - var originalId = self.Id; - var upsertData = ImmutableDictionary.Empty; - - ////////////////////////////////////////////////////////////////// - // for leafs we need no LoD generation (contain original data) - // just build kd-tree and estimate normals ... - if (self.IsLeaf) - { - var kd = self.KdTree?.Value; + var kd = self.KdTree?.Value; - if (kd == null) - { - kd = await self.Positions.Value.BuildKdTreeAsync(); - var kdKey = Guid.NewGuid(); - store?.Add(kdKey, kd.Data); - upsertData = upsertData.Add(Durable.Octree.PointRkdTreeFDataReference, kdKey); - } + if (kd == null) + { + kd = await self.Positions.Value.BuildKdTreeAsync(); + var kdKey = Guid.NewGuid(); + store?.Add(kdKey, kd.Data); + upsertData = upsertData.Add(Durable.Octree.PointRkdTreeFDataReference, kdKey); + } - if (!self.HasNormals) - { - var ns = await self.Positions.Value.EstimateNormalsAsync(16, kd); - if (ns.Length != self.Positions.Value!.Length) throw new Exception(); - var nsId = Guid.NewGuid(); - store?.Add(nsId, ns); - upsertData = upsertData.Add(Durable.Octree.Normals3fReference, nsId); - } - else - { - if (self.Normals!.Value!.Length != self.PointCountCell) throw new Exception(); - } + if (!self.HasNormals) + { + var ns = await self.Positions.Value.EstimateNormalsAsync(16, kd); + if (ns.Length != self.Positions.Value!.Length) throw new Exception(); + var nsId = Guid.NewGuid(); + store?.Add(nsId, ns); + upsertData = upsertData.Add(Durable.Octree.Normals3fReference, nsId); + } + else + { + if (self.Normals!.Value!.Length != self.PointCountCell) throw new Exception(); + } - if (upsertData.Count > 0) self = self.With(upsertData); - self = self.Without(PointSetNode.TemporaryImportNode); - if (self.Id != originalId) self = self.WriteToStore(); + if (upsertData.Count > 0) self = self.With(upsertData); + self = self.Without(PointSetNode.TemporaryImportNode); + if (self.Id != originalId) self = self.WriteToStore(); - if (!self.HasNormals) throw new Exception(); - return self; - } + if (!self.HasNormals) throw new Exception(); + return self; + } - ////////////////////////////////////////////////////////////////// - // how much data should we take from each subode ... - if (self.Subnodes == null || self.Subnodes.Length != 8) throw new InvalidOperationException(); + ////////////////////////////////////////////////////////////////// + // how much data should we take from each subode ... + if (self.Subnodes == null || self.Subnodes.Length != 8) throw new InvalidOperationException(); - var subcellsAsync = self.Subnodes.Map(x => (x?.Value as PointSetNode)?.GenerateLod(octreeSplitLimit, callback, ct)); - await Task.WhenAll(subcellsAsync.Where(x => x != null)); - var subcells = subcellsAsync.Map(x => x?.Result); - var fractions = ComputeLodFractions(subcells); - var aggregateCount = Math.Min(octreeSplitLimit, subcells.Sum(x => x?.PointCountCell) ?? 0); - var counts = ComputeLodCounts(aggregateCount, fractions); + var subcellsAsync = self.Subnodes.Map(x => (x?.Value as PointSetNode)?.GenerateLod(octreeSplitLimit, callback, ct)); + await Task.WhenAll(subcellsAsync.Where(x => x != null)); + var subcells = subcellsAsync.Map(x => x?.Result); + var fractions = ComputeLodFractions(subcells); + var aggregateCount = Math.Min(octreeSplitLimit, subcells.Sum(x => x?.PointCountCell) ?? 0); + var counts = ComputeLodCounts(aggregateCount, fractions); #if DEBUG - if (counts.Sum() != aggregateCount) throw new Exception( - $"Expected aggregate count {aggregateCount} to be the same as sum of LoD counts {counts.Sum()}. " + - $"Invariant 21033eb8-2a19-43a5-82b4-f6c398ac599f." - ); + if (counts.Sum() != aggregateCount) throw new Exception( + $"Expected aggregate count {aggregateCount} to be the same as sum of LoD counts {counts.Sum()}. " + + $"Invariant 21033eb8-2a19-43a5-82b4-f6c398ac599f." + ); #endif - ////////////////////////////////////////////////////////////////// - // generate LoD data ... + ////////////////////////////////////////////////////////////////// + // generate LoD data ... - bool subnodesHaveAttribute(Func check, string kind) + bool subnodesHaveAttribute(Func check, string kind) + { + var has = 0; var hasNot = 0; + foreach (var n in subcells) { - var has = 0; var hasNot = 0; - foreach (var n in subcells) - { - if (n == null) continue; - if (check(n)) has++; else hasNot++; - } - - if (has > 0 && hasNot > 0) + if (n == null) continue; + if (check(n)) has++; else hasNot++; + } + + if (has > 0 && hasNot > 0) + { + var info = "Subnodes: [" + string.Join("; ", subcells.Select((n, i) => { - var info = "Subnodes: [" + string.Join("; ", subcells.Select((n, i) => - { - if (n == null) return $"{i}: null"; - else if (check(n)) return $"{i}: exists"; - else return $"{i}: n/a"; - })) + "]"; - - throw new Exception( - $"Inconsistent {kind} attribute in subnodes of node id={self.Id}. Invariant 6454e7ab-0b39-4f3e-b158-c3f9cbc24953. {info}." - ); - } - - return has > 0; + if (n == null) return $"{i}: null"; + else if (check(n)) return $"{i}: exists"; + else return $"{i}: n/a"; + })) + "]"; + + throw new Exception( + $"Inconsistent {kind} attribute in subnodes of node id={self.Id}. Invariant 6454e7ab-0b39-4f3e-b158-c3f9cbc24953. {info}." + ); } - var firstNonEmptySubnode = subcells.First(n => n != null)!; - var lodAttributeCandidates = firstNonEmptySubnode.Properties.Keys.Where(x => x.IsArray && - x != Durable.Octree.SubnodesGuids && - x != Durable.Octree.PositionsLocal3f && - x != Durable.Octree.Normals3f && - x != Durable.Octree.Classifications1b && - x != Durable.Octree.Colors4b && - x != Durable.Octree.Intensities1i && - x != Durable.Octree.PerCellPartIndex1i && - x != Durable.Octree.PerCellPartIndex1ui && - x != Durable.Octree.PerPointPartIndex1b && - x != Durable.Octree.PerPointPartIndex1s && - x != Durable.Octree.PerPointPartIndex1i - ).ToArray(); - - // ... positions ... - var subcenters = subcells.Map(x => x?.Center); - var lodPs = AggregateSubPositions(counts, aggregateCount, self.Center, subcenters, subcells.Map(x => x?.Positions?.Value)); - if (lodPs.Any(p => p.IsNaN)) throw new Exception("One or more positions are NaN."); - var lodPsKey = Guid.NewGuid(); - store?.Add(lodPsKey, lodPs); - upsertData = upsertData - .Add(Durable.Octree.PositionsLocal3fReference, lodPsKey) - .Add(Durable.Octree.PointCountCell, lodPs.Length) - ; + return has > 0; + } - // ... kd-tree ... - var lodKd = await lodPs.BuildKdTreeAsync(); - var lodKdKey = Guid.NewGuid(); - store?.Add(lodKdKey, lodKd.Data); - upsertData = upsertData.Add(Durable.Octree.PointRkdTreeFDataReference, lodKdKey); + var firstNonEmptySubnode = subcells.First(n => n != null)!; + var lodAttributeCandidates = firstNonEmptySubnode.Properties.Keys.Where(x => x.IsArray && + x != Durable.Octree.SubnodesGuids && + x != Durable.Octree.PositionsLocal3f && + x != Durable.Octree.Normals3f && + x != Durable.Octree.Classifications1b && + x != Durable.Octree.Colors4b && + x != Durable.Octree.Intensities1i && + x != Durable.Octree.PerCellPartIndex1i && + x != Durable.Octree.PerCellPartIndex1ui && + x != Durable.Octree.PerPointPartIndex1b && + x != Durable.Octree.PerPointPartIndex1s && + x != Durable.Octree.PerPointPartIndex1i + ).ToArray(); + + // ... positions ... + var subcenters = subcells.Map(x => x?.Center); + var lodPs = AggregateSubPositions(counts, aggregateCount, self.Center, subcenters, subcells.Map(x => x?.Positions?.Value)); + if (lodPs.Any(p => p.IsNaN)) throw new Exception("One or more positions are NaN."); + var lodPsKey = Guid.NewGuid(); + store?.Add(lodPsKey, lodPs); + upsertData = upsertData + .Add(Durable.Octree.PositionsLocal3fReference, lodPsKey) + .Add(Durable.Octree.PointCountCell, lodPs.Length) + ; + + // ... kd-tree ... + var lodKd = await lodPs.BuildKdTreeAsync(); + var lodKdKey = Guid.NewGuid(); + store?.Add(lodKdKey, lodKd.Data); + upsertData = upsertData.Add(Durable.Octree.PointRkdTreeFDataReference, lodKdKey); + + // .. normals ... + var lodNs = await lodPs.EstimateNormalsAsync(16, lodKd); + if (lodNs.Length != lodPs.Length) throw new Exception( + $"Inconsistent lod-normals length {lodNs.Length}. Should be {lodPs.Length}. Error 806dfe30-4faa-46b1-a2a3-f50e336cbe67." + ); + var lodNsKey = Guid.NewGuid(); + store?.Add(lodNsKey, lodNs); + upsertData = upsertData.Add(Durable.Octree.Normals3fReference, lodNsKey); + + void addAttributeByRef(string kind, Durable.Def def, Func has, Func getData) + { + var hasAttribute = subnodesHaveAttribute(has, kind); + if (hasAttribute) + { + var lod = AggregateSubArrays(counts, octreeSplitLimit, subcells.Map(getData)); + if (lod.Length != lodPs.Length) throw new Exception( + $"Inconsistent lod {kind} length {lod.Length}. Should be {lodPs.Length}. Error 1aa7a00f-cb3a-42a6-ad38-6c03fcd6ea9f." + ); + // add attribute by ref to separate blob + var key = Guid.NewGuid(); + store?.Add(key, lod); + upsertData = upsertData.Add(def, key); + } + } - // .. normals ... - var lodNs = await lodPs.EstimateNormalsAsync(16, lodKd); - if (lodNs.Length != lodPs.Length) throw new Exception( - $"Inconsistent lod-normals length {lodNs.Length}. Should be {lodPs.Length}. Error 806dfe30-4faa-46b1-a2a3-f50e336cbe67." - ); - var lodNsKey = Guid.NewGuid(); - store?.Add(lodNsKey, lodNs); - upsertData = upsertData.Add(Durable.Octree.Normals3fReference, lodNsKey); + // ... part indices ... + var (lodQs, lodQsRange) = AggregateSubPartIndices(counts, aggregateCount, subcells.Map(x => x?.PartIndices)); + if (lodQs != null) + { + upsertData = upsertData + //.Add(PartIndexUtils.GetDurableDefForPartIndices(lodQs), lodQs) + .Add(Durable.Octree.PartIndexRange, lodQsRange ?? throw new Exception($"Expected part index range to be not null. Error d355eb9f-02b3-4cd7-b1de-041d4d0e7c3c.")) + ; - void addAttributeByRef(string kind, Durable.Def def, Func has, Func getData) + if (lodQs is Array xs) { - var hasAttribute = subnodesHaveAttribute(has, kind); - if (hasAttribute) + // add attribute by ref to separate blob + var key = Guid.NewGuid(); + store?.Add(key, xs); + var def = xs switch { - var lod = AggregateSubArrays(counts, octreeSplitLimit, subcells.Map(getData)); - if (lod.Length != lodPs.Length) throw new Exception( - $"Inconsistent lod {kind} length {lod.Length}. Should be {lodPs.Length}. Error 1aa7a00f-cb3a-42a6-ad38-6c03fcd6ea9f." - ); - // add attribute by ref to separate blob - var key = Guid.NewGuid(); - store?.Add(key, lod); - upsertData = upsertData.Add(def, key); - } + byte[] => Durable.Octree.PerPointPartIndex1bReference, + short[] => Durable.Octree.PerPointPartIndex1sReference, + int[] => Durable.Octree.PerPointPartIndex1iReference, + _ => throw new Exception("Invariant 5a25f407-5ccf-4577-a7eb-c0d691093d10.") + }; + upsertData = upsertData.Add(def, key); } - - // ... part indices ... - var (lodQs, lodQsRange) = AggregateSubPartIndices(counts, aggregateCount, subcells.Map(x => x?.PartIndices)); - if (lodQs != null) + else { upsertData = upsertData - //.Add(PartIndexUtils.GetDurableDefForPartIndices(lodQs), lodQs) - .Add(Durable.Octree.PartIndexRange, lodQsRange ?? throw new Exception($"Expected part index range to be not null. Error d355eb9f-02b3-4cd7-b1de-041d4d0e7c3c.")) + .Add(PartIndexUtils.GetDurableDefForPartIndices(lodQs), lodQs) ; - - if (lodQs is Array xs) - { - // add attribute by ref to separate blob - var key = Guid.NewGuid(); - store?.Add(key, xs); - var def = xs switch - { - byte[] => Durable.Octree.PerPointPartIndex1bReference, - short[] => Durable.Octree.PerPointPartIndex1sReference, - int[] => Durable.Octree.PerPointPartIndex1iReference, - _ => throw new Exception("Invariant 5a25f407-5ccf-4577-a7eb-c0d691093d10.") - }; - upsertData = upsertData.Add(def, key); - } - else - { - upsertData = upsertData - .Add(PartIndexUtils.GetDurableDefForPartIndices(lodQs), lodQs) - ; - } } - - // ... classifications ... - addAttributeByRef("classifications", Durable.Octree.Classifications1bReference, n => n.HasClassifications, n => n?.Classifications?.Value); + } + + // ... classifications ... + addAttributeByRef("classifications", Durable.Octree.Classifications1bReference, n => n.HasClassifications, n => n?.Classifications?.Value); - // ... colors ... - addAttributeByRef("colors", Durable.Octree.Colors4bReference, n => n.HasColors, n => n?.Colors?.Value); + // ... colors ... + addAttributeByRef("colors", Durable.Octree.Colors4bReference, n => n.HasColors, n => n?.Colors?.Value); - // ... intensities ... - addAttributeByRef("intensities", Durable.Octree.Intensities1iReference, n => n.HasIntensities, n => n?.Intensities?.Value); + // ... intensities ... + addAttributeByRef("intensities", Durable.Octree.Intensities1iReference, n => n.HasIntensities, n => n?.Intensities?.Value); - // ... for all other attributes ... - foreach (var def in lodAttributeCandidates) + // ... for all other attributes ... + foreach (var def in lodAttributeCandidates) + { + try { - try - { - var lod = AggregateSubArrays(counts, octreeSplitLimit, subcells.Map(x => x?.Properties[def]!)); - if (lod.Length != lodPs.Length) throw new Exception( - $"Inconsistent lod-array length {lod.Length}. Should be {lodPs.Length}. Error e3f0398a-b0ae-4e57-95ab-b5d83922ec6e." - ); - - // add attribute inside node (for custom attributes we don't know which type (Durable.Def) - // to use for referencing the attribute data as a blob outside this node) - upsertData = upsertData.Add(def, lod); - } - catch - { - Report.Error($"Failure to aggregate subnode data for custom attribute {def}. Error d74d2b84-58ec-482b-9971-750f8c50324e."); - throw; - } + var lod = AggregateSubArrays(counts, octreeSplitLimit, subcells.Map(x => x?.Properties[def]!)); + if (lod.Length != lodPs.Length) throw new Exception( + $"Inconsistent lod-array length {lod.Length}. Should be {lodPs.Length}. Error e3f0398a-b0ae-4e57-95ab-b5d83922ec6e." + ); + + // add attribute inside node (for custom attributes we don't know which type (Durable.Def) + // to use for referencing the attribute data as a blob outside this node) + upsertData = upsertData.Add(def, lod); + } + catch + { + Report.Error($"Failure to aggregate subnode data for custom attribute {def}. Error d74d2b84-58ec-482b-9971-750f8c50324e."); + throw; } + } - var subnodeIds = subcells.Map(x => x != null ? x.Id : Guid.Empty); - upsertData = upsertData.Add(Durable.Octree.SubnodesGuids, subnodeIds); + var subnodeIds = subcells.Map(x => x != null ? x.Id : Guid.Empty); + upsertData = upsertData.Add(Durable.Octree.SubnodesGuids, subnodeIds); - ////////////////////////////////////////////////////////////////// - // store LoD data ... - self = self - .With(upsertData) - .Without(PointSetNode.TemporaryImportNode) - ; + ////////////////////////////////////////////////////////////////// + // store LoD data ... + self = self + .With(upsertData) + .Without(PointSetNode.TemporaryImportNode) + ; - if (self.Id != originalId) self = self.WriteToStore(); + if (self.Id != originalId) self = self.WriteToStore(); - if (!self.HasNormals) throw new Exception(); - return self; - } - finally - { - callback?.Invoke(); - } + if (!self.HasNormals) throw new Exception(); + return self; + } + finally + { + callback?.Invoke(); } } } diff --git a/src/Aardvark.Geometry.PointSet/Octrees/Merge.cs b/src/Aardvark.Geometry.PointSet/Octrees/Merge.cs index dcb7e153..a23781aa 100644 --- a/src/Aardvark.Geometry.PointSet/Octrees/Merge.cs +++ b/src/Aardvark.Geometry.PointSet/Octrees/Merge.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -20,1168 +20,1185 @@ You should have received a copy of the GNU Affero General Public License using System.Collections.Immutable; using System.Linq; -namespace Aardvark.Geometry.Points +#pragma warning disable IDE0130 // Namespace does not match folder structure + +namespace Aardvark.Geometry.Points; + +/// +/// +public static class MergeExtensions { - /// - /// - public static class MergeExtensions + private static bool DidMergeWorkObject(object? x, int xCount, object? y, int yCount, object? m) { - private static bool DidMergeWorkObject(object? x, int xCount, object? y, int yCount, object? m) + var xqs = PartIndexUtils.ConcatIndices(x, xCount, y, yCount); + var ass = ((int[])xqs!); + ass.QuickSortAscending(); + var mss = ((int[])m!); + mss.QuickSortAscending(); + + var assHist = + ass.GroupBy(i => i) + .Select(group => (group.Key, group.Count())) + .OrderBy(x => x.Key) + .ToArray(); + var mssHist = + mss.GroupBy(i => i) + .Select(group => (group.Key, group.Count())) + .OrderBy(x => x.Key) + .ToArray(); + var eq = Enumerable.SequenceEqual(ass, mss); + if (!eq) { - var xqs = PartIndexUtils.ConcatIndices(x, xCount, y, yCount); - var ass = ((int[])xqs!); - ass.QuickSortAscending(); - var mss = ((int[])m!); - mss.QuickSortAscending(); - - var assHist = - ass.GroupBy(i => i) - .Select(group => (group.Key, group.Count())) - .OrderBy(x => x.Item1) - .ToArray(); - var mssHist = - mss.GroupBy(i => i) - .Select(group => (group.Key, group.Count())) - .OrderBy(x => x.Item1) - .ToArray(); - var eq = Enumerable.SequenceEqual(ass, mss); - if (!eq) + Report.Begin("Ass"); + foreach (var (k, c) in assHist) { - Report.Begin("Ass"); - foreach (var (k, c) in assHist) - { - Report.Line("{0}:{1}", k, c); - } - Report.End(); - Report.Begin("Mss"); - foreach (var (k, c) in mssHist) - { - Report.Line("{0}:{1}", k, c); - } - Report.End(); + Report.Line("{0}:{1}", k, c); + } + Report.End(); + Report.Begin("Mss"); + foreach (var (k, c) in mssHist) + { + Report.Line("{0}:{1}", k, c); } - return eq; + Report.End(); } - private static bool DidMergeWork(IPointCloudNode x, IPointCloudNode y,IPointCloudNode m) + return eq; + } + private static bool DidMergeWork(IPointCloudNode x, IPointCloudNode y,IPointCloudNode m) + { + object? xqs = null; + object? mqs = null; + CollectEverything(x, [], null, null, null, null, ref xqs); + CollectEverything(y, [], null, null, null, null, ref xqs); + CollectEverything(m, [], null, null, null, null, ref mqs); + var ass = ((int[])xqs!); + ass.QuickSortAscending(); + var mss = ((int[])mqs!); + mss.QuickSortAscending(); + + var assHist = + ass.GroupBy(i => i) + .Select(group => (group.Key, group.Count())) + .OrderBy(x => x.Key) + .ToArray(); + var mssHist = + mss.GroupBy(i => i) + .Select(group => (group.Key, group.Count())) + .OrderBy(x => x.Key) + .ToArray(); + var eq = Enumerable.SequenceEqual(ass, mss); + if(!eq) { - object? xqs = null; - object? mqs = null; - CollectEverything(x, new List(), null, null, null, null, ref xqs); - CollectEverything(y, new List(), null, null, null, null, ref xqs); - CollectEverything(m, new List(), null, null, null, null, ref mqs); - var ass = ((int[])xqs!); - ass.QuickSortAscending(); - var mss = ((int[])mqs!); - mss.QuickSortAscending(); - - var assHist = - ass.GroupBy(i => i) - .Select(group => (group.Key, group.Count())) - .OrderBy(x => x.Item1) - .ToArray(); - var mssHist = - mss.GroupBy(i => i) - .Select(group => (group.Key, group.Count())) - .OrderBy(x => x.Item1) - .ToArray(); - var eq = Enumerable.SequenceEqual(ass, mss); - if(!eq) + Report.Begin("Ass"); + foreach(var (k,c) in assHist) { - Report.Begin("Ass"); - foreach(var (k,c) in assHist) - { - Report.Line("{0}:{1}", k, c); - } - Report.End(); - Report.Begin("Mss"); - foreach (var (k, c) in mssHist) - { - Report.Line("{0}:{1}", k, c); - } - Report.End(); + Report.Line("{0}:{1}", k, c); + } + Report.End(); + Report.Begin("Mss"); + foreach (var (k, c) in mssHist) + { + Report.Line("{0}:{1}", k, c); } - return eq; + Report.End(); } + return eq; + } - /// - /// Collects all leaf per-point properties into given lists. - /// Returns number of leaves that have been collected. - /// - internal static int CollectEverything(IPointCloudNode self, List ps, List? cs, List? ns, List? js, List? ks, ref object? qs) - { - if (self == null) return 0; + /// + /// Collects all leaf per-point properties into given lists. + /// Returns number of leaves that have been collected. + /// + internal static int CollectEverything(IPointCloudNode self, List ps, List? cs, List? ns, List? js, List? ks, ref object? qs) + { + if (self == null) return 0; - if (self.IsLeaf) - { - var initialCount = ps.Count; + if (self.IsLeaf) + { + var initialCount = ps.Count; - var off = self.Center; - ps.AddRange(self.Positions.Value.Map(p => off + (V3d)p)); + var off = self.Center; + ps.AddRange(self.Positions.Value.Map(p => off + (V3d)p)); - if (self.HasColors && cs != null) cs.AddRange(self.Colors.Value ); - if (self.HasNormals && ns != null) ns.AddRange(self.Normals.Value ); - if (self.HasIntensities && js != null) js.AddRange(self.Intensities.Value ); - if (self.HasClassifications && ks != null) ks.AddRange(self.Classifications.Value); - qs = PartIndexUtils.ConcatIndices(qs, initialCount, self.PartIndices, self.PointCountCell); + if (self.HasColors && cs != null) cs.AddRange(self.Colors.Value ); + if (self.HasNormals && ns != null) ns.AddRange(self.Normals.Value ); + if (self.HasIntensities && js != null) js.AddRange(self.Intensities.Value ); + if (self.HasClassifications && ks != null) ks.AddRange(self.Classifications.Value); + qs = PartIndexUtils.ConcatIndices(qs, initialCount, self.PartIndices, self.PointCountCell); - return 1; - } - else + return 1; + } + else + { + var leaves = 0; + foreach (var x in self.Subnodes) { - var leaves = 0; - foreach (var x in self.Subnodes) + if (x != null) { - if (x != null) - { - leaves += CollectEverything(x.Value, ps, cs, ns, js, ks, ref qs); - } + leaves += CollectEverything(x.Value, ps, cs, ns, js, ks, ref qs); } + } - if (leaves == 0) throw new Exception($"Expected at least 1 leaf. Error 5c37764f-0c38-4da2-b2cd-2840af83c687."); + if (leaves == 0) throw new Exception($"Expected at least 1 leaf. Error 5c37764f-0c38-4da2-b2cd-2840af83c687."); - return leaves; - } + return leaves; } + } - internal static (IPointCloudNode, bool) CollapseLeafNodes(this IPointCloudNode self, ImportConfig config) - { - if (!self.IsTemporaryImportNode) throw new InvalidOperationException( - "CollapseLeafNodes is only valid for temporary import nodes. Invariant 4aa0809d-4cb0-422b-97ee-fa5b6dc4785e." - ); + internal static (IPointCloudNode, bool) CollapseLeafNodes(this IPointCloudNode self, ImportConfig config) + { + if (!self.IsTemporaryImportNode) throw new InvalidOperationException( + "CollapseLeafNodes is only valid for temporary import nodes. Invariant 4aa0809d-4cb0-422b-97ee-fa5b6dc4785e." + ); - if (self.PointCountTree <= config.OctreeSplitLimit) + if (self.PointCountTree <= config.OctreeSplitLimit) + { + if (self.IsLeaf) { - if (self.IsLeaf) - { - // leaf node ... - return (self.WriteToStore(), true); - } - else - { - // inner node ... + // leaf node ... + return (self.WriteToStore(), true); + } + else + { + // inner node ... - var psla = new List(); - var csla = new List(); - var nsla = new List(); - var jsla = new List(); - var ksla = new List(); - var qsla = (object?)null; + var psla = new List(); + var csla = new List(); + var nsla = new List(); + var jsla = new List(); + var ksla = new List(); + var qsla = (object?)null; - var leafCount = CollectEverything(self, psla, csla, nsla, jsla, ksla, ref qsla); + var leafCount = CollectEverything(self, psla, csla, nsla, jsla, ksla, ref qsla); - var hasCollectedPartIndices = qsla != null; + var hasCollectedPartIndices = qsla != null; - // positions might be slightly (~eps) outside this node's bounds, - // due to floating point conversion from local sub-node space to global space - var bb = self.BoundingBoxExactGlobal; - var eps = bb.Size * 1e-5; - for (var i = 0; i < psla.Count; i++) - { - var p = psla[i]; - if (p.X <= bb.Min.X) { if (!p.X.ApproximateEquals(bb.Min.X, eps.X)) throw new Exception($"Invariant 4840fe92-02df-4b9a-8233-18edb12656f9."); p.X = bb.Min.X + eps.X; } - if (p.Y <= bb.Min.Y) { if (!p.Y.ApproximateEquals(bb.Min.Y, eps.Y)) throw new Exception($"Invariant 942019a9-cb0d-476c-bfb8-69a2bde8debf."); p.Y = bb.Min.Y + eps.Y; } - if (p.Z <= bb.Min.Z) { if (!p.Z.ApproximateEquals(bb.Min.Z, eps.Z)) throw new Exception($"Invariant 68fd4c9e-6de1-4a43-91ae-fec4a9fb28df."); p.Z = bb.Min.Z + eps.Z; } - if (p.X >= bb.Max.X) { if (!p.X.ApproximateEquals(bb.Max.X, eps.X)) throw new Exception($"Invariant a24f717c-19d9-46eb-9cf5-b1f6d928963a."); p.X = bb.Max.X - eps.X; } - if (p.Y >= bb.Max.Y) { if (!p.Y.ApproximateEquals(bb.Max.Y, eps.Y)) throw new Exception($"Invariant fd8aaa89-43d3-428c-9d95-a62bf5a41b07."); p.Y = bb.Max.Y - eps.Y; } - if (p.Z >= bb.Max.Z) { if (!p.Z.ApproximateEquals(bb.Max.Z, eps.Z)) throw new Exception($"Invariant 9905f569-16d0-4e46-8ae2-147aeb6e7acc."); p.Z = bb.Max.Z - eps.Z; } - psla[i] = p; - } - var bbNew = new Box3d(psla); + // positions might be slightly (~eps) outside this node's bounds, + // due to floating point conversion from local sub-node space to global space + var bb = self.BoundingBoxExactGlobal; + var eps = bb.Size * 1e-5; + for (var i = 0; i < psla.Count; i++) + { + var p = psla[i]; + if (p.X <= bb.Min.X) { if (!p.X.ApproximateEquals(bb.Min.X, eps.X)) throw new Exception($"Invariant 4840fe92-02df-4b9a-8233-18edb12656f9."); p.X = bb.Min.X + eps.X; } + if (p.Y <= bb.Min.Y) { if (!p.Y.ApproximateEquals(bb.Min.Y, eps.Y)) throw new Exception($"Invariant 942019a9-cb0d-476c-bfb8-69a2bde8debf."); p.Y = bb.Min.Y + eps.Y; } + if (p.Z <= bb.Min.Z) { if (!p.Z.ApproximateEquals(bb.Min.Z, eps.Z)) throw new Exception($"Invariant 68fd4c9e-6de1-4a43-91ae-fec4a9fb28df."); p.Z = bb.Min.Z + eps.Z; } + if (p.X >= bb.Max.X) { if (!p.X.ApproximateEquals(bb.Max.X, eps.X)) throw new Exception($"Invariant a24f717c-19d9-46eb-9cf5-b1f6d928963a."); p.X = bb.Max.X - eps.X; } + if (p.Y >= bb.Max.Y) { if (!p.Y.ApproximateEquals(bb.Max.Y, eps.Y)) throw new Exception($"Invariant fd8aaa89-43d3-428c-9d95-a62bf5a41b07."); p.Y = bb.Max.Y - eps.Y; } + if (p.Z >= bb.Max.Z) { if (!p.Z.ApproximateEquals(bb.Max.Z, eps.Z)) throw new Exception($"Invariant 9905f569-16d0-4e46-8ae2-147aeb6e7acc."); p.Z = bb.Max.Z - eps.Z; } + psla[i] = p; + } + var bbNew = new Box3d(psla); #if DEBUG - { - // Invariant: bounding box of collected positions MUST be contained in original trees bounding box - if (!self.BoundingBoxExactGlobal.Contains(new Box3d(psla))) throw new Exception($"Invariant 0fdad697-b315-45b2-a581-49db8c46e20e."); - } + { + // Invariant: bounding box of collected positions MUST be contained in original trees bounding box + if (!self.BoundingBoxExactGlobal.Contains(new Box3d(psla))) throw new Exception($"Invariant 0fdad697-b315-45b2-a581-49db8c46e20e."); + } #endif - if (leafCount <= 1) + if (leafCount <= 1) + { + return (self.WriteToStore(), true); + } + else + { + var chunk = new Chunk( + psla.Count > 0 ? psla : null, + csla.Count > 0 ? csla : null, + nsla.Count > 0 ? nsla : null, + jsla.Count > 0 ? jsla : null, + ksla.Count > 0 ? ksla : null, + qsla, partIndexRange: null, + bbox: null + ); + + if (config.NormalizePointDensityGlobal) { - return (self.WriteToStore(), true); + chunk = chunk.ImmutableFilterMinDistByCell(self.Cell, config.ParseConfig); } - else - { - var chunk = new Chunk( - psla.Count > 0 ? psla : null, - csla.Count > 0 ? csla : null, - nsla.Count > 0 ? nsla : null, - jsla.Count > 0 ? jsla : null, - ksla.Count > 0 ? ksla : null, - qsla, partIndexRange: null, - bbox: null - ); - - if (config.NormalizePointDensityGlobal) - { - chunk = chunk.ImmutableFilterMinDistByCell(self.Cell, config.ParseConfig); - } #if DEBUG - { - // Invariant: collected and filtered subtree data MUST still have no more points than split limit - if (chunk.Count > config.OctreeSplitLimit) throw new Exception($"Invariant 8d48f48c-9f35-4d14-a9fc-80d33bf94615."); - } + { + // Invariant: collected and filtered subtree data MUST still have no more points than split limit + if (chunk.Count > config.OctreeSplitLimit) throw new Exception($"Invariant 8d48f48c-9f35-4d14-a9fc-80d33bf94615."); + } #endif - var inMemory = InMemoryPointSet.Build(chunk, config.OctreeSplitLimit); - var collapsedNode = inMemory.ToPointSetNode(self.Storage, isTemporaryImportNode: true); + var inMemory = InMemoryPointSet.Build(chunk, config.OctreeSplitLimit); + var collapsedNode = inMemory.ToPointSetNode(self.Storage, isTemporaryImportNode: true); #if DEBUG - { - // Invariant: collapsed node's bounding box MUST be contained in original tree's bounding box - if (!self.BoundingBoxExactGlobal/*.EnlargedByRelativeEps(1e-6)*/.Contains(collapsedNode.BoundingBoxExactGlobal)) - throw new Exception($"Invariant 0936ab9d-7c4a-4873-86ab-d36deb163716."); + { + // Invariant: collapsed node's bounding box MUST be contained in original tree's bounding box + if (!self.BoundingBoxExactGlobal/*.EnlargedByRelativeEps(1e-6)*/.Contains(collapsedNode.BoundingBoxExactGlobal)) + throw new Exception($"Invariant 0936ab9d-7c4a-4873-86ab-d36deb163716."); - // Invariant: original tree's root node cell MUST contain collapsed node's cell - if (self.Cell != collapsedNode.Cell) throw new Exception($"Invariant 8370ea8c-ba61-42ba-823e-94d596cb5f3f."); + // Invariant: original tree's root node cell MUST contain collapsed node's cell + if (self.Cell != collapsedNode.Cell) throw new Exception($"Invariant 8370ea8c-ba61-42ba-823e-94d596cb5f3f."); - // Invariant: collapsed node MUST still be a leaf node - if (collapsedNode.IsNotLeaf) throw new Exception($"Invariant c4f7e0e3-9ad5-4cba-80ee-4b0849a995e6."); - } + // Invariant: collapsed node MUST still be a leaf node + if (collapsedNode.IsNotLeaf) throw new Exception($"Invariant c4f7e0e3-9ad5-4cba-80ee-4b0849a995e6."); + } #endif - // Invariant: collapsed node must retain original part indices and part index range (if any) - if (hasCollectedPartIndices) - { - if (!collapsedNode.HasPartIndices) throw new Exception($"Invariant 58389bf7-be01-46f1-b0df-f75942eea2b7."); - if (!collapsedNode.HasPartIndexRange) throw new Exception($"Invariant 5adce094-281b-4617-b45a-1805301e34af."); - } - - if (self.Cell != collapsedNode.Cell) - { - return (JoinTreeToRootCell(self.Cell, collapsedNode, config, collapse: false), true); - } - else - { - return (collapsedNode, true); - } + // Invariant: collapsed node must retain original part indices and part index range (if any) + if (hasCollectedPartIndices) + { + if (!collapsedNode.HasPartIndices) throw new Exception($"Invariant 58389bf7-be01-46f1-b0df-f75942eea2b7."); + if (!collapsedNode.HasPartIndexRange) throw new Exception($"Invariant 5adce094-281b-4617-b45a-1805301e34af."); + } + + if (self.Cell != collapsedNode.Cell) + { + return (JoinTreeToRootCell(self.Cell, collapsedNode, config, collapse: false), true); + } + else + { + return (collapsedNode, true); } } } - else - { - return (self.WriteToStore(), true); - } } + else + { + return (self.WriteToStore(), true); + } + } - /// - /// If node is a leaf, it will be split once (non-recursive, without taking into account any split limit). - /// If node is not a leaf, this is an invalid operation. - /// - internal static IPointCloudNode ForceSplitLeaf(this IPointCloudNode self, ImportConfig config) + /// + /// If node is a leaf, it will be split once (non-recursive, without taking into account any split limit). + /// If node is not a leaf, this is an invalid operation. + /// + internal static IPointCloudNode ForceSplitLeaf(this IPointCloudNode self, ImportConfig config) + { + if (!self.IsTemporaryImportNode) throw new InvalidOperationException( + "ForceSplitLeaf is only valid for temporary import nodes. Invariant 3bfca971-be98-45b7-86e7-de436b78cefb." + ); + + if (self == null) throw new ArgumentNullException(nameof(self)); + if (self.IsLeaf == false) throw new InvalidOperationException(); + if (self.PointCountCell == 0) throw new InvalidOperationException(); + if (self.PointCountTree != self.PointCountCell) throw new InvalidOperationException(); + if (self.HasPartIndices && !self.HasPartIndexRange) throw new InvalidOperationException(); + + var ps = self.PositionsAbsolute; + var cs = self.Colors?.Value; + var ns = self.Normals?.Value; + var js = self.Intensities?.Value; + var ks = self.Classifications?.Value; + var qs = self.PartIndices; + + var pss = new V3d[]?[8]; + var css = self.HasColors ? new C4b[]?[8] : null; + var nss = self.HasNormals ? new V3f[]?[8] : null; + var jss = self.HasIntensities ? new int[]?[8] : null; + var kss = self.HasClassifications ? new byte[]?[8] : null; + var qss = self.HasPartIndices ? new object?[8] : null; + + var imax = self.PointCountCell; + if (ps.Length != imax) throw new InvalidOperationException(); + + var ias = new List[8]; + for (var i = 0; i < 8; i++) ias[i] = []; + for (var i = 0; i < imax; i++) ias[self.GetSubIndex(ps[i])].Add(i); + + for (var i = 0; i < 8; i++) { - if (!self.IsTemporaryImportNode) throw new InvalidOperationException( - "ForceSplitLeaf is only valid for temporary import nodes. Invariant 3bfca971-be98-45b7-86e7-de436b78cefb." - ); + var ia = ias[i]; + if (ia.Count == 0) continue; + + pss[i] = ps.Subset(ia); + if (css != null) css[i] = cs?.Subset(ia); + if (nss != null) nss[i] = ns?.Subset(ia); + if (jss != null) jss[i] = js?.Subset(ia); + if (kss != null) kss[i] = ks?.Subset(ia); + if (qss != null) qss[i] = PartIndexUtils.Subset(qs, ia); + } - if (self == null) throw new ArgumentNullException(nameof(self)); - if (self.IsLeaf == false) throw new InvalidOperationException(); - if (self.PointCountCell == 0) throw new InvalidOperationException(); - if (self.PointCountTree != self.PointCountCell) throw new InvalidOperationException(); - if (self.HasPartIndices && !self.HasPartIndexRange) throw new InvalidOperationException(); - - var ps = self.PositionsAbsolute; - var cs = self.Colors?.Value; - var ns = self.Normals?.Value; - var js = self.Intensities?.Value; - var ks = self.Classifications?.Value; - var qs = self.PartIndices; - - var pss = new V3d[]?[8]; - var css = self.HasColors ? new C4b[]?[8] : null; - var nss = self.HasNormals ? new V3f[]?[8] : null; - var jss = self.HasIntensities ? new int[]?[8] : null; - var kss = self.HasClassifications ? new byte[]?[8] : null; - var qss = self.HasPartIndices ? new object?[8] : null; - - var imax = self.PointCountCell; - if (ps.Length != imax) throw new InvalidOperationException(); - - var ias = new List[8]; - for (var i = 0; i < 8; i++) ias[i] = new(); - for (var i = 0; i < imax; i++) ias[self.GetSubIndex(ps[i])].Add(i); + var subnodes = new PointSetNode[8]; + for (var i = 0; i < 8; i++) + { + var subPs = pss[i]; + if (subPs == null) continue; - for (var i = 0; i < 8; i++) + var subCell = self.Cell.GetOctant(i); + if (!self.Cell.Contains(subCell)) throw new InvalidOperationException(); + if (self.Cell.Exponent != subCell.Exponent + 1) throw new InvalidOperationException(); + + var chunk = new Chunk(subPs, css?[i], nss?[i], jss?[i], kss?[i], qss?[i], partIndexRange: null, subCell.BoundingBox); + + if (config.NormalizePointDensityGlobal) { - var ia = ias[i]; - if (ia.Count == 0) continue; - - pss[i] = ps.Subset(ia); - if (css != null) css[i] = cs?.Subset(ia); - if (nss != null) nss[i] = ns?.Subset(ia); - if (jss != null) jss[i] = js?.Subset(ia); - if (kss != null) kss[i] = ks?.Subset(ia); - if (qss != null) qss[i] = PartIndexUtils.Subset(qs, ia); + chunk = chunk.ImmutableFilterMinDistByCell(subCell, config.ParseConfig); } - var subnodes = new PointSetNode[8]; - for (var i = 0; i < 8; i++) - { - var subPs = pss[i]; - if (subPs == null) continue; + var builder = InMemoryPointSet.Build(subPs, css?[i], nss?[i], jss?[i], kss?[i], qss?[i], subCell, int.MaxValue); - var subCell = self.Cell.GetOctant(i); - if (!self.Cell.Contains(subCell)) throw new InvalidOperationException(); - if (self.Cell.Exponent != subCell.Exponent + 1) throw new InvalidOperationException(); + var subnode = builder.ToPointSetNode(config.Storage, isTemporaryImportNode: true); + if (subnode.PointCountTree > subPs.Length) throw new InvalidOperationException(); + if (!self.Cell.Contains(subnode.Cell)) throw new InvalidOperationException(); + if (self.Cell.Exponent != subnode.Cell.Exponent + 1) throw new InvalidOperationException(); + + subnodes[i] = subnode; + } - var chunk = new Chunk(subPs, css?[i], nss?[i], jss?[i], kss?[i], qss?[i], partIndexRange: null, subCell.BoundingBox); + var bbExactGlobal = new Box3d(subnodes.Where(x => x != null).Select(x => x.BoundingBoxExactGlobal)); + + var data = ImmutableDictionary.Empty + .Add(PointSetNode.TemporaryImportNode, 0) + .Add(Durable.Octree.NodeId, Guid.NewGuid()) + .Add(Durable.Octree.Cell, self.Cell) + .Add(Durable.Octree.BoundingBoxExactGlobal, bbExactGlobal) + .Add(Durable.Octree.PointCountTreeLeafs, self.PointCountTree) + .Add(Durable.Octree.SubnodesGuids, subnodes.Map(x => x?.Id ?? Guid.Empty)) + ; + var result = new PointSetNode(data, self.Storage, true); + + // POST + if (result.IsLeaf) throw new InvalidOperationException(); + if (result.PointCountTree != self.PointCountTree) throw new InvalidOperationException(); + if (result.PointCountCell != 0) throw new InvalidOperationException(); + if (result.Subnodes.Sum(x => x?.Value?.PointCountTree) > self.PointCountTree) throw new InvalidOperationException(); + if (self.HasPartIndexRange && !result.HasPartIndexRange) throw new InvalidOperationException(); + + return result; + } - if (config.NormalizePointDensityGlobal) - { - chunk = chunk.ImmutableFilterMinDistByCell(subCell, config.ParseConfig); - } + /// + /// Returns union of trees as new tree (immutable operation). + /// + public static (IPointCloudNode, bool) Merge(this IPointCloudNode a, IPointCloudNode b, Action pointsMergedCallback, ImportConfig config) + { + if (!a.IsTemporaryImportNode || !b.IsTemporaryImportNode) throw new InvalidOperationException( + "Merge is only allowed on temporary import nodes. Invariant d53042e7-a032-47a9-98dc-034c0749a649." + ); - var builder = InMemoryPointSet.Build(subPs, css?[i], nss?[i], jss?[i], kss?[i], qss?[i], subCell, int.MaxValue); + if (a == null || a.PointCountTree == 0) { pointsMergedCallback.Invoke(b.PointCountTree); return (b, true); } + if (b == null || b.PointCountTree == 0) { pointsMergedCallback.Invoke(a.PointCountTree); return (a, true); } - var subnode = builder.ToPointSetNode(config.Storage, isTemporaryImportNode: true); - if (subnode.PointCountTree > subPs.Length) throw new InvalidOperationException(); - if (!self.Cell.Contains(subnode.Cell)) throw new InvalidOperationException(); - if (self.Cell.Exponent != subnode.Cell.Exponent + 1) throw new InvalidOperationException(); - - subnodes[i] = subnode; - } +#if DEBUG && NEVERMORE + if (config.Verbose) Report.Line($"[Merge] a = {a.Cell}, b = {b.Cell}"); +#endif - var bbExactGlobal = new Box3d(subnodes.Where(x => x != null).Select(x => x.BoundingBoxExactGlobal)); + // expect + if (a.HasPartIndexRange != b.HasPartIndexRange) throw new Exception("Invariant ff2ea514-a7b7-46dd-b147-ff7bd58ed1db."); - var data = ImmutableDictionary.Empty - .Add(PointSetNode.TemporaryImportNode, 0) - .Add(Durable.Octree.NodeId, Guid.NewGuid()) - .Add(Durable.Octree.Cell, self.Cell) - .Add(Durable.Octree.BoundingBoxExactGlobal, bbExactGlobal) - .Add(Durable.Octree.PointCountTreeLeafs, self.PointCountTree) - .Add(Durable.Octree.SubnodesGuids, subnodes.Map(x => x?.Id ?? Guid.Empty)) - ; - var result = new PointSetNode(data, self.Storage, true); + if (a.PointCountTree + b.PointCountTree <= config.OctreeSplitLimit) + { + var psla = new List(); + var csla = new List(); + var nsla = new List(); + var jsla = new List(); + var ksla = new List(); + var qsla = (object?)null; - // POST - if (result.IsLeaf) throw new InvalidOperationException(); - if (result.PointCountTree != self.PointCountTree) throw new InvalidOperationException(); - if (result.PointCountCell != 0) throw new InvalidOperationException(); - if (result.Subnodes.Sum(x => x?.Value?.PointCountTree) > self.PointCountTree) throw new InvalidOperationException(); - if (self.HasPartIndexRange && !result.HasPartIndexRange) throw new InvalidOperationException(); + CollectEverything(a, psla, csla, nsla, jsla, ksla, ref qsla); + CollectEverything(b, psla, csla, nsla, jsla, ksla, ref qsla); - return result; - } - /// - /// Returns union of trees as new tree (immutable operation). - /// - public static (IPointCloudNode, bool) Merge(this IPointCloudNode a, IPointCloudNode b, Action pointsMergedCallback, ImportConfig config) - { - if (!a.IsTemporaryImportNode || !b.IsTemporaryImportNode) throw new InvalidOperationException( - "Merge is only allowed on temporary import nodes. Invariant d53042e7-a032-47a9-98dc-034c0749a649." - ); + var range = PartIndexUtils.MergeRanges(a.PartIndexRange, b.PartIndexRange); - if (a == null || a.PointCountTree == 0) { pointsMergedCallback.Invoke(b.PointCountTree); return (b, true); } - if (b == null || b.PointCountTree == 0) { pointsMergedCallback.Invoke(a.PointCountTree); return (a, true); } + //var checkRange = PartIndexUtils.GetRange(qsla); + //if (range != checkRange) throw new Exception("!aasdfasdasd"); -#if DEBUG && NEVERMORE - if (config.Verbose) Report.Line($"[Merge] a = {a.Cell}, b = {b.Cell}"); -#endif - // expect - if (a.HasPartIndexRange != b.HasPartIndexRange) throw new Exception("Invariant ff2ea514-a7b7-46dd-b147-ff7bd58ed1db."); + var cell = new Cell(a.Cell, b.Cell); + var chunk = new Chunk( + psla.Count > 0 ? psla : null, + csla.Count > 0 ? csla : null, + nsla.Count > 0 ? nsla : null, + jsla.Count > 0 ? jsla : null, + ksla.Count > 0 ? ksla : null, + partIndices: qsla, + partIndexRange: range, + bbox: null + ); - if (a.PointCountTree + b.PointCountTree <= config.OctreeSplitLimit) + if (config.NormalizePointDensityGlobal) { - var psla = new List(); - var csla = new List(); - var nsla = new List(); - var jsla = new List(); - var ksla = new List(); - var qsla = (object?)null; + chunk = chunk.ImmutableFilterMinDistByCell(cell, config.ParseConfig); + } - CollectEverything(a, psla, csla, nsla, jsla, ksla, ref qsla); - CollectEverything(b, psla, csla, nsla, jsla, ksla, ref qsla); + var storage = config.Storage; + var ac = a.Center; + var bc = b.Center; + var psAbs = chunk.Positions.ToArray(); + var ns = chunk.Normals?.ToArray(); + var cs = chunk.Colors?.ToArray(); + var js = chunk.Intensities?.ToArray(); + var ks = chunk.Classifications?.ToArray(); + var qs = chunk.PartIndices; + var qsRange = chunk.PartIndexRange; - var range = PartIndexUtils.MergeRanges(a.PartIndexRange, b.PartIndexRange); + var bbExactGlobal = chunk.BoundingBox; - //var checkRange = PartIndexUtils.GetRange(qsla); - //if (range != checkRange) throw new Exception("!aasdfasdasd"); + Guid psId = Guid.NewGuid(); + //Guid? kdId = psAbs != null ? Guid.NewGuid() : (Guid?)null; + Guid? nsId = ns != null ? Guid.NewGuid() : null; + Guid? csId = cs != null ? Guid.NewGuid() : null; + Guid? jsId = js != null ? Guid.NewGuid() : null; + Guid? ksId = ks != null ? Guid.NewGuid() : null; - var cell = new Cell(a.Cell, b.Cell); - var chunk = new Chunk( - psla.Count > 0 ? psla : null, - csla.Count > 0 ? csla : null, - nsla.Count > 0 ? nsla : null, - jsla.Count > 0 ? jsla : null, - ksla.Count > 0 ? ksla : null, - partIndices: qsla, - partIndexRange: range, - bbox: null - ); + var center = cell.BoundingBox.Center; - if (config.NormalizePointDensityGlobal) - { - chunk = chunk.ImmutableFilterMinDistByCell(cell, config.ParseConfig); - } + var ps = psAbs.Map(p => (V3f)(p - center)); - var storage = config.Storage; - var ac = a.Center; - var bc = b.Center; - - var psAbs = chunk.Positions.ToArray(); - var ns = chunk.Normals?.ToArray(); - var cs = chunk.Colors?.ToArray(); - var js = chunk.Intensities?.ToArray(); - var ks = chunk.Classifications?.ToArray(); - var qs = chunk.PartIndices; - var qsRange = chunk.PartIndexRange; - - var bbExactGlobal = chunk.BoundingBox; - - Guid psId = Guid.NewGuid(); - //Guid? kdId = psAbs != null ? Guid.NewGuid() : (Guid?)null; - Guid? nsId = ns != null ? Guid.NewGuid() : null; - Guid? csId = cs != null ? Guid.NewGuid() : null; - Guid? jsId = js != null ? Guid.NewGuid() : null; - Guid? ksId = ks != null ? Guid.NewGuid() : null; - - - var center = cell.BoundingBox.Center; - - var ps = psAbs.Map(p => (V3f)(p - center)); - - var data = ImmutableDictionary.Empty - .Add(PointSetNode.TemporaryImportNode, 0) - .Add(Durable.Octree.NodeId, Guid.NewGuid()) - .Add(Durable.Octree.Cell, cell) - .Add(Durable.Octree.BoundingBoxExactGlobal, bbExactGlobal) - .Add(Durable.Octree.PointCountTreeLeafs, ps.LongLength) - ; - - storage.Add(psId, ps ); data = data.Add(Durable.Octree.PositionsLocal3fReference, psId); - //if (kdId.HasValue) { storage.Add(kdId.Value, kd.Data); data = data.Add(Durable.Octree.PointRkdTreeFDataReference, kdId.Value); } - if (nsId.HasValue) { storage.Add(nsId.Value, ns!); data = data.Add(Durable.Octree.Normals3fReference , nsId.Value); } - if (csId.HasValue) { storage.Add(csId.Value, cs!); data = data.Add(Durable.Octree.Colors4bReference , csId.Value); } - if (jsId.HasValue) { storage.Add(jsId.Value, js!); data = data.Add(Durable.Octree.Intensities1iReference , jsId.Value); } - if (ksId.HasValue) { storage.Add(ksId.Value, ks!); data = data.Add(Durable.Octree.Classifications1bReference, ksId.Value); } - if (qs != null) - { - if (qsRange == null) throw new Exception("Invariant 7c01f554-d833-42cc-ab1d-9dbaa61bef45."); - data = data.Add(Durable.Octree.PartIndexRange, qsRange); + var data = ImmutableDictionary.Empty + .Add(PointSetNode.TemporaryImportNode, 0) + .Add(Durable.Octree.NodeId, Guid.NewGuid()) + .Add(Durable.Octree.Cell, cell) + .Add(Durable.Octree.BoundingBoxExactGlobal, bbExactGlobal) + .Add(Durable.Octree.PointCountTreeLeafs, ps.LongLength) + ; - if (qs is Array qsArray) - { - // store separately and reference by id ... - var id = Guid.NewGuid(); - storage.Add(id, qsArray); - data = qs switch - { - byte[] => data.Add(Durable.Octree.PerPointPartIndex1bReference, id), - short[] => data.Add(Durable.Octree.PerPointPartIndex1sReference, id), - int[] => data.Add(Durable.Octree.PerPointPartIndex1iReference, id), - _ => throw new Exception($"Unexpected type {qs.GetType()}. Invariant cc05e74c-8cac-4d32-9972-0ea53e6e0911."), - }; - } - else + storage.Add(psId, ps ); data = data.Add(Durable.Octree.PositionsLocal3fReference, psId); + //if (kdId.HasValue) { storage.Add(kdId.Value, kd.Data); data = data.Add(Durable.Octree.PointRkdTreeFDataReference, kdId.Value); } + if (nsId.HasValue) { storage.Add(nsId.Value, ns!); data = data.Add(Durable.Octree.Normals3fReference , nsId.Value); } + if (csId.HasValue) { storage.Add(csId.Value, cs!); data = data.Add(Durable.Octree.Colors4bReference , csId.Value); } + if (jsId.HasValue) { storage.Add(jsId.Value, js!); data = data.Add(Durable.Octree.Intensities1iReference , jsId.Value); } + if (ksId.HasValue) { storage.Add(ksId.Value, ks!); data = data.Add(Durable.Octree.Classifications1bReference, ksId.Value); } + if (qs != null) + { + if (qsRange == null) throw new Exception("Invariant 7c01f554-d833-42cc-ab1d-9dbaa61bef45."); + data = data.Add(Durable.Octree.PartIndexRange, qsRange); + + if (qs is Array qsArray) + { + // store separately and reference by id ... + var id = Guid.NewGuid(); + storage.Add(id, qsArray); + data = qs switch { - var def = PartIndexUtils.GetDurableDefForPartIndices(qs); - data = data.Add(def, qs); - } - + byte[] => data.Add(Durable.Octree.PerPointPartIndex1bReference, id), + short[] => data.Add(Durable.Octree.PerPointPartIndex1sReference, id), + int[] => data.Add(Durable.Octree.PerPointPartIndex1iReference, id), + _ => throw new Exception($"Unexpected type {qs.GetType()}. Invariant cc05e74c-8cac-4d32-9972-0ea53e6e0911."), + }; } - - var result = new PointSetNode(data, config.Storage, writeToStore: true); - if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant 28925464-2ff0-49e8-bf77-c97cbb2dcb47."); - return (result, true); + else + { + var def = PartIndexUtils.GetDurableDefForPartIndices(qs); + data = data.Add(def, qs); + } + } + var result = new PointSetNode(data, config.Storage, writeToStore: true); + if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant 28925464-2ff0-49e8-bf77-c97cbb2dcb47."); + return (result, true); + } - // if A and B have identical root cells, then merge ... - if (a.Cell == b.Cell) - { - var result = a.IsLeaf - ? (b.IsLeaf ? MergeLeafAndLeafWithIdenticalRootCell(a, b, config) - : MergeLeafAndTreeWithIdenticalRootCell(a, b, config)) - : (b.IsLeaf ? MergeLeafAndTreeWithIdenticalRootCell(b, a, config) - : MergeTreeAndTreeWithIdenticalRootCell(a, b, pointsMergedCallback, config)) - ; - - //DidMergeWork(a, b, result); - pointsMergedCallback?.Invoke(a.PointCountTree + b.PointCountTree); - if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant 6b05a096-bab5-4a51-8b4c-1eff19e6806d."); - return result.CollapseLeafNodes(config); - } - // if A and B do not intersect ... - if (!a.Cell.Intersects(b.Cell)) - { - var rootCell = new Cell(a.Cell, b.Cell); - var result = JoinNonOverlappingTrees(rootCell, a, b, pointsMergedCallback, config); + // if A and B have identical root cells, then merge ... + if (a.Cell == b.Cell) + { + var result = a.IsLeaf + ? (b.IsLeaf ? MergeLeafAndLeafWithIdenticalRootCell(a, b, config) + : MergeLeafAndTreeWithIdenticalRootCell(a, b, config)) + : (b.IsLeaf ? MergeLeafAndTreeWithIdenticalRootCell(b, a, config) + : MergeTreeAndTreeWithIdenticalRootCell(a, b, pointsMergedCallback, config)) + ; + + //DidMergeWork(a, b, result); + pointsMergedCallback?.Invoke(a.PointCountTree + b.PointCountTree); + if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant 6b05a096-bab5-4a51-8b4c-1eff19e6806d."); + return result.CollapseLeafNodes(config); + } + + // if A and B do not intersect ... + if (!a.Cell.Intersects(b.Cell)) + { + var rootCell = new Cell(a.Cell, b.Cell); + var result = JoinNonOverlappingTrees(rootCell, a, b, pointsMergedCallback, config); //#if DEBUG // if (!config.NormalizePointDensityGlobal && result.PointCountTree != totalPointCountTree) throw new InvalidOperationException(); //#endif - pointsMergedCallback?.Invoke(a.PointCountTree + b.PointCountTree); - if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant b4f38aff-6499-44cf-9f0f-f8c3e78f0538."); - return result.CollapseLeafNodes(config); - } + pointsMergedCallback?.Invoke(a.PointCountTree + b.PointCountTree); + if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant b4f38aff-6499-44cf-9f0f-f8c3e78f0538."); + return result.CollapseLeafNodes(config); + } - if (a.Cell.IsCenteredAtOrigin || b.Cell.IsCenteredAtOrigin) + if (a.Cell.IsCenteredAtOrigin || b.Cell.IsCenteredAtOrigin) + { + // enumerate all non-IsCenteredAtOrigin (sub)cells of A and B + var parts = new List(); + if (a.Cell.IsCenteredAtOrigin) { - // enumerate all non-IsCenteredAtOrigin (sub)cells of A and B - var parts = new List(); - if (a.Cell.IsCenteredAtOrigin) + if (a.IsLeaf) { - if (a.IsLeaf) - { - // split A into 8 subcells to get rid of centered cell - var aSplit = a.ForceSplitLeaf(config); - if (a.HasPartIndexRange != aSplit.HasPartIndexRange) throw new Exception("Invariant 018b94f4-50fe-4d15-a2fe-6e5d93cbf9a0."); - return Merge(aSplit, b, pointsMergedCallback, config); - } - else - { - parts.AddRange(a.Subnodes.Select(x => x?.Value)); - } + // split A into 8 subcells to get rid of centered cell + var aSplit = a.ForceSplitLeaf(config); + if (a.HasPartIndexRange != aSplit.HasPartIndexRange) throw new Exception("Invariant 018b94f4-50fe-4d15-a2fe-6e5d93cbf9a0."); + return Merge(aSplit, b, pointsMergedCallback, config); } else { - parts.Add(a); + parts.AddRange(a.Subnodes.Select(x => x?.Value)); } + } + else + { + parts.Add(a); + } - if (b.Cell.IsCenteredAtOrigin) + if (b.Cell.IsCenteredAtOrigin) + { + if (b.IsLeaf) { - if (b.IsLeaf) - { - // split B into 8 subcells to get rid of centered cell - var bSplit = b.ForceSplitLeaf(config); - if (a.HasPartIndexRange != bSplit.HasPartIndexRange) throw new Exception("Invariant 59650027-2b6e-4ddd-8c3b-2bf6b3d3e08b."); - return Merge(a, bSplit, pointsMergedCallback, config); - } - else - { - parts.AddRange(b.Subnodes.Select(x => x?.Value)); - } + // split B into 8 subcells to get rid of centered cell + var bSplit = b.ForceSplitLeaf(config); + if (a.HasPartIndexRange != bSplit.HasPartIndexRange) throw new Exception("Invariant 59650027-2b6e-4ddd-8c3b-2bf6b3d3e08b."); + return Merge(a, bSplit, pointsMergedCallback, config); } else { - parts.Add(b); + parts.AddRange(b.Subnodes.Select(x => x?.Value)); } + } + else + { + parts.Add(b); + } - // special case: there is only 1 part -> finished - List partsNonNull = parts.Where(x => x != null).ToList()!; - if (partsNonNull.Count == 0) throw new InvalidOperationException(); - if (partsNonNull.Count == 1) - { - var r = partsNonNull.Single(); - pointsMergedCallback?.Invoke(r.PointCountTree); - if (a.HasPartIndexRange != r.HasPartIndexRange) throw new Exception("Invariant 2d6be9c1-0f65-48d3-985a-856a82085dca."); - return r.CollapseLeafNodes(config); - } + // special case: there is only 1 part -> finished + List partsNonNull = parts.Where(x => x != null).ToList()!; + if (partsNonNull.Count == 0) throw new InvalidOperationException(); + if (partsNonNull.Count == 1) + { + var r = partsNonNull.Single(); + pointsMergedCallback?.Invoke(r.PointCountTree); + if (a.HasPartIndexRange != r.HasPartIndexRange) throw new Exception("Invariant 2d6be9c1-0f65-48d3-985a-856a82085dca."); + return r.CollapseLeafNodes(config); + } - // common case: multiple parts - var rootCell = new Cell(a.Cell, b.Cell); - var roots = new IPointCloudNode[8]; + // common case: multiple parts + var rootCell = new Cell(a.Cell, b.Cell); + var roots = new IPointCloudNode[8]; - static int octant(Cell x) - { - if (x.IsCenteredAtOrigin) throw new InvalidOperationException(); - return (x.X >= 0 ? 1 : 0) + (x.Y >= 0 ? 2 : 0) + (x.Z >= 0 ? 4 : 0); - } + static int octant(Cell x) + { + if (x.IsCenteredAtOrigin) throw new InvalidOperationException(); + return (x.X >= 0 ? 1 : 0) + (x.Y >= 0 ? 2 : 0) + (x.Z >= 0 ? 4 : 0); + } - var qsRange = (Range1i?)null; - foreach (var x in partsNonNull) + var qsRange = (Range1i?)null; + foreach (var x in partsNonNull) + { + var oi = octant(x.Cell); + var oct = rootCell.GetOctant(oi); + IPointCloudNode r; + if (roots[oi] == null) { - var oi = octant(x.Cell); - var oct = rootCell.GetOctant(oi); - IPointCloudNode r; - if (roots[oi] == null) + if (x.Cell != oct) { - if (x.Cell != oct) - { - if (!oct.Contains(x.Cell)) throw new InvalidOperationException(); - r = JoinTreeToRootCell(oct, x, config); - if (oct != r.Cell) throw new InvalidOperationException(); - } - else - { - r = x; - if (oct != r.Cell) throw new InvalidOperationException(); - } + if (!oct.Contains(x.Cell)) throw new InvalidOperationException(); + r = JoinTreeToRootCell(oct, x, config); + if (oct != r.Cell) throw new InvalidOperationException(); } else { - r = Merge(roots[oi], x, pointsMergedCallback, config).Item1; + r = x; if (oct != r.Cell) throw new InvalidOperationException(); } - + } + else + { + r = Merge(roots[oi], x, pointsMergedCallback, config).Item1; if (oct != r.Cell) throw new InvalidOperationException(); - roots[oi] = r; - - qsRange = PartIndexUtils.MergeRanges(qsRange, r.PartIndexRange); } - var pointCountTreeLeafs = roots.Where(x => x != null).Sum(x => x.PointCountTree); - var bbExactGlobal = new Box3d(roots.Where(x => x != null).Select(x => x.BoundingBoxExactGlobal)); - pointsMergedCallback?.Invoke(pointCountTreeLeafs); + if (oct != r.Cell) throw new InvalidOperationException(); + roots[oi] = r; - var data = ImmutableDictionary.Empty - .Add(PointSetNode.TemporaryImportNode, 0) - .Add(Durable.Octree.NodeId, Guid.NewGuid()) - .Add(Durable.Octree.Cell, rootCell) - .Add(Durable.Octree.BoundingBoxExactGlobal, bbExactGlobal) - .Add(Durable.Octree.PointCountTreeLeafs, pointCountTreeLeafs) - .Add(Durable.Octree.SubnodesGuids, roots.Map(n => n?.Id ?? Guid.Empty)) - ; + qsRange = PartIndexUtils.MergeRanges(qsRange, r.PartIndexRange); + } - if (qsRange != null) data = data.Add(Durable.Octree.PartIndexRange, qsRange); + var pointCountTreeLeafs = roots.Where(x => x != null).Sum(x => x.PointCountTree); + var bbExactGlobal = new Box3d(roots.Where(x => x != null).Select(x => x.BoundingBoxExactGlobal)); + pointsMergedCallback?.Invoke(pointCountTreeLeafs); - var result = new PointSetNode(data, config.Storage, writeToStore: true); - if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant 71148683-b67c-41e5-aeff-2d63643fd440."); - return result.CollapseLeafNodes(config); - } + var data = ImmutableDictionary.Empty + .Add(PointSetNode.TemporaryImportNode, 0) + .Add(Durable.Octree.NodeId, Guid.NewGuid()) + .Add(Durable.Octree.Cell, rootCell) + .Add(Durable.Octree.BoundingBoxExactGlobal, bbExactGlobal) + .Add(Durable.Octree.PointCountTreeLeafs, pointCountTreeLeafs) + .Add(Durable.Octree.SubnodesGuids, roots.Map(n => n?.Id ?? Guid.Empty)) + ; + + if (qsRange != null) data = data.Add(Durable.Octree.PartIndexRange, qsRange); + + var result = new PointSetNode(data, config.Storage, writeToStore: true); + if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant 71148683-b67c-41e5-aeff-2d63643fd440."); + return result.CollapseLeafNodes(config); + } #if DEBUG - if (a.Cell.Exponent == b.Cell.Exponent) - { - if (!a.Cell.IsCenteredAtOrigin && !b.Cell.IsCenteredAtOrigin) throw new InvalidOperationException( - $"merge {a.Cell} with {b.Cell}") - ; - } + if (a.Cell.Exponent == b.Cell.Exponent) + { + if (!a.Cell.IsCenteredAtOrigin && !b.Cell.IsCenteredAtOrigin) throw new InvalidOperationException( + $"merge {a.Cell} with {b.Cell}") + ; + } #endif - // ... otherwise ensure that A's root cell is bigger than B's to reduce number of cases to handle ... - if (a.Cell.Exponent < b.Cell.Exponent) - { - var result = Merge(b, a, pointsMergedCallback, config); - if (a.HasPartIndexRange != result.Item1.HasPartIndexRange) throw new Exception("Invariant ce60ef6a-c752-4ada-a540-6eb8bf8b0aec."); - return result; - } + // ... otherwise ensure that A's root cell is bigger than B's to reduce number of cases to handle ... + if (a.Cell.Exponent < b.Cell.Exponent) + { + var result = Merge(b, a, pointsMergedCallback, config); + if (a.HasPartIndexRange != result.Item1.HasPartIndexRange) throw new Exception("Invariant ce60ef6a-c752-4ada-a540-6eb8bf8b0aec."); + return result; + } - // ... B must now be contained in exactly one of A's subcells + // ... B must now be contained in exactly one of A's subcells #if DEBUG - var isExactlyOne = false; + var isExactlyOne = false; #endif - var processedPointCount = 0L; - IPointCloudNode?[] subcells = a.Subnodes?.Map(x => x?.Value) ?? new IPointCloudNode?[8]; - for (var i = 0; i < 8; i++) + var processedPointCount = 0L; + IPointCloudNode?[] subcells = a.Subnodes?.Map(x => x?.Value) ?? new IPointCloudNode?[8]; + for (var i = 0; i < 8; i++) + { + var subcellIndex = a.Cell.GetOctant(i); + if (subcellIndex.Contains(b.Cell)) { - var subcellIndex = a.Cell.GetOctant(i); - if (subcellIndex.Contains(b.Cell)) - { #if DEBUG - if (isExactlyOne) throw new InvalidOperationException(); - isExactlyOne = true; + if (isExactlyOne) throw new InvalidOperationException(); + isExactlyOne = true; #endif - if (subcells[i] == null) - { - subcells[i] = JoinTreeToRootCell(subcellIndex, b, config); - } - else - { - subcells[i] = Merge(subcells[i]!, b, - n => pointsMergedCallback?.Invoke(processedPointCount + n), - config).Item1; - } - - processedPointCount += subcells[i]?.PointCountTree ?? 0L; - pointsMergedCallback?.Invoke(processedPointCount); + if (subcells[i] == null) + { + subcells[i] = JoinTreeToRootCell(subcellIndex, b, config); } + else + { + subcells[i] = Merge(subcells[i]!, b, + n => pointsMergedCallback?.Invoke(processedPointCount + n), + config).Item1; + } + + processedPointCount += subcells[i]?.PointCountTree ?? 0L; + pointsMergedCallback?.Invoke(processedPointCount); } + } #if DEBUG - if (!isExactlyOne) throw new InvalidOperationException(); + if (!isExactlyOne) throw new InvalidOperationException(); #endif - IPointCloudNode result2; - if (a.IsLeaf) - { - result2 = a.WithSubNodes(subcells!); - result2 = InjectPointsIntoTree( - a.PositionsAbsolute, a.Colors?.Value, a.Normals?.Value, a.Intensities?.Value, a.Classifications?.Value, a.PartIndices, - result2, result2.Cell, config); - } - else - { - result2 = a.WithSubNodes(subcells); - } - - pointsMergedCallback?.Invoke(result2.PointCountTree); - if (a.HasPartIndexRange != result2.HasPartIndexRange) throw new Exception("Invariant 18852eb2-1be0-4f22-9c9c-1f824c657685."); - return result2.CollapseLeafNodes(config); - } - - private static T[]? Concat(T[]? xs, T[]? ys) + IPointCloudNode result2; + if (a.IsLeaf) { - if (xs == null && ys == null) return null; - if ((xs == null) != (ys == null)) throw new InvalidOperationException(); - var rs = new T[xs!.Length + ys!.Length]; - Array.Copy(xs, 0, rs, 0, xs.Length); - Array.Copy(ys, 0, rs, xs.Length, ys.Length); - return rs; + result2 = a.WithSubNodes(subcells!); + result2 = InjectPointsIntoTree( + a.PositionsAbsolute, a.Colors?.Value, a.Normals?.Value, a.Intensities?.Value, a.Classifications?.Value, a.PartIndices, + result2, result2.Cell, config); } - - private static IPointCloudNode JoinNonOverlappingTrees(Cell rootCell, IPointCloudNode a, IPointCloudNode b, - Action pointsMergedCallback, ImportConfig config - ) + else { - #region Preconditions + result2 = a.WithSubNodes(subcells); + } - // PRE: ensure that trees 'a' and 'b' do not intersect, - // because we are joining non-overlapping trees here - if (a.Cell == b.Cell || a.Cell.Intersects(b.Cell)) throw new InvalidOperationException(); + pointsMergedCallback?.Invoke(result2.PointCountTree); + if (a.HasPartIndexRange != result2.HasPartIndexRange) throw new Exception("Invariant 18852eb2-1be0-4f22-9c9c-1f824c657685."); + return result2.CollapseLeafNodes(config); + } + + private static T[]? Concat(T[]? xs, T[]? ys) + { + if (xs == null && ys == null) return null; + if ((xs == null) != (ys == null)) throw new InvalidOperationException(); + var rs = new T[xs!.Length + ys!.Length]; + Array.Copy(xs, 0, rs, 0, xs.Length); + Array.Copy(ys, 0, rs, xs.Length, ys.Length); + return rs; + } + + private static IPointCloudNode JoinNonOverlappingTrees(Cell rootCell, IPointCloudNode a, IPointCloudNode b, + Action pointsMergedCallback, ImportConfig config + ) + { + #region Preconditions - // PRE: we further assume, that both trees are non-empty - if (a.PointCountTree == 0 && b.PointCountTree == 0) throw new InvalidOperationException(); + // PRE: ensure that trees 'a' and 'b' do not intersect, + // because we are joining non-overlapping trees here + if (a.Cell == b.Cell || a.Cell.Intersects(b.Cell)) throw new InvalidOperationException(); - // PRE: assume that part indices are available in both trees or in no tree (but not in one or the other) - if (a.HasPartIndexRange != b.HasPartIndexRange) throw new Exception("Invariant b3feedfd-927d-4436-9eb9-350d377ab852."); + // PRE: we further assume, that both trees are non-empty + if (a.PointCountTree == 0 && b.PointCountTree == 0) throw new InvalidOperationException(); - #endregion + // PRE: assume that part indices are available in both trees or in no tree (but not in one or the other) + if (a.HasPartIndexRange != b.HasPartIndexRange) throw new Exception("Invariant b3feedfd-927d-4436-9eb9-350d377ab852."); + var hasPartIndices = a.HasPartIndexRange && b.HasPartIndexRange; + #endregion - #region Case reduction + #region Case reduction - // REDUCE CASES: - // if one tree ('a' or 'b') is centered at origin, then ensure that 'a' is centered - // (by swapping 'a' and 'b' if necessary) - if (b.Cell.IsCenteredAtOrigin) - { + // REDUCE CASES: + // if one tree ('a' or 'b') is centered at origin, then ensure that 'a' is centered + // (by swapping 'a' and 'b' if necessary) + if (b.Cell.IsCenteredAtOrigin) + { #if DEBUG - // PRE: if 'b' is centered, than 'a' cannot be centered - // (because then 'a' and 'b' would overlap, and we join non-overlapping trees here) - if (a.Cell.IsCenteredAtOrigin) throw new InvalidOperationException(); + // PRE: if 'b' is centered, than 'a' cannot be centered + // (because then 'a' and 'b' would overlap, and we join non-overlapping trees here) + if (a.Cell.IsCenteredAtOrigin) throw new InvalidOperationException(); #endif - Fun.Swap(ref a, ref b); + Fun.Swap(ref a, ref b); #if DEBUG - // POST: 'a' is centered, 'b' is not centered - if (!a.Cell.IsCenteredAtOrigin) throw new InvalidOperationException(); - if (b.Cell.IsCenteredAtOrigin) throw new InvalidOperationException(); + // POST: 'a' is centered, 'b' is not centered + if (!a.Cell.IsCenteredAtOrigin) throw new InvalidOperationException(); + if (b.Cell.IsCenteredAtOrigin) throw new InvalidOperationException(); #endif - } + } #endregion - + #region CASE 1 of 2: one tree is centered (must be 'a', since if it originally was 'b' we would have swapped) - if (rootCell.IsCenteredAtOrigin && a.Cell.IsCenteredAtOrigin) - { + if (rootCell.IsCenteredAtOrigin && a.Cell.IsCenteredAtOrigin) + { #region special case: split 'a' into subcells to get rid of centered cell containing points - if (a.IsLeaf) - { - var r = JoinNonOverlappingTrees(rootCell, a.ForceSplitLeaf(config), b, pointsMergedCallback, config); - if (a.HasPartIndexRange != r.HasPartIndexRange) throw new Exception("Invariant b3feedfd-927d-4436-9eb9-350d377ab852."); - return r; - } + if (a.IsLeaf) + { + var aForceSplitLeaf = a.ForceSplitLeaf(config); + if (aForceSplitLeaf.HasPartIndexRange != b.HasPartIndexRange) throw new Exception("Invariant f4b031c0-c81f-4e81-87d9-35ea15b2b9ea."); + var r = JoinNonOverlappingTrees(rootCell, aForceSplitLeaf, b, pointsMergedCallback, config); + if (hasPartIndices && !r.HasPartIndexRange) throw new Exception("Invariant b3feedfd-927d-4436-9eb9-350d377ab852."); + return r; + } #endregion #if DEBUG - if (a.PointCountCell != 0) throw new InvalidOperationException(); + if (a.PointCountCell != 0) throw new InvalidOperationException(); #endif - var subcells = new IPointCloudNode?[8]; - for (var i = 0; i < 8; i++) - { - var rootCellOctant = rootCell.GetOctant(i); + var subcells = new IPointCloudNode?[8]; + for (var i = 0; i < 8; i++) + { + var rootCellOctant = rootCell.GetOctant(i); - var aSub = a.Subnodes![i]?.Value; - var bIsContained = rootCellOctant.Contains(b.Cell); + var aSub = a.Subnodes![i]?.Value; + var bIsContained = rootCellOctant.Contains(b.Cell); #if DEBUG - if (!bIsContained && rootCellOctant.Intersects(b.Cell)) throw new InvalidOperationException(); + if (!bIsContained && rootCellOctant.Intersects(b.Cell)) throw new InvalidOperationException(); #endif - if (aSub != null) + if (aSub != null) + { + if (bIsContained) { - if (bIsContained) - { - // CASE: both contained - var merged = Merge(aSub, b, pointsMergedCallback, config).Item1; - subcells[i] = JoinTreeToRootCell(rootCellOctant, merged, config); - } - else - { - // CASE: aSub contained - subcells[i] = JoinTreeToRootCell(rootCellOctant, aSub, config); - } + // CASE: both contained + if (aSub.HasPartIndexRange != b.HasPartIndexRange) throw new Exception("Invariant 7bbc36bb-8dba-4224-b6fb-660bc40b0829."); + var merged = Merge(aSub, b, pointsMergedCallback, config).Item1; + if (hasPartIndices && !merged.HasPartIndexRange) throw new Exception("Invariant 9c41c146-4cce-4313-b16a-38c20d07266f."); + subcells[i] = JoinTreeToRootCell(rootCellOctant, merged, config); + if (hasPartIndices && !subcells[i]!.HasPartIndexRange) throw new Exception("Invariant 45548cc0-f069-4812-9a36-fb3e2cbe9adf."); } else { - if (bIsContained) - { - // CASE: b contained - subcells[i] = JoinTreeToRootCell(rootCellOctant, b, config); - } - else - { - // CASE: none contained -> empty subcell - subcells[i] = null; - } + // CASE: aSub contained + if (hasPartIndices && !aSub.HasPartIndexRange) throw new Exception("Invariant cd7ea7d5-ad25-4c6d-9e04-457c4fcb8710."); + subcells[i] = JoinTreeToRootCell(rootCellOctant, aSub, config); + if (hasPartIndices && !subcells[i]!.HasPartIndexRange) throw new Exception("Invariant 896cbd9e-5891-4af3-b990-2873fe6568e5."); } } - - var data = ImmutableDictionary.Empty - .Add(PointSetNode.TemporaryImportNode, 0) - .Add(Durable.Octree.NodeId, Guid.NewGuid()) - .Add(Durable.Octree.Cell, rootCell) - .Add(Durable.Octree.PointCountTreeLeafs, a.PointCountTree + b.PointCountTree) - .Add(Durable.Octree.SubnodesGuids, subcells.Map(x => x?.Id ?? Guid.Empty)) - ; - var result = new PointSetNode(data, config.Storage, writeToStore: false).CollapseLeafNodes(config).Item1; -#if DEBUG - if (result.PointCountTree != result.Subnodes.Sum(x => x?.Value?.PointCountTree)) throw new InvalidOperationException(); -#endif - //pointsMergedCallback?.Invoke(result.PointCountTree); - if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant 595235c3-7541-41e4-9bca-77d38daff7fd."); - return result; - } - -#endregion - -#region CASE 2 of 2: no tree is centered - - else - { -#if DEBUG - // PRE: no tree is centered - if (a.Cell.IsCenteredAtOrigin) throw new InvalidOperationException(); - if (b.Cell.IsCenteredAtOrigin) throw new InvalidOperationException(); -#endif - - var subcells = new IPointCloudNode[8]; - var doneA = false; - var doneB = false; - for (var i = 0; i < 8; i++) + else { - var subcell = rootCell.GetOctant(i); - if (subcell.Contains(a.Cell)) + if (bIsContained) { -#if DEBUG - if (subcell.Intersects(b.Cell)) throw new InvalidOperationException(); -#endif - subcells[i] = JoinTreeToRootCell(subcell, a, config); - if (doneB) break; - doneA = true; + // CASE: b contained + if (hasPartIndices && !b.HasPartIndexRange) throw new Exception("Invariant 12803bc1-e910-4a2c-95da-aabe26c1355f."); + subcells[i] = JoinTreeToRootCell(rootCellOctant, b, config); + if (hasPartIndices && !subcells[i]!.HasPartIndexRange) throw new Exception("Invariant 424b1b8f-cdad-4ad9-bd22-f6d66e2e67b5."); } - if (subcell.Intersects(b.Cell)) + else { -#if DEBUG - if (subcell.Intersects(a.Cell)) throw new InvalidOperationException(); -#endif - subcells[i] = JoinTreeToRootCell(subcell, b, config); - if (doneA == true) break; - doneB = true; + // CASE: none contained -> empty subcell + subcells[i] = null; } } + } - var pointCountTree = subcells.Sum(x => x?.PointCountTree); - var bbExactGlobal = new Box3d(subcells.Where(x => x != null).Select(x => x.BoundingBoxExactGlobal)); - - var data = ImmutableDictionary.Empty - .Add(PointSetNode.TemporaryImportNode, 0) - .Add(Durable.Octree.NodeId, Guid.NewGuid()) - .Add(Durable.Octree.Cell, rootCell) - .Add(Durable.Octree.BoundingBoxExactGlobal, bbExactGlobal) - .Add(Durable.Octree.PointCountTreeLeafs, a.PointCountTree + b.PointCountTree) - .Add(Durable.Octree.SubnodesGuids, subcells.Map(x => x?.Id ?? Guid.Empty)) - ; - - if (a.HasPartIndexRange) - { - var mergedPartIndexRange = PartIndexUtils.MergeRanges(a.PartIndexRange, b.PartIndexRange) ?? throw new Exception("Invariant d4ed616f-a348-4303-8e64-651d669cb7bc."); - data = data.Add(Durable.Octree.PartIndexRange, mergedPartIndexRange); - } + var data = ImmutableDictionary.Empty + .Add(PointSetNode.TemporaryImportNode, 0) + .Add(Durable.Octree.NodeId, Guid.NewGuid()) + .Add(Durable.Octree.Cell, rootCell) + .Add(Durable.Octree.PointCountTreeLeafs, a.PointCountTree + b.PointCountTree) + .Add(Durable.Octree.SubnodesGuids, subcells.Map(x => x?.Id ?? Guid.Empty)) + ; - var result = new PointSetNode(data, config.Storage, writeToStore: false).CollapseLeafNodes(config).Item1; + if (a.HasPartIndexRange) + { + var mergedPartIndexRange = PartIndexUtils.MergeRanges(a.PartIndexRange, b.PartIndexRange) ?? throw new Exception("Invariant 2cac0791-b636-458c-b5bd-c3ef66477511."); + data = data.Add(Durable.Octree.PartIndexRange, mergedPartIndexRange); + } + var result = new PointSetNode(data, config.Storage, writeToStore: false).CollapseLeafNodes(config).Item1; #if DEBUG - if (result.PointCountTree != a.PointCountTree + b.PointCountTree) throw new InvalidOperationException(); - if (result.PointCountTree != pointCountTree) throw new InvalidOperationException( - $"Invariant d2957ed7-d12c-461c-ae79-5181a4197654. {result.PointCountTree} != {pointCountTree}." - ); + if (result.PointCountTree != result.Subnodes.Sum(x => x?.Value?.PointCountTree)) throw new InvalidOperationException(); #endif - //pointsMergedCallback?.Invoke(result.PointCountTree);/pointsMergedCallback?.Invoke(result.PointCountTree); - if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant 2a5e4624-1597-4933-a6ba-50d41097af6a."); - return result; - } + //pointsMergedCallback?.Invoke(result.PointCountTree); + if (hasPartIndices && !result.HasPartIndexRange) throw new Exception("Invariant 595235c3-7541-41e4-9bca-77d38daff7fd."); + return result; + } #endregion - } - internal static IPointCloudNode JoinTreeToRootCell(Cell rootCell, IPointCloudNode a, ImportConfig config, bool collapse = true) - { - if (!rootCell.Contains(a.Cell)) throw new InvalidOperationException(); +#region CASE 2 of 2: no tree is centered - if (a.Cell.IsCenteredAtOrigin) - { - throw new InvalidOperationException(); - } - if (rootCell == a.Cell) return a; + else + { +#if DEBUG + // PRE: no tree is centered + if (a.Cell.IsCenteredAtOrigin) throw new InvalidOperationException(); + if (b.Cell.IsCenteredAtOrigin) throw new InvalidOperationException(); +#endif var subcells = new IPointCloudNode[8]; + var doneA = false; + var doneB = false; for (var i = 0; i < 8; i++) { var subcell = rootCell.GetOctant(i); - if (subcell == a.Cell) { subcells[i] = a; break; } - if (subcell.Contains(a.Cell)) { subcells[i] = JoinTreeToRootCell(subcell, a, config, collapse); break; } + if (subcell.Contains(a.Cell)) + { +#if DEBUG + if (subcell.Intersects(b.Cell)) throw new InvalidOperationException(); +#endif + subcells[i] = JoinTreeToRootCell(subcell, a, config); + if (doneB) break; + doneA = true; + } + if (subcell.Intersects(b.Cell)) + { +#if DEBUG + if (subcell.Intersects(a.Cell)) throw new InvalidOperationException(); +#endif + subcells[i] = JoinTreeToRootCell(subcell, b, config); + if (doneA == true) break; + doneB = true; + } } + var pointCountTree = subcells.Sum(x => x?.PointCountTree); + var bbExactGlobal = new Box3d(subcells.Where(x => x != null).Select(x => x.BoundingBoxExactGlobal)); + var data = ImmutableDictionary.Empty .Add(PointSetNode.TemporaryImportNode, 0) .Add(Durable.Octree.NodeId, Guid.NewGuid()) .Add(Durable.Octree.Cell, rootCell) - .Add(Durable.Octree.BoundingBoxExactGlobal, a.BoundingBoxExactGlobal) - .Add(Durable.Octree.PointCountTreeLeafs, a.PointCountTree) + .Add(Durable.Octree.BoundingBoxExactGlobal, bbExactGlobal) + .Add(Durable.Octree.PointCountTreeLeafs, a.PointCountTree + b.PointCountTree) .Add(Durable.Octree.SubnodesGuids, subcells.Map(x => x?.Id ?? Guid.Empty)) ; - if (a.HasPartIndexRange) data = data.Add(Durable.Octree.PartIndexRange, a.PartIndexRange); + if (a.HasPartIndexRange) + { + var mergedPartIndexRange = PartIndexUtils.MergeRanges(a.PartIndexRange, b.PartIndexRange) ?? throw new Exception("Invariant d4ed616f-a348-4303-8e64-651d669cb7bc."); + data = data.Add(Durable.Octree.PartIndexRange, mergedPartIndexRange); + } - var result = (IPointCloudNode)new PointSetNode(data, config.Storage, writeToStore: false); - if (collapse) result = result.CollapseLeafNodes(config).Item1; - else result = result.WriteToStore(); + var result = new PointSetNode(data, config.Storage, writeToStore: false).CollapseLeafNodes(config).Item1; - if (a.PartIndexRange != result.PartIndexRange) throw new Exception("Invariant 8386bc52-2f58-4bbb-8260-25300d0b4e0f."); +#if DEBUG + if (result.PointCountTree != a.PointCountTree + b.PointCountTree) throw new InvalidOperationException(); + if (result.PointCountTree != pointCountTree) throw new InvalidOperationException( + $"Invariant d2957ed7-d12c-461c-ae79-5181a4197654. {result.PointCountTree} != {pointCountTree}." + ); +#endif + //pointsMergedCallback?.Invoke(result.PointCountTree);/pointsMergedCallback?.Invoke(result.PointCountTree); + if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant 2a5e4624-1597-4933-a6ba-50d41097af6a."); return result; } - private static IPointCloudNode MergeLeafAndLeafWithIdenticalRootCell(IPointCloudNode a, IPointCloudNode b, ImportConfig config) - { - if (!a.IsTemporaryImportNode || !b.IsTemporaryImportNode) throw new InvalidOperationException( - "MergeLeafAndLeafWithIdenticalRootCell is only valid for temporary import nodes. Invariant 2d68b9d2-a001-47a8-b481-87488f33b85d." - ); +#endregion + } - if (a.IsLeaf == false || b.IsLeaf == false) throw new InvalidOperationException(); - if (a.Cell != b.Cell) throw new InvalidOperationException(); - if (b.PositionsAbsolute == null) throw new InvalidOperationException(); - if (a.HasColors != b.HasColors) throw new InvalidOperationException(); - if (a.HasNormals != b.HasNormals) throw new InvalidOperationException(); - if (a.HasIntensities != b.HasIntensities) throw new InvalidOperationException(); - if (a.HasClassifications != b.HasClassifications) throw new InvalidOperationException(); - if (a.HasPartIndexRange != b.HasPartIndexRange) throw new Exception("Invariant 653807fb-16d5-42d5-bf24-d48635195c92."); - - var cell = a.Cell; - - var ps = Concat(a.PositionsAbsolute, b.PositionsAbsolute); - var cs = Concat(a.Colors?.Value, b.Colors?.Value); - var ns = Concat(a.Normals?.Value, b.Normals?.Value); - var js = Concat(a.Intensities?.Value, b.Intensities?.Value); - var ks = Concat(a.Classifications?.Value, b.Classifications?.Value); - var qs = PartIndexUtils.ConcatIndices(a.PartIndices, a.PointCountCell, b.PartIndices, b.PointCountCell); - - var chunk = new Chunk(ps, cs, ns, js, ks, partIndices: qs, partIndexRange: null, cell.BoundingBox); - if (config.NormalizePointDensityGlobal) - { - chunk = chunk.ImmutableFilterMinDistByCell(cell, config.ParseConfig); - } - var result = InMemoryPointSet.Build(chunk, cell, config.OctreeSplitLimit).ToPointSetNode(config.Storage, isTemporaryImportNode: true); - if (a.Cell != result.Cell) throw new InvalidOperationException("Invariant 771d781a-6d37-4017-a890-4f72a96a01a8."); - if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant 583675f5-5fb2-4b2d-9b55-3c559bac8bd4."); - return result; + internal static IPointCloudNode JoinTreeToRootCell(Cell rootCell, IPointCloudNode a, ImportConfig config, bool collapse = true) + { + if (!rootCell.Contains(a.Cell)) throw new InvalidOperationException(); + + if (a.Cell.IsCenteredAtOrigin) + { + throw new InvalidOperationException(); } + if (rootCell == a.Cell) return a; - private static IPointCloudNode MergeLeafAndTreeWithIdenticalRootCell(IPointCloudNode a, IPointCloudNode b, ImportConfig config) + var subcells = new IPointCloudNode[8]; + for (var i = 0; i < 8; i++) { - if (a == null) throw new ArgumentNullException(nameof(a)); - if (b == null) throw new ArgumentNullException(nameof(b)); - if (a.IsLeaf == false || b.IsLeaf == true) throw new InvalidOperationException(); - if (a.Cell != b.Cell) throw new InvalidOperationException(); - if (a.HasPartIndexRange != b.HasPartIndexRange) throw new Exception("Invariant 7fe9ebc7-b51e-4a5a-8aa8-0e08b63b6240."); - - var result = InjectPointsIntoTree(a.PositionsAbsolute, a.Colors?.Value, a.Normals?.Value, a.Intensities?.Value, a.Classifications?.Value, a.PartIndices, b, a.Cell, config); - if (a.Cell != result.Cell) throw new InvalidOperationException("Invariant 55551919-1a11-4ea9-bb4e-6f1a6b15e3d5."); - if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant bf22e8ce-b517-4f42-90d8-947db603c244."); - return result; + var subcell = rootCell.GetOctant(i); + if (subcell == a.Cell) { subcells[i] = a; break; } + if (subcell.Contains(a.Cell)) { subcells[i] = JoinTreeToRootCell(subcell, a, config, collapse); break; } } - private static IPointCloudNode MergeTreeAndTreeWithIdenticalRootCell(IPointCloudNode a, IPointCloudNode b, - Action pointsMergedCallback, - ImportConfig config - ) + var data = ImmutableDictionary.Empty + .Add(PointSetNode.TemporaryImportNode, 0) + .Add(Durable.Octree.NodeId, Guid.NewGuid()) + .Add(Durable.Octree.Cell, rootCell) + .Add(Durable.Octree.BoundingBoxExactGlobal, a.BoundingBoxExactGlobal) + .Add(Durable.Octree.PointCountTreeLeafs, a.PointCountTree) + .Add(Durable.Octree.SubnodesGuids, subcells.Map(x => x?.Id ?? Guid.Empty)) + ; + + if (a.HasPartIndexRange) data = data.Add(Durable.Octree.PartIndexRange, a.PartIndexRange); + + var result = (IPointCloudNode)new PointSetNode(data, config.Storage, writeToStore: false); + if (collapse) result = result.CollapseLeafNodes(config).Item1; + else result = result.WriteToStore(); + + if (a.PartIndexRange != result.PartIndexRange) throw new Exception("Invariant 8386bc52-2f58-4bbb-8260-25300d0b4e0f."); + return result; + } + + private static IPointCloudNode MergeLeafAndLeafWithIdenticalRootCell(IPointCloudNode a, IPointCloudNode b, ImportConfig config) + { + if (!a.IsTemporaryImportNode || !b.IsTemporaryImportNode) throw new InvalidOperationException( + "MergeLeafAndLeafWithIdenticalRootCell is only valid for temporary import nodes. Invariant 2d68b9d2-a001-47a8-b481-87488f33b85d." + ); + + if (a.IsLeaf == false || b.IsLeaf == false) throw new InvalidOperationException(); + if (a.Cell != b.Cell) throw new InvalidOperationException(); + if (b.PositionsAbsolute == null) throw new InvalidOperationException(); + if (a.HasColors != b.HasColors) throw new InvalidOperationException(); + if (a.HasNormals != b.HasNormals) throw new InvalidOperationException(); + if (a.HasIntensities != b.HasIntensities) throw new InvalidOperationException(); + if (a.HasClassifications != b.HasClassifications) throw new InvalidOperationException(); + if (a.HasPartIndexRange != b.HasPartIndexRange) throw new Exception("Invariant 653807fb-16d5-42d5-bf24-d48635195c92."); + + var cell = a.Cell; + + var ps = Concat(a.PositionsAbsolute, b.PositionsAbsolute); + var cs = Concat(a.Colors?.Value, b.Colors?.Value); + var ns = Concat(a.Normals?.Value, b.Normals?.Value); + var js = Concat(a.Intensities?.Value, b.Intensities?.Value); + var ks = Concat(a.Classifications?.Value, b.Classifications?.Value); + var qs = PartIndexUtils.ConcatIndices(a.PartIndices, a.PointCountCell, b.PartIndices, b.PointCountCell); + + var chunk = new Chunk(ps, cs, ns, js, ks, partIndices: qs, partIndexRange: null, cell.BoundingBox); + if (config.NormalizePointDensityGlobal) { - if (a.IsLeaf || b.IsLeaf) throw new InvalidOperationException(); - if (a.Cell != b.Cell) throw new InvalidOperationException(); - if (a.PointCountCell > 0) throw new InvalidOperationException(); - if (b.PointCountCell > 0) throw new InvalidOperationException(); - if (a.HasPartIndexRange != b.HasPartIndexRange) throw new Exception("Invariant 0103f130-f022-42e5-8efc-da4e5747177a."); + chunk = chunk.ImmutableFilterMinDistByCell(cell, config.ParseConfig); + } + var result = InMemoryPointSet.Build(chunk, cell, config.OctreeSplitLimit).ToPointSetNode(config.Storage, isTemporaryImportNode: true); + if (a.Cell != result.Cell) throw new InvalidOperationException("Invariant 771d781a-6d37-4017-a890-4f72a96a01a8."); + if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant 583675f5-5fb2-4b2d-9b55-3c559bac8bd4."); + return result; + } - var pointCountTree = 0L; - var subcells = new IPointCloudNode?[8]; - var subcellsDebug = new int[8]; - Range1i? qsRange = null; - for (var i = 0; i < 8; i++) - { - var octant = a.Cell.GetOctant(i); - var x = a.Subnodes![i]?.Value; - var y = b.Subnodes![i]?.Value; + private static IPointCloudNode MergeLeafAndTreeWithIdenticalRootCell(IPointCloudNode a, IPointCloudNode b, ImportConfig config) + { + if (a == null) throw new ArgumentNullException(nameof(a)); + if (b == null) throw new ArgumentNullException(nameof(b)); + if (a.IsLeaf == false || b.IsLeaf == true) throw new InvalidOperationException(); + if (a.Cell != b.Cell) throw new InvalidOperationException(); + if (a.HasPartIndexRange != b.HasPartIndexRange) throw new Exception("Invariant 7fe9ebc7-b51e-4a5a-8aa8-0e08b63b6240."); + + var result = InjectPointsIntoTree(a.PositionsAbsolute, a.Colors?.Value, a.Normals?.Value, a.Intensities?.Value, a.Classifications?.Value, a.PartIndices, b, a.Cell, config); + if (a.Cell != result.Cell) throw new InvalidOperationException("Invariant 55551919-1a11-4ea9-bb4e-6f1a6b15e3d5."); + if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant bf22e8ce-b517-4f42-90d8-947db603c244."); + return result; + } - if (a.Subnodes[i] != null && a.Subnodes[i]?.Value == null) throw new InvalidOperationException("Invariant 5571b3ac-a807-4318-9d07-d0843664b142."); - if (b.Subnodes[i] != null && b.Subnodes[i]?.Value == null) throw new InvalidOperationException("Invariant 5eecb345-3460-4f9a-948c-efa29dea26b9."); + private static IPointCloudNode MergeTreeAndTreeWithIdenticalRootCell(IPointCloudNode a, IPointCloudNode b, + Action pointsMergedCallback, + ImportConfig config + ) + { + if (a.IsLeaf || b.IsLeaf) throw new InvalidOperationException(); + if (a.Cell != b.Cell) throw new InvalidOperationException(); + if (a.PointCountCell > 0) throw new InvalidOperationException(); + if (b.PointCountCell > 0) throw new InvalidOperationException(); + if (a.HasPartIndexRange != b.HasPartIndexRange) throw new Exception("Invariant 0103f130-f022-42e5-8efc-da4e5747177a."); + + var pointCountTree = 0L; + var subcells = new IPointCloudNode?[8]; + var subcellsDebug = new int[8]; + Range1i? qsRange = null; + for (var i = 0; i < 8; i++) + { + var octant = a.Cell.GetOctant(i); + var x = a.Subnodes![i]?.Value; + var y = b.Subnodes![i]?.Value; - if (x != null) + if (a.Subnodes[i] != null && a.Subnodes[i]?.Value == null) throw new InvalidOperationException("Invariant 5571b3ac-a807-4318-9d07-d0843664b142."); + if (b.Subnodes[i] != null && b.Subnodes[i]?.Value == null) throw new InvalidOperationException("Invariant 5eecb345-3460-4f9a-948c-efa29dea26b9."); + + if (x != null) + { + if (y != null) { - if (y != null) - { - var m = Merge(x, y, pointsMergedCallback, config).Item1; - subcells[i] = m; - //if (x.PointCountTree + y.PointCountTree != subcells[i].PointCountTree) throw new InvalidOperationException("Invariant 82072553-7271-4448-b74d-735d44eb03b0."); - pointCountTree += m.PointCountTree; - qsRange = PartIndexUtils.MergeRanges(qsRange, m.PartIndexRange); - subcellsDebug[i] = 0; - } - else - { - subcells[i] = x; - pointCountTree += x.PointCountTree; - qsRange = PartIndexUtils.MergeRanges(qsRange, x.PartIndexRange); - //if (subcells[i].PointCountTree != x.PointCountTree) throw new InvalidOperationException(); - subcellsDebug[i] = 1; - } + var m = Merge(x, y, pointsMergedCallback, config).Item1; + subcells[i] = m; + //if (x.PointCountTree + y.PointCountTree != subcells[i].PointCountTree) throw new InvalidOperationException("Invariant 82072553-7271-4448-b74d-735d44eb03b0."); + pointCountTree += m.PointCountTree; + qsRange = PartIndexUtils.MergeRanges(qsRange, m.PartIndexRange); + subcellsDebug[i] = 0; } else { - if (y != null) - { - subcells[i] = y; - pointCountTree += y.PointCountTree; - qsRange = PartIndexUtils.MergeRanges(qsRange, y.PartIndexRange); + subcells[i] = x; + pointCountTree += x.PointCountTree; + qsRange = PartIndexUtils.MergeRanges(qsRange, x.PartIndexRange); + //if (subcells[i].PointCountTree != x.PointCountTree) throw new InvalidOperationException(); + subcellsDebug[i] = 1; + } + } + else + { + if (y != null) + { + subcells[i] = y; + pointCountTree += y.PointCountTree; + qsRange = PartIndexUtils.MergeRanges(qsRange, y.PartIndexRange); - //if (subcells[i].PointCountTree != y.PointCountTree) throw new InvalidOperationException(); - subcellsDebug[i] = 2; - } - else - { - subcells[i] = null; - subcellsDebug[i] = 3; - } + //if (subcells[i].PointCountTree != y.PointCountTree) throw new InvalidOperationException(); + subcellsDebug[i] = 2; + } + else + { + subcells[i] = null; + subcellsDebug[i] = 3; } } + } - var replacements = ImmutableDictionary.Empty - .Add(Durable.Octree.PointCountTreeLeafs, pointCountTree) - .Add(Durable.Octree.SubnodesGuids, subcells.Map(x => x?.Id ?? Guid.Empty)) - .Add(Durable.Octree.BoundingBoxExactGlobal, new Box3d(subcells.Where(n => n != null).Select(n => n!.BoundingBoxExactGlobal))) - ; + var replacements = ImmutableDictionary.Empty + .Add(Durable.Octree.PointCountTreeLeafs, pointCountTree) + .Add(Durable.Octree.SubnodesGuids, subcells.Map(x => x?.Id ?? Guid.Empty)) + .Add(Durable.Octree.BoundingBoxExactGlobal, new Box3d(subcells.Where(n => n != null).Select(n => n!.BoundingBoxExactGlobal))) + ; - if (qsRange != null) replacements = replacements.Add(Durable.Octree.PartIndexRange, qsRange); + if (qsRange != null) replacements = replacements.Add(Durable.Octree.PartIndexRange, qsRange); - var result = a.With(replacements).CollapseLeafNodes(config).Item1; - if (a.Cell != result.Cell) throw new InvalidOperationException("Invariant 97239777-8a0c-4158-853b-e9ebef63fda8."); - if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant 95228e78-b71f-4778-bf8a-926bd91e3560."); - return result; - } - - private static PointSetNode CreateTmpNode( - ImportConfig config, - Cell cell, - IList? positions, - IList? colors, - IList? normals, - IList? intensities, - IList? classifications, - object? partIndices, - Range1i? partIndexRange - ) - { - var chunk = new Chunk(positions, colors, normals, intensities, classifications, partIndices, partIndexRange, bbox: null); - if (config.NormalizePointDensityGlobal) chunk = chunk.ImmutableFilterMinDistByCell(cell, config.ParseConfig); - var node = InMemoryPointSet.Build(chunk, cell, config.OctreeSplitLimit).ToPointSetNode(config.Storage, isTemporaryImportNode: true); - if (node.Cell != cell) throw new InvalidOperationException("Invariant a9d952d5-5e01-4f59-9b6b-8a4e6a3d4cd9."); - if (partIndices != null && !node.HasPartIndexRange) throw new Exception("Invariant b9fccc91-cb8e-4efe-b4f1-25f39c90a8f7."); - return node; - } + var result = a.With(replacements).CollapseLeafNodes(config).Item1; + if (a.Cell != result.Cell) throw new InvalidOperationException("Invariant 97239777-8a0c-4158-853b-e9ebef63fda8."); + if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant 95228e78-b71f-4778-bf8a-926bd91e3560."); + return result; + } + + private static PointSetNode CreateTmpNode( + ImportConfig config, + Cell cell, + IList? positions, + IList? colors, + IList? normals, + IList? intensities, + IList? classifications, + object? partIndices, + Range1i? partIndexRange + ) + { + var chunk = new Chunk(positions, colors, normals, intensities, classifications, partIndices, partIndexRange, bbox: null); + if (config.NormalizePointDensityGlobal) chunk = chunk.ImmutableFilterMinDistByCell(cell, config.ParseConfig); + var node = InMemoryPointSet.Build(chunk, cell, config.OctreeSplitLimit).ToPointSetNode(config.Storage, isTemporaryImportNode: true); + if (node.Cell != cell) throw new InvalidOperationException("Invariant a9d952d5-5e01-4f59-9b6b-8a4e6a3d4cd9."); + if (partIndices != null && !node.HasPartIndexRange) throw new Exception("Invariant b9fccc91-cb8e-4efe-b4f1-25f39c90a8f7."); + return node; + } - private static IPointCloudNode InjectPointsIntoTree( - IList psAbsolute, IList? cs, IList? ns, IList? js, IList? ks, object? qs, - IPointCloudNode a, Cell cell, ImportConfig config - ) - { - if (a != null && !a.IsTemporaryImportNode) throw new InvalidOperationException( - "InjectPointsIntoTree is only valid for temporary import nodes. Invariant 0b0c48dc-8500-4ad6-a3dd-9c00f6d0b1d9." - ); + private static IPointCloudNode InjectPointsIntoTree( + IList psAbsolute, IList? cs, IList? ns, IList? js, IList? ks, object? qs, + IPointCloudNode a, Cell cell, ImportConfig config + ) + { + if (a != null && !a.IsTemporaryImportNode) throw new InvalidOperationException( + "InjectPointsIntoTree is only valid for temporary import nodes. Invariant 0b0c48dc-8500-4ad6-a3dd-9c00f6d0b1d9." + ); - if (a == null) - { - var result0 = CreateTmpNode(config, cell, psAbsolute, cs, ns, js, ks, qs, partIndexRange: null); - if (qs != null && !result0.HasPartIndices) throw new NotImplementedException("PARTINDICES"); - //DidMergeWorkObject(null, 0, qs, psAbsolute.Count, result0.PartIndices); - return result0; - } + if (a == null) + { + var result0 = CreateTmpNode(config, cell, psAbsolute, cs, ns, js, ks, qs, partIndexRange: null); + if (qs != null && !result0.HasPartIndices) throw new NotImplementedException("PARTINDICES"); + //DidMergeWorkObject(null, 0, qs, psAbsolute.Count, result0.PartIndices); + return result0; + } - if (a.Cell != cell) throw new InvalidOperationException("Invariant f447b6e5-52ef-4535-b8e4-e2aabedaef9e."); + if (a.Cell != cell) throw new InvalidOperationException("Invariant f447b6e5-52ef-4535-b8e4-e2aabedaef9e."); - if (a.IsLeaf) - { - if (cs != null && !a.HasColors) throw new InvalidOperationException("Invariant 64d98ee7-5b08-4de7-9086-a38e707eb354."); - if (cs == null && a.HasColors) throw new InvalidOperationException("Invariant 7c1cb6cb-16fe-40aa-83f4-61c0c2e50ec9."); - if (ns != null && !a.HasNormals) throw new InvalidOperationException("Invariant 12263f36-1d5d-4c2b-aa1e-9f96f80047f2."); - if (ns == null && a.HasNormals) throw new InvalidOperationException("Invariant 1e35a025-9a10-4bee-993b-109090c85b50."); - - var newPs = new List(psAbsolute); newPs.AddRange(a.PositionsAbsolute); - var newCs = cs != null ? new List(cs) : null; newCs?.AddRange(a.Colors!.Value); - var newNs = ns != null ? new List(ns) : null; newNs?.AddRange(a.Normals!.Value); - var newJs = js != null ? new List(js) : null; newJs?.AddRange(a.Intensities!.Value); - var newKs = ks != null ? new List(ks) : null; newKs?.AddRange(a.Classifications!.Value); - var newQs = PartIndexUtils.ConcatIndices(qs, psAbsolute.Count, a.PartIndices, a.PointCountCell); - - var result0 = CreateTmpNode(config, cell, newPs, newCs, newNs, newJs, newKs, newQs, partIndexRange: null); - if (a.HasPartIndexRange != result0.HasPartIndexRange) throw new Exception("Invariant c338fe24-7377-450c-af16-26be47861137."); - //doesnt check if result0 is innernode - //DidMergeWorkObject(a.PartIndices, a.PointCountCell, qs, psAbsolute.Count, result0.PartIndices); - return result0; - } + if (a.IsLeaf) + { + if (cs != null && !a.HasColors) throw new InvalidOperationException("Invariant 64d98ee7-5b08-4de7-9086-a38e707eb354."); + if (cs == null && a.HasColors) throw new InvalidOperationException("Invariant 7c1cb6cb-16fe-40aa-83f4-61c0c2e50ec9."); + if (ns != null && !a.HasNormals) throw new InvalidOperationException("Invariant 12263f36-1d5d-4c2b-aa1e-9f96f80047f2."); + if (ns == null && a.HasNormals) throw new InvalidOperationException("Invariant 1e35a025-9a10-4bee-993b-109090c85b50."); + + var newPs = new List(psAbsolute); newPs.AddRange(a.PositionsAbsolute); + var newCs = cs != null ? new List(cs) : null; newCs?.AddRange(a.Colors!.Value); + var newNs = ns != null ? new List(ns) : null; newNs?.AddRange(a.Normals!.Value); + var newJs = js != null ? new List(js) : null; newJs?.AddRange(a.Intensities!.Value); + var newKs = ks != null ? new List(ks) : null; newKs?.AddRange(a.Classifications!.Value); + var newQs = PartIndexUtils.ConcatIndices(qs, psAbsolute.Count, a.PartIndices, a.PointCountCell); + + var result0 = CreateTmpNode(config, cell, newPs, newCs, newNs, newJs, newKs, newQs, partIndexRange: null); + if (a.HasPartIndexRange != result0.HasPartIndexRange) throw new Exception("Invariant c338fe24-7377-450c-af16-26be47861137."); + //doesnt check if result0 is innernode + //DidMergeWorkObject(a.PartIndices, a.PointCountCell, qs, psAbsolute.Count, result0.PartIndices); + return result0; + } - var pss = new List[8]; - var css = cs != null ? new List[8] : null; - var nss = ns != null ? new List[8] : null; - var iss = js != null ? new List[8] : null; - var kss = ks != null ? new List[8] : null; - var qss = qs != null ? new List[8] : null; + var pss = new List[8]; + var css = cs != null ? new List[8] : null; + var nss = ns != null ? new List[8] : null; + var iss = js != null ? new List[8] : null; + var kss = ks != null ? new List[8] : null; + var qss = qs != null ? new List[8] : null; #if DEBUG - var bb = cell.BoundingBox; - if (!psAbsolute.All(bb.Contains)) Report.Warn( - $"Not all points contained in cell bounds {cell}. " + - $"Warning b2749dac-e8d4-4f95-a0ac-b97cad9c0b37." - ); + var bb = cell.BoundingBox; + if (!psAbsolute.All(bb.Contains)) Report.Warn( + $"Not all points contained in cell bounds {cell}. " + + $"Warning b2749dac-e8d4-4f95-a0ac-b97cad9c0b37." + ); #endif - for (var i = 0; i < psAbsolute.Count; i++) + for (var i = 0; i < psAbsolute.Count; i++) + { + var j = a.GetSubIndex(psAbsolute[i]); + if (pss[j] == null) { - var j = a.GetSubIndex(psAbsolute[i]); - if (pss[j] == null) - { - pss[j] = new List(); - if (cs != null) css![j] = new List(); - if (ns != null) nss![j] = new List(); - if (js != null) iss![j] = new List(); - if (ks != null) kss![j] = new List(); - if (qs != null) qss![j] = new List(); - } - pss[j].Add(psAbsolute[i]); - if (cs != null) css![j].Add(cs[i]); - if (ns != null) nss![j].Add(ns[i]); - if (js != null) iss![j].Add(js[i]); - if (ks != null) kss![j].Add(ks[i]); - if (qs != null) qss![j].Add(PartIndexUtils.Get(qs, i)!.Value); + pss[j] = []; + if (cs != null) css![j] = []; + if (ns != null) nss![j] = []; + if (js != null) iss![j] = []; + if (ks != null) kss![j] = []; + if (qs != null) qss![j] = []; } + pss[j].Add(psAbsolute[i]); + if (cs != null) css![j].Add(cs[i]); + if (ns != null) nss![j].Add(ns[i]); + if (js != null) iss![j].Add(js[i]); + if (ks != null) kss![j].Add(ks[i]); + if (qs != null) qss![j].Add(PartIndexUtils.Get(qs, i)!.Value); + } - if (pss.Sum(x => x?.Count) != psAbsolute.Count) throw new InvalidOperationException(); + if (pss.Sum(x => x?.Count) != psAbsolute.Count) throw new InvalidOperationException(); - var subcells = new IPointCloudNode?[8]; - for (var j = 0; j < 8; j++) + var subcells = new IPointCloudNode?[8]; + for (var j = 0; j < 8; j++) + { + var subCell = cell.GetOctant(j); + var x = a.Subnodes![j]?.Value; + var qsss = (qss != null && qss[j] != null) ? PartIndexUtils.Compact(qss![j].ToArray()) : null; + if (pss[j] != null) { - var subCell = cell.GetOctant(j); - var x = a.Subnodes![j]?.Value; - var qsss = (qss != null && qss[j] != null) ? PartIndexUtils.Compact(qss![j].ToArray()) : null; - if (pss[j] != null) + if (x == null) { - if (x == null) - { - // injecting points into non-existing subtree - subcells[j] = CreateTmpNode(config, subCell, pss[j], css?[j], nss?[j], iss?[j], kss?[j], qsss, partIndexRange: null); - } - else - { - subcells[j] = InjectPointsIntoTree(pss[j], css?[j], nss?[j], iss?[j], kss?[j], qsss, x, subCell, config); - } + // injecting points into non-existing subtree + subcells[j] = CreateTmpNode(config, subCell, pss[j], css?[j], nss?[j], iss?[j], kss?[j], qsss, partIndexRange: null); } else { - subcells[j] = x; + subcells[j] = InjectPointsIntoTree(pss[j], css?[j], nss?[j], iss?[j], kss?[j], qsss, x, subCell, config); } } - - var result = a.WithSubNodes(subcells).CollapseLeafNodes(config).Item1; - if (result.Cell != cell) throw new InvalidOperationException("Invariant 04aa0996-2942-41e5-bfdb-0c6841e2f12f."); - if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant 9a734621-00d6-4ea9-b915-20faf1eaaef5."); - return result; + else + { + subcells[j] = x; + } } + + var result = a.WithSubNodes(subcells).CollapseLeafNodes(config).Item1; + if (result.Cell != cell) throw new InvalidOperationException("Invariant 04aa0996-2942-41e5-bfdb-0c6841e2f12f."); + if (a.HasPartIndexRange != result.HasPartIndexRange) throw new Exception("Invariant 9a734621-00d6-4ea9-b915-20faf1eaaef5."); + return result; } } diff --git a/src/Aardvark.Geometry.PointSet/Octrees/MultiNodeIndex.cs b/src/Aardvark.Geometry.PointSet/Octrees/MultiNodeIndex.cs deleted file mode 100644 index e4a66b9a..00000000 --- a/src/Aardvark.Geometry.PointSet/Octrees/MultiNodeIndex.cs +++ /dev/null @@ -1,247 +0,0 @@ -//using Aardvark.Base; -//using Aardvark.Data; -//using Aardvark.Data.Points; -//using System; -//using System.Collections.Generic; -//using System.Collections.Immutable; -//using System.Linq; -//using System.Text; -//using System.Threading.Tasks; - -//namespace Aardvark.Geometry.Points -//{ -// public class MultiNodeIndex -// { -// /// -// /// Blob key of this MultiNodeIndex. -// /// -// public string Id { get; } - -// /// -// /// Key of tree root node. -// /// -// public string RootNodeId => Id; - -// /// -// /// Blob key containing tree nodes this index applies to. -// /// -// public string TreeBlobId { get; } - -// private readonly Dictionary _index; - -// public (long offset, int size) GetOffsetAndSize(string key) => _index[key]; -// public bool TryGetOffsetAndSize(string key, out (long offset, int size) result) => _index.TryGetValue(key, out result); - -// public PossiblyGZippedBuffer GetNodeBlobGzipped(string key, Func getBlobPartial) -// { -// var (offset, size) = _index[key]; -// var buffer = getBlobPartial(TreeBlobId, offset, size); -// return buffer; -// } - -// public IPointCloudNode GetNode(string key, Func getBlob, Func getBlobPartial) -// { -// //var buffer = GetNodeBlobGzipped(key, getBlobPartial); -// //var result = QuantizedOctreeNode.Decode(buffer, getBlob, getBlobPartial); -// //return result; -// throw new Exception("Not supported. Error 833810da-0996-4df0-bd55-2e66553049e7."); -// } - -// private MultiNodeIndex(string id, string treeBlobId, Dictionary index) -// { -// Id = id; -// TreeBlobId = treeBlobId; -// _index = index; -// } - -// public byte[] Encode() -// { -// var keys = _index.Select(kv => kv.Key).ToArray(); -// var offsets = _index.Select(kv => kv.Value.offset).ToArray(); -// var sizes = _index.Select(kv => kv.Value.size).ToArray(); -// var map = ImmutableDictionary.Empty -// .Add("Id", (Durable.Primitives.StringUTF8, Id)) -// .Add("NodeDataId", (Durable.Primitives.StringUTF8, TreeBlobId)) -// .Add("Keys", (Durable.Primitives.StringUTF8Array, keys)) -// .Add("Offsets", (Durable.Primitives.Int32Array, offsets)) -// .Add("Sizes", (Durable.Primitives.Int32Array, sizes)) -// ; - -// var buffer = DurableCodec.Serialize(Durable.Octree.MultiNodeIndex, map); -// var bufferGz = buffer.Gzip(); -// return bufferGz; -// } - -// public static MultiNodeIndex Decode(object obj) -// { -// var map = (IDictionary)obj; - -// var id = (string)map["Id"].value; -// var nodeDataId = (string)map["NodeDataId"].value; -// var keys = (string[])map["Keys"].value; -// var offsets = (int[])map["Offsets"].value; -// var sizes = (int[])map["Sizes"].value; - -// var index = new Dictionary(); -// for (var i = 0; i < keys.Length; i++) -// index[keys[i]] = (offsets[i], sizes[i]); - -// return new MultiNodeIndex(id, nodeDataId, index); -// } - -// public static MultiNodeIndex Decode(byte[] buffer) -// { -// var (_, obj) = DurableCodec.Deserialize(buffer); -// var map = (IDictionary)obj; -// return Decode(map); -// } -// } - -// public class MultiNode : IPointCloudNode -// { -// public MultiNodeIndex Index { get; } -// public PointSetNode Node { get; } -// public PersistentRef[] _subnodes; - -// private (bool ok, IPointCloudNode node) TryLoadNode(string id) -// { -// if (Index.TryGetOffsetAndSize(id, out var result)) -// { -// try -// { -// var bufferRootNode = StorageExtensions.UnGZip(Storage.f_getSlice(Index.TreeBlobId, result.offset, result.size)); -// var node = PointSetNode.Decode(Storage, bufferRootNode); -// var indexnode = new MultiNode(Index, node); -// return (true, indexnode); -// } -// catch (Exception e) -// { -// Report.Warn($"{e}"); -// return (false, null); -// } -// } -// else -// { -// return (false, null); -// } -// } - -// private IPointCloudNode LoadNode(string id) => TryLoadNode(id).node; - -// public MultiNode(MultiNodeIndex index, PointSetNode node) -// { -// Index = index; -// Node = node; -// _subnodes = node.Subnodes?.Map(x => x != null ? new PersistentRef(x.Id, LoadNode, TryLoadNode) : null); -// } - -// public PersistentRef[] Subnodes => _subnodes; - -// public Storage Storage => Node.Storage; -// public bool IsMaterialized => Node.IsMaterialized; -// public Guid Id => Node.Id; -// public bool IsTemporaryImportNode => Node.IsTemporaryImportNode; -// public Cell Cell => Node.Cell; -// public V3d Center => Node.Center; -// public int PointCountCell => Node.PointCountCell; -// public long PointCountTree => Node.PointCountTree; -// public bool IsLeaf => Node.IsLeaf; -// public IReadOnlyDictionary Properties => Node.Properties; -// public bool HasPositions => Node.HasPositions; -// public PersistentRef Positions => Node.Positions; -// public V3d[] PositionsAbsolute => Node.PositionsAbsolute; -// public bool HasBoundingBoxExactLocal => Node.HasBoundingBoxExactLocal; -// public Box3f BoundingBoxExactLocal => Node.BoundingBoxExactLocal; -// public bool HasBoundingBoxExactGlobal => Node.HasBoundingBoxExactGlobal; -// public Box3d BoundingBoxExactGlobal => Node.BoundingBoxExactGlobal; -// public Box3d BoundingBoxApproximate => Node.BoundingBoxApproximate; -// public bool HasKdTree => Node.HasKdTree; -// public PersistentRef> KdTree => Node.KdTree; -// public bool HasColors => Node.HasColors; -// public PersistentRef Colors => Node.Colors; -// public bool HasNormals => Node.HasNormals; -// public PersistentRef Normals => Node.Normals; -// public bool HasIntensities => Node.HasIntensities; -// public PersistentRef Intensities => Node.Intensities; -// public bool HasClassifications => Node.HasClassifications; -// public PersistentRef Classifications => Node.Classifications; - -// #region Velocities - -// /// -// /// Deprecated. Always returns false. Use custom attributes instead. -// /// -// [Obsolete("Use custom attributes instead.")] -// public bool HasVelocities => Node.HasVelocities; - -// /// -// /// Deprecated. Always returns null. Use custom attributes instead. -// /// -// [Obsolete("Use custom attributes instead.")] -// public PersistentRef Velocities => Node.Velocities; - -// #endregion - -// public bool HasCentroidLocal => Node.HasCentroidLocal; -// public V3f CentroidLocal => Node.CentroidLocal; -// public bool HasCentroidLocalStdDev => Node.HasCentroidLocalStdDev; -// public float CentroidLocalStdDev => Node.CentroidLocalStdDev; -// public bool HasMinTreeDepth => Node.HasMinTreeDepth; -// public int MinTreeDepth => Node.MinTreeDepth; -// public bool HasMaxTreeDepth => Node.HasMaxTreeDepth; -// public int MaxTreeDepth => Node.MaxTreeDepth; -// public bool HasPointDistanceAverage => Node.HasPointDistanceAverage; -// public float PointDistanceAverage => Node.PointDistanceAverage; -// public bool HasPointDistanceStandardDeviation => Node.HasPointDistanceStandardDeviation; -// public float PointDistanceStandardDeviation => Node.PointDistanceStandardDeviation; -// public bool Has(Durable.Def what) => Node.Has(what); -// public IPointCloudNode Materialize() => Node.Materialize(); -// public bool TryGetValue(Durable.Def what, out object o) => Node.TryGetValue(what, out o); - -// #region Not supported ... - -// /// -// /// MultiNode does not support Encode. -// /// -// public byte[] Encode() => throw new Exception("Not supported. Error 0cc343db-219a-4c3f-a066-4a2f8950ab71."); - -// /// -// /// MultiNode does not support WithSubNodes. -// /// -// public IPointCloudNode WithSubNodes(IPointCloudNode[] subnodes) => throw new Exception("Not supported. Error 40776584-f6f0-4ddf-91d2-765172d61fc4."); - -// /// -// /// MultiNode does not support WriteToStore. -// /// -// public IPointCloudNode WriteToStore() => throw new Exception("Not supported. Error 822fac0a-538c-4613-94fd-9189d48a42c8."); - -// /// -// /// MultiNode does not support With. -// /// -// public IPointCloudNode With(IReadOnlyDictionary replacements) => throw new Exception("Not supported. Error 314d6802-9e65-4ad7-9d09-90d2e4c28f49."); - -// #endregion -// } - -// public class PossiblyGZippedBuffer -// { -// private byte[] _buffer; -// private bool _isGZipped; - -// public PossiblyGZippedBuffer(byte[] buffer, bool isGZipped) -// { -// _buffer = buffer; -// _isGZipped = isGZipped; -// } - -// public byte[] GetUncompressed() -// { -// if (_isGZipped) -// { -// _buffer = StorageExtensions.UnGZip(_buffer); -// _isGZipped = false; -// } -// return _buffer; -// } -// } -//} diff --git a/src/Aardvark.Geometry.PointSet/Octrees/ObsoleteNodeParser.cs b/src/Aardvark.Geometry.PointSet/Octrees/ObsoleteNodeParser.cs index b51709ab..dbdf04bd 100644 --- a/src/Aardvark.Geometry.PointSet/Octrees/ObsoleteNodeParser.cs +++ b/src/Aardvark.Geometry.PointSet/Octrees/ObsoleteNodeParser.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -18,201 +18,200 @@ You should have received a copy of the GNU Affero General Public License using System; using System.Collections.Immutable; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// Backwards compatibility. +/// +public static class ObsoleteNodeParser { /// - /// Backwards compatibility. + /// Octree. Obsolete per-point positions. Raw V3f[]. + /// + public static readonly Durable.Def ObsoletePositions = new( + new Guid("7cb3be14-062e-47d0-9941-fe135a82c632"), + "Octree.ObsoletePositions", + "Octree. Obsolete per-point positions. Raw V3f[].", + Guid.Empty, + false + ); + + public static PointSetNode Parse(Storage storage, byte[] buffer) + { + return ParseBinary(buffer, storage); + } + + /// /// - public static class ObsoleteNodeParser + [Flags] + private enum PointSetAttributes : uint { /// - /// Octree. Obsolete per-point positions. Raw V3f[]. + /// V3f[] relative to Center. /// - public static readonly Durable.Def ObsoletePositions = new( - new Guid("7cb3be14-062e-47d0-9941-fe135a82c632"), - "Octree.ObsoletePositions", - "Octree. Obsolete per-point positions. Raw V3f[].", - Guid.Empty, - false - ); + Positions = 1 << 0, - public static PointSetNode Parse(Storage storage, byte[] buffer) - { - return ParseBinary(buffer, storage); - } + /// + /// C4b[]. + /// + Colors = 1 << 1, + + /// + /// V3f[]. + /// + Normals = 1 << 2, + + /// + /// int[]. + /// + Intensities = 1 << 3, + + /// + /// PointRkdTreeD<V3f[], V3f>. + /// + KdTree = 1 << 4, + + /// + /// V3f[] relative to Center. + /// + [Obsolete] + LodPositions = 1 << 5, + + /// + /// C4b[]. + /// + [Obsolete] + LodColors = 1 << 6, + + /// + /// V3f[]. + /// + [Obsolete] + LodNormals = 1 << 7, /// + /// int[]. /// - [Flags] - private enum PointSetAttributes : uint + [Obsolete] + LodIntensities = 1 << 8, + + /// + /// PointRkdTreeD<V3f[], V3f>. + /// + [Obsolete] + LodKdTree = 1 << 9, + + /// + /// byte[]. + /// + Classifications = 1 << 10, + + /// + /// byte[]. + /// + [Obsolete] + LodClassifications = 1 << 11, + + /// + /// Cell attributes. + /// + HasCellAttributes = 1 << 23, + } + + private static PointSetNode ParseBinary(byte[] buffer, Storage storage) + { + var masks = BitConverter.ToUInt32(buffer, 0); + var subcellmask = masks >> 24; + var attributemask = masks & 0b00000000_11111111_11111111_11111111; + + var offset = 4; + var id = ParseGuid(buffer, ref offset); + + var cell = new Cell( + BitConverter.ToInt64(buffer, 20), + BitConverter.ToInt64(buffer, 28), + BitConverter.ToInt64(buffer, 36), + BitConverter.ToInt32(buffer, 44) + ); + + var pointCountTree = BitConverter.ToInt64(buffer, 48); + + offset = 56; + + Guid[]? subcellIds = null; + if (subcellmask != 0) { - /// - /// V3f[] relative to Center. - /// - Positions = 1 << 0, - - /// - /// C4b[]. - /// - Colors = 1 << 1, - - /// - /// V3f[]. - /// - Normals = 1 << 2, - - /// - /// int[]. - /// - Intensities = 1 << 3, - - /// - /// PointRkdTreeD<V3f[], V3f>. - /// - KdTree = 1 << 4, - - /// - /// V3f[] relative to Center. - /// - [Obsolete] - LodPositions = 1 << 5, - - /// - /// C4b[]. - /// - [Obsolete] - LodColors = 1 << 6, - - /// - /// V3f[]. - /// - [Obsolete] - LodNormals = 1 << 7, - - /// - /// int[]. - /// - [Obsolete] - LodIntensities = 1 << 8, - - /// - /// PointRkdTreeD<V3f[], V3f>. - /// - [Obsolete] - LodKdTree = 1 << 9, - - /// - /// byte[]. - /// - Classifications = 1 << 10, - - /// - /// byte[]. - /// - [Obsolete] - LodClassifications = 1 << 11, - - /// - /// Cell attributes. - /// - HasCellAttributes = 1 << 23, + subcellIds = new Guid[8]; + if ((subcellmask & 0x01) != 0) subcellIds[0] = ParseGuid(buffer, ref offset); + if ((subcellmask & 0x02) != 0) subcellIds[1] = ParseGuid(buffer, ref offset); + if ((subcellmask & 0x04) != 0) subcellIds[2] = ParseGuid(buffer, ref offset); + if ((subcellmask & 0x08) != 0) subcellIds[3] = ParseGuid(buffer, ref offset); + if ((subcellmask & 0x10) != 0) subcellIds[4] = ParseGuid(buffer, ref offset); + if ((subcellmask & 0x20) != 0) subcellIds[5] = ParseGuid(buffer, ref offset); + if ((subcellmask & 0x40) != 0) subcellIds[6] = ParseGuid(buffer, ref offset); + if ((subcellmask & 0x80) != 0) subcellIds[7] = ParseGuid(buffer, ref offset); } - private static PointSetNode ParseBinary(byte[] buffer, Storage storage) - { - var masks = BitConverter.ToUInt32(buffer, 0); - var subcellmask = masks >> 24; - var attributemask = masks & 0b00000000_11111111_11111111_11111111; - - var offset = 4; - var id = ParseGuid(buffer, ref offset); - - var cell = new Cell( - BitConverter.ToInt64(buffer, 20), - BitConverter.ToInt64(buffer, 28), - BitConverter.ToInt64(buffer, 36), - BitConverter.ToInt32(buffer, 44) - ); - - var pointCountTree = BitConverter.ToInt64(buffer, 48); - - offset = 56; - - Guid[]? subcellIds = null; - if (subcellmask != 0) - { - subcellIds = new Guid[8]; - if ((subcellmask & 0x01) != 0) subcellIds[0] = ParseGuid(buffer, ref offset); - if ((subcellmask & 0x02) != 0) subcellIds[1] = ParseGuid(buffer, ref offset); - if ((subcellmask & 0x04) != 0) subcellIds[2] = ParseGuid(buffer, ref offset); - if ((subcellmask & 0x08) != 0) subcellIds[3] = ParseGuid(buffer, ref offset); - if ((subcellmask & 0x10) != 0) subcellIds[4] = ParseGuid(buffer, ref offset); - if ((subcellmask & 0x20) != 0) subcellIds[5] = ParseGuid(buffer, ref offset); - if ((subcellmask & 0x40) != 0) subcellIds[6] = ParseGuid(buffer, ref offset); - if ((subcellmask & 0x80) != 0) subcellIds[7] = ParseGuid(buffer, ref offset); - } - - var psId = (attributemask & (uint)PointSetAttributes.Positions) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; - var csId = (attributemask & (uint)PointSetAttributes.Colors) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; - var nsId = (attributemask & (uint)PointSetAttributes.Normals) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; - var isId = (attributemask & (uint)PointSetAttributes.Intensities) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; - var kdId = (attributemask & (uint)PointSetAttributes.KdTree) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; + var psId = (attributemask & (uint)PointSetAttributes.Positions) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; + var csId = (attributemask & (uint)PointSetAttributes.Colors) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; + var nsId = (attributemask & (uint)PointSetAttributes.Normals) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; + var isId = (attributemask & (uint)PointSetAttributes.Intensities) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; + var kdId = (attributemask & (uint)PointSetAttributes.KdTree) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; #pragma warning disable CS0612 // Type or member is obsolete - var lodPsId = (attributemask & (uint)PointSetAttributes.LodPositions) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; - var lodCsId = (attributemask & (uint)PointSetAttributes.LodColors) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; - var lodNsId = (attributemask & (uint)PointSetAttributes.LodNormals) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; - var lodIsId = (attributemask & (uint)PointSetAttributes.LodIntensities) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; - var lodKdId = (attributemask & (uint)PointSetAttributes.LodKdTree) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; - var ksId = (attributemask & (uint)PointSetAttributes.Classifications) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; - var lodKsId = (attributemask & (uint)PointSetAttributes.LodClassifications) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; + var lodPsId = (attributemask & (uint)PointSetAttributes.LodPositions) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; + var lodCsId = (attributemask & (uint)PointSetAttributes.LodColors) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; + var lodNsId = (attributemask & (uint)PointSetAttributes.LodNormals) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; + var lodIsId = (attributemask & (uint)PointSetAttributes.LodIntensities) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; + var lodKdId = (attributemask & (uint)PointSetAttributes.LodKdTree) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; + var ksId = (attributemask & (uint)PointSetAttributes.Classifications) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; + var lodKsId = (attributemask & (uint)PointSetAttributes.LodClassifications) != 0 ? ParseGuid(buffer, ref offset) : (Guid?)null; #pragma warning restore CS0612 // Type or member is obsolete - #region backwards compatibility with obsolete lod entries - - if (lodPsId.HasValue) psId = lodPsId; - if (lodCsId.HasValue) csId = lodCsId; - if (lodNsId.HasValue) nsId = lodNsId; - if (lodIsId.HasValue) isId = lodIsId; - if (lodKdId.HasValue) kdId = lodKdId; - if (lodKsId.HasValue) ksId = lodKsId; + #region backwards compatibility with obsolete lod entries - #endregion + if (lodPsId.HasValue) psId = lodPsId; + if (lodCsId.HasValue) csId = lodCsId; + if (lodNsId.HasValue) nsId = lodNsId; + if (lodIsId.HasValue) isId = lodIsId; + if (lodKdId.HasValue) kdId = lodKdId; + if (lodKsId.HasValue) ksId = lodKsId; - var data = ImmutableDictionary.Empty - .Add(Durable.Octree.NodeId, id) - .Add(Durable.Octree.Cell, cell) - .Add(Durable.Octree.PointCountTreeLeafs, pointCountTree) - ; + #endregion - if (psId.HasValue && !storage.Exists(psId.Value)) throw new Exception("Invalid format. Invariant 29215445-2a5e-42bb-a679-970ec8b7479a."); + var data = ImmutableDictionary.Empty + .Add(Durable.Octree.NodeId, id) + .Add(Durable.Octree.Cell, cell) + .Add(Durable.Octree.PointCountTreeLeafs, pointCountTree) + ; - if (psId.HasValue) data = data.Add(Durable.Octree.PositionsLocal3fReference, psId.Value); - if (csId.HasValue) data = data.Add(Durable.Octree.Colors4bReference, csId.Value); - if (kdId.HasValue) data = data.Add(Durable.Octree.PointRkdTreeDDataReference, kdId.Value); - if (nsId.HasValue) data = data.Add(Durable.Octree.Normals3fReference, nsId.Value); - if (isId.HasValue) data = data.Add(Durable.Octree.Intensities1iReference, isId.Value); - if (ksId.HasValue) data = data.Add(Durable.Octree.Classifications1bReference, ksId.Value); + if (psId.HasValue && !storage.Exists(psId.Value)) throw new Exception("Invalid format. Invariant 29215445-2a5e-42bb-a679-970ec8b7479a."); - if (subcellIds != null) - { - data = data.Add(Durable.Octree.SubnodesGuids, subcellIds); - } + if (psId.HasValue) data = data.Add(Durable.Octree.PositionsLocal3fReference, psId.Value); + if (csId.HasValue) data = data.Add(Durable.Octree.Colors4bReference, csId.Value); + if (kdId.HasValue) data = data.Add(Durable.Octree.PointRkdTreeDDataReference, kdId.Value); + if (nsId.HasValue) data = data.Add(Durable.Octree.Normals3fReference, nsId.Value); + if (isId.HasValue) data = data.Add(Durable.Octree.Intensities1iReference, isId.Value); + if (ksId.HasValue) data = data.Add(Durable.Octree.Classifications1bReference, ksId.Value); - var result = new PointSetNode(data, storage, false); - return result; - } - - private static Guid ParseGuid(byte[] buffer, ref int offset) + if (subcellIds != null) { - var guid = new Guid( - BitConverter.ToUInt32(buffer, offset), - BitConverter.ToUInt16(buffer, offset + 4), - BitConverter.ToUInt16(buffer, offset + 6), - buffer[offset + 8], buffer[offset + 9], buffer[offset + 10], buffer[offset + 11], - buffer[offset + 12], buffer[offset + 13], buffer[offset + 14], buffer[offset + 15] - ); - offset += 16; - return guid; + data = data.Add(Durable.Octree.SubnodesGuids, subcellIds); } + + var result = new PointSetNode(data, storage, false); + return result; + } + + private static Guid ParseGuid(byte[] buffer, ref int offset) + { + var guid = new Guid( + BitConverter.ToUInt32(buffer, offset), + BitConverter.ToUInt16(buffer, offset + 4), + BitConverter.ToUInt16(buffer, offset + 6), + buffer[offset + 8], buffer[offset + 9], buffer[offset + 10], buffer[offset + 11], + buffer[offset + 12], buffer[offset + 13], buffer[offset + 14], buffer[offset + 15] + ); + offset += 16; + return guid; } } diff --git a/src/Aardvark.Geometry.PointSet/Octrees/PointSet.cs b/src/Aardvark.Geometry.PointSet/Octrees/PointSet.cs index d32ec5a1..3d5ca9af 100644 --- a/src/Aardvark.Geometry.PointSet/Octrees/PointSet.cs +++ b/src/Aardvark.Geometry.PointSet/Octrees/PointSet.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -21,244 +21,243 @@ You should have received a copy of the GNU Affero General Public License using System.Text.Json.Serialization; using System.Threading; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// An immutable set of points. +/// +public class PointSet { /// - /// An immutable set of points. + /// The empty pointset. /// - public class PointSet - { - /// - /// The empty pointset. - /// - public static readonly PointSet Empty = new(Storage.None, "PointSet.Empty"); - - #region Construction - - /// - /// Creates PointSet from given points and colors. - /// - public static PointSet Create(Storage storage, string pointSetId, - IList positions, IList colors, IList normals, IList intensities, IList classifications, object? partIndices, - int octreeSplitLimit, bool generateLod, bool isTemporaryImportNode, CancellationToken ct = default - ) - { - if (storage == null) throw new ArgumentNullException(nameof(storage)); - if (pointSetId == null) throw new ArgumentNullException(nameof(pointSetId)); - var bounds = new Box3d(positions); - var builder = InMemoryPointSet.Build(positions, colors, normals, intensities, classifications, partIndices, new Cell(bounds), octreeSplitLimit); - var root = builder.ToPointSetNode(storage, isTemporaryImportNode); + public static readonly PointSet Empty = new(Storage.None, "PointSet.Empty"); - var result = new PointSet(storage, pointSetId: pointSetId, rootCellId: root.Id, octreeSplitLimit); + #region Construction - if (result.Root.Value == null) throw new InvalidOperationException("Invariant 5492d57b-add1-48bf-9721-5087c957d81e."); + /// + /// Creates PointSet from given points and colors. + /// + public static PointSet Create(Storage storage, string pointSetId, + IList positions, IList colors, IList normals, IList intensities, IList classifications, object? partIndices, + int octreeSplitLimit, bool generateLod, bool isTemporaryImportNode, CancellationToken ct = default + ) + { + if (storage == null) throw new ArgumentNullException(nameof(storage)); + if (pointSetId == null) throw new ArgumentNullException(nameof(pointSetId)); + var bounds = new Box3d(positions); + var builder = InMemoryPointSet.Build(positions, colors, normals, intensities, classifications, partIndices, new Cell(bounds), octreeSplitLimit); + var root = builder.ToPointSetNode(storage, isTemporaryImportNode); - var config = ImportConfig.Default - .WithRandomKey() - .WithCancellationToken(ct) - ; + var result = new PointSet(storage, pointSetId: pointSetId, rootCellId: root.Id, octreeSplitLimit); - if (generateLod) - { - result = result.GenerateLod(config); - } + if (result.Root.Value == null) throw new InvalidOperationException("Invariant 5492d57b-add1-48bf-9721-5087c957d81e."); - return result; - } + var config = ImportConfig.Default + .WithRandomKey() + .WithCancellationToken(ct) + ; - /// - /// Creates pointset from given root cell. - /// - public PointSet(Storage storage, string pointSetId, Guid rootCellId, int splitLimit) + if (generateLod) { - Storage = storage ?? throw new ArgumentNullException(nameof(storage)); - Id = pointSetId ?? throw new ArgumentNullException(nameof(pointSetId)); - SplitLimit = splitLimit; - - Root = rootCellId != Guid.Empty - ? new PersistentRef(rootCellId.ToString(), storage.GetPointCloudNode, storage.TryGetPointCloudNode) - : new PersistentRef(Guid.Empty, PointSetNode.Empty) - ; + result = result.GenerateLod(config); } - /// - /// Creates pointset from given root cell. - /// - public PointSet(Storage storage, string key, IPointCloudNode root, int splitLimit) - { - if (root == null) throw new ArgumentNullException(nameof(root)); + return result; + } - Storage = storage ?? throw new ArgumentNullException(nameof(storage)); - Id = key ?? throw new ArgumentNullException(nameof(key)); - SplitLimit = splitLimit; + /// + /// Creates pointset from given root cell. + /// + public PointSet(Storage storage, string pointSetId, Guid rootCellId, int splitLimit) + { + Storage = storage ?? throw new ArgumentNullException(nameof(storage)); + Id = pointSetId ?? throw new ArgumentNullException(nameof(pointSetId)); + SplitLimit = splitLimit; + + Root = rootCellId != Guid.Empty + ? new PersistentRef(rootCellId.ToString(), storage.GetPointCloudNode, storage.TryGetPointCloudNode) + : new PersistentRef(Guid.Empty, PointSetNode.Empty) + ; + } - Root = root.IsEmpty - ? new PersistentRef(Guid.Empty, PointSetNode.Empty) - : new PersistentRef(root.Id.ToString(), storage.GetPointCloudNode, storage.TryGetPointCloudNode) - ; - } + /// + /// Creates pointset from given root cell. + /// + public PointSet(Storage storage, string key, IPointCloudNode root, int splitLimit) + { + if (root == null) throw new ArgumentNullException(nameof(root)); - /// - /// Creates empty pointset. - /// - public PointSet(Storage storage, string key) - { - Storage = storage ?? throw new ArgumentNullException(nameof(storage)); - Id = key ?? throw new ArgumentNullException(nameof(key)); - SplitLimit = 0; + Storage = storage ?? throw new ArgumentNullException(nameof(storage)); + Id = key ?? throw new ArgumentNullException(nameof(key)); + SplitLimit = splitLimit; - Root = new(id: Guid.Empty, PointSetNode.Empty); - } + Root = root.IsEmpty + ? new PersistentRef(Guid.Empty, PointSetNode.Empty) + : new PersistentRef(root.Id.ToString(), storage.GetPointCloudNode, storage.TryGetPointCloudNode) + ; + } + + /// + /// Creates empty pointset. + /// + public PointSet(Storage storage, string key) + { + Storage = storage ?? throw new ArgumentNullException(nameof(storage)); + Id = key ?? throw new ArgumentNullException(nameof(key)); + SplitLimit = 0; + + Root = new(id: Guid.Empty, PointSetNode.Empty); + } - #endregion + #endregion - #region Properties (state to serialize) + #region Properties (state to serialize) - /// - /// - public string Id { get; init; } + /// + /// + public string Id { get; init; } - /// - /// - public int SplitLimit { get; init; } - - /// - /// - public PersistentRef Root { get; init; } + /// + /// + public int SplitLimit { get; init; } + + /// + /// + public PersistentRef Root { get; init; } - #endregion + #endregion - #region Json + #region Json - /// - /// - public JsonNode ToJson() => JsonSerializer.SerializeToNode(new - { - Id, - OctreeId = Root.Id, - RootCellId = Root.Id, // backwards compatibility - SplitLimit - })!; - - /// - /// - public static PointSet Parse(JsonNode json, Storage storage) - { - var o = json.AsObject() ?? throw new Exception($"Expected JSON object, but found {json}."); + /// + /// + public JsonNode ToJson() => JsonSerializer.SerializeToNode(new + { + Id, + OctreeId = Root.Id, + RootCellId = Root.Id, // backwards compatibility + SplitLimit + })!; - // id - var id = (string?)o["Id"] ?? throw new Exception("Missing id. Error 71730558-699e-4128-b0f2-130fd04672e9."); + /// + /// + public static PointSet Parse(JsonNode json, Storage storage) + { + var o = json.AsObject() ?? throw new Exception($"Expected JSON object, but found {json}."); + // id + var id = (string?)o["Id"] ?? throw new Exception("Missing id. Error 71730558-699e-4128-b0f2-130fd04672e9."); - var octreeId = (string?)o["OctreeId"] ?? (string?)o["RootCellId"]; - if (octreeId == "" || octreeId == Guid.Empty.ToString()) octreeId = null; - var octreeRef = octreeId != null - ? new PersistentRef(octreeId, storage.GetPointCloudNode, storage.TryGetPointCloudNode) - : null - ; - var octree = octreeRef?.Value; + var octreeId = (string?)o["OctreeId"] ?? (string?)o["RootCellId"]; + if (octreeId == "" || octreeId == Guid.Empty.ToString()) octreeId = null; - // backwards compatibility: if split limit is not set, guess as number of points in root cell - var splitLimit = o.TryGetPropertyValue("SplitLimit", out var x) ? (int)x! : 8192; + var octreeRef = octreeId != null + ? new PersistentRef(octreeId, storage.GetPointCloudNode, storage.TryGetPointCloudNode) + : null + ; + var octree = octreeRef?.Value; - return new PointSet(storage, id, octree ?? PointSetNode.Empty, splitLimit); - } + // backwards compatibility: if split limit is not set, guess as number of points in root cell + var splitLimit = o.TryGetPropertyValue("SplitLimit", out var x) ? (int)x! : 8192; + + return new PointSet(storage, id, octree ?? PointSetNode.Empty, splitLimit); + } - #endregion + #endregion - #region Properties (derived, non-serialized) + #region Properties (derived, non-serialized) - /// - /// - [JsonIgnore] - public Storage Storage { get; init; } + /// + /// + [JsonIgnore] + public Storage Storage { get; init; } - /// - /// Returns true if pointset is empty. - /// - public bool IsEmpty => Root.Id == "" || Root.Id == Guid.Empty.ToString(); + /// + /// Returns true if pointset is empty. + /// + public bool IsEmpty => Root.Id == "" || Root.Id == Guid.Empty.ToString(); - /// - /// Gets total number of points in dataset. - /// - public long PointCount => IsEmpty ? 0 : (Root?.Value?.PointCountTree ?? 0); + /// + /// Gets total number of points in dataset. + /// + public long PointCount => IsEmpty ? 0 : (Root?.Value?.PointCountTree ?? 0); - /// - /// Gets bounds of dataset root cell. - /// - public Box3d Bounds => Root.Value!.Cell.IsValid ? Root.Value.Cell.BoundingBox : Box3d.Invalid; + /// + /// Gets bounds of dataset root cell. + /// + public Box3d Bounds => Root.Value!.Cell.IsValid ? Root.Value.Cell.BoundingBox : Box3d.Invalid; - /// - /// Gets exact bounding box of all points in pointcloud. - /// - public Box3d BoundingBox + /// + /// Gets exact bounding box of all points in pointcloud. + /// + public Box3d BoundingBox + { + get { - get + try { - try - { - return Root.Value!.BoundingBoxExactGlobal; - } - catch (NullReferenceException) - { - return Box3d.Invalid; - } + return Root.Value!.BoundingBoxExactGlobal; + } + catch (NullReferenceException) + { + return Box3d.Invalid; } } + } - /// - public bool HasColors => Root != null && Root.Value!.HasColors; + /// + public bool HasColors => Root != null && Root.Value!.HasColors; - /// - public bool HasIntensities => Root != null && Root.Value!.HasIntensities; - - /// - public bool HasClassifications => Root != null && Root.Value!.HasClassifications; + /// + public bool HasIntensities => Root != null && Root.Value!.HasIntensities; + + /// + public bool HasClassifications => Root != null && Root.Value!.HasClassifications; - /// - public bool HasKdTree => Root != null && Root.Value!.HasKdTree; - - /// - public bool HasNormals => Root != null && Root.Value!.HasNormals; + /// + public bool HasKdTree => Root != null && Root.Value!.HasKdTree; + + /// + public bool HasNormals => Root != null && Root.Value!.HasNormals; - /// - public bool HasPositions => Root != null && Root.Value!.HasPositions; + /// + public bool HasPositions => Root != null && Root.Value!.HasPositions; - public bool HasPartIndices => Root != null && Root.Value.HasPartIndices; + public bool HasPartIndices => Root != null && Root.Value.HasPartIndices; - [MemberNotNullWhen(true, nameof(PartIndexRange))] - public bool HasPartIndexRange => Root != null && Root.Value.HasPartIndexRange; + [MemberNotNullWhen(true, nameof(PartIndexRange))] + public bool HasPartIndexRange => Root != null && Root.Value.HasPartIndexRange; - public Range1i? PartIndexRange => Root!.Value.PartIndexRange; + public Range1i? PartIndexRange => Root!.Value.PartIndexRange; - #endregion + #endregion - #region Immutable operations + #region Immutable operations - /// - /// - public PointSet Merge(PointSet other, Action pointsMergedCallback, ImportConfig config) - { - if (this.HasPartIndices && !this.HasPartIndexRange) throw new NotImplementedException("PARTINDICES"); - if (other.HasPartIndices && !other.HasPartIndexRange) throw new NotImplementedException("PARTINDICES"); + /// + /// + public PointSet Merge(PointSet other, Action pointsMergedCallback, ImportConfig config) + { + if (this.HasPartIndices && !this.HasPartIndexRange) throw new NotImplementedException("PARTINDICES"); + if (other.HasPartIndices && !other.HasPartIndexRange) throw new NotImplementedException("PARTINDICES"); - if (other.IsEmpty) return this; - if (this.IsEmpty) return other; - if (this.Storage != other.Storage) throw new InvalidOperationException("Invariant 3267c283-3192-438b-a219-821d67ac5061."); + if (other.IsEmpty) return this; + if (this.IsEmpty) return other; + if (this.Storage != other.Storage) throw new InvalidOperationException("Invariant 3267c283-3192-438b-a219-821d67ac5061."); - if (Root.Value is PointSetNode root && other.Root.Value is PointSetNode otherRoot) - { - var merged = root.Merge(otherRoot, pointsMergedCallback, config); - var id = $"{Guid.NewGuid()}.json"; - return new PointSet(Storage, id, merged.Item1.Id, SplitLimit); - } - else - { - throw new InvalidOperationException($"Cannot merge {Root.Value!.GetType()} with {other.Root.Value!.GetType()}. Error cf3a17bf-c3c7-46de-9fb7-f08243992ff0."); - } + if (Root.Value is PointSetNode root && other.Root.Value is PointSetNode otherRoot) + { + var merged = root.Merge(otherRoot, pointsMergedCallback, config); + var id = $"{Guid.NewGuid()}.json"; + return new PointSet(Storage, id, merged.Item1.Id, SplitLimit); + } + else + { + throw new InvalidOperationException($"Cannot merge {Root.Value!.GetType()} with {other.Root.Value!.GetType()}. Error cf3a17bf-c3c7-46de-9fb7-f08243992ff0."); } - - #endregion } + + #endregion } diff --git a/src/Aardvark.Geometry.PointSet/Octrees/PointSetNode.cs b/src/Aardvark.Geometry.PointSet/Octrees/PointSetNode.cs index 5e5ba2a2..d69debe6 100644 --- a/src/Aardvark.Geometry.PointSet/Octrees/PointSetNode.cs +++ b/src/Aardvark.Geometry.PointSet/Octrees/PointSetNode.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -20,1323 +20,1321 @@ You should have received a copy of the GNU Affero General Public License using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.Json.Serialization; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// An immutable point cloud node. +/// +public class PointSetNode : IPointCloudNode { + private static readonly string GuidEmptyString = Guid.Empty.ToString(); + + /// + /// This node has been stored during out-of-core import, without derived properties, like kd-trees, etc. + /// The value of this attribute has no meaning. + /// + public static readonly Durable.Def TemporaryImportNode = new( + new Guid("01bcdfee-b01e-40ff-ad02-7ea724198f10"), + "Durable.Octree.TemporaryImportNode", + "This node has been stored during out-of-core import, without derived properties, like kd-trees, etc. The value of this attribute has no meaning.", + Durable.Primitives.Int32.Id, + isArray: false + ); + + public static readonly PointSetNode Empty = new( + null!, writeToStore: false, + (Durable.Octree.NodeId, Guid.Empty), + (Durable.Octree.Cell, Cell.Invalid), + (Durable.Octree.BoundingBoxExactLocal, Box3f.Invalid), + (Durable.Octree.BoundingBoxExactGlobal, Box3d.Invalid), + (Durable.Octree.PointCountCell, 0), + (Durable.Octree.PointCountTreeLeafs, 0L), + //(Durable.Octree.PositionsLocal3fReference, Guid.Empty), + //(Durable.Octree.PointRkdTreeFDataReference, Guid.Empty), + (Durable.Octree.PositionsLocal3fCentroid, V3f.NaN), + (Durable.Octree.PositionsLocal3fDistToCentroidStdDev, float.NaN), + (Durable.Octree.AveragePointDistance, float.NaN), + (Durable.Octree.AveragePointDistanceStdDev, float.NaN), + (Durable.Octree.MinTreeDepth, 0), + (Durable.Octree.MaxTreeDepth, 0), + (Durable.Octree.PartIndexRange, Range1i.Invalid), + (Durable.Octree.PerPointPartIndex1b, Array.Empty()) + ); + + #region Construction + /// - /// An immutable point cloud node. + /// Creates node. /// - public class PointSetNode : IPointCloudNode + public PointSetNode(Storage storage, bool writeToStore, params (Durable.Def, object)[] props) + : this( + ImmutableDictionary.Empty.AddRange(props.Select(x => new KeyValuePair(x.Item1, x.Item2))), + storage, writeToStore + ) { - private static readonly string GuidEmptyString = Guid.Empty.ToString(); - - /// - /// This node has been stored during out-of-core import, without derived properties, like kd-trees, etc. - /// The value of this attribute has no meaning. - /// - public static readonly Durable.Def TemporaryImportNode = new( - new Guid("01bcdfee-b01e-40ff-ad02-7ea724198f10"), - "Durable.Octree.TemporaryImportNode", - "This node has been stored during out-of-core import, without derived properties, like kd-trees, etc. The value of this attribute has no meaning.", - Durable.Primitives.Int32.Id, - isArray: false - ); + } - public static readonly PointSetNode Empty = new( - null!, writeToStore: false, - (Durable.Octree.NodeId, Guid.Empty), - (Durable.Octree.Cell, Cell.Invalid), - (Durable.Octree.BoundingBoxExactLocal, Box3f.Invalid), - (Durable.Octree.BoundingBoxExactGlobal, Box3d.Invalid), - (Durable.Octree.PointCountCell, 0), - (Durable.Octree.PointCountTreeLeafs, 0L), - //(Durable.Octree.PositionsLocal3fReference, Guid.Empty), - //(Durable.Octree.PointRkdTreeFDataReference, Guid.Empty), - (Durable.Octree.PositionsLocal3fCentroid, V3f.NaN), - (Durable.Octree.PositionsLocal3fDistToCentroidStdDev, float.NaN), - (Durable.Octree.AveragePointDistance, float.NaN), - (Durable.Octree.AveragePointDistanceStdDev, float.NaN), - (Durable.Octree.MinTreeDepth, 0), - (Durable.Octree.MaxTreeDepth, 0), - (Durable.Octree.PartIndexRange, Range1i.Invalid), - (Durable.Octree.PerPointPartIndex1b, Array.Empty()) + /// + /// Creates node. + /// + public PointSetNode( + ImmutableDictionary data, + Storage? storage, bool writeToStore + ) + { + if (!data.ContainsKey(Durable.Octree.NodeId)) throw new ArgumentException( + "Missing Durable.Octree.NodeId. Invariant a14a3d76-36cd-430b-b160-42d7a53f916d." ); - #region Construction - - /// - /// Creates node. - /// - public PointSetNode(Storage storage, bool writeToStore, params (Durable.Def, object)[] props) - : this( - ImmutableDictionary.Empty.AddRange(props.Select(x => new KeyValuePair(x.Item1, x.Item2))), - storage, writeToStore - ) - { - } - - /// - /// Creates node. - /// - public PointSetNode( - ImmutableDictionary data, - Storage? storage, bool writeToStore - ) - { - if (!data.ContainsKey(Durable.Octree.NodeId)) throw new ArgumentException( - "Missing Durable.Octree.NodeId. Invariant a14a3d76-36cd-430b-b160-42d7a53f916d." - ); - - if (!data.ContainsKey(Durable.Octree.Cell)) throw new ArgumentException( - "Missing Durable.Octree.Cell. Invariant f6f13915-c254-4d88-b60f-5e673c13ef59." - ); + if (!data.ContainsKey(Durable.Octree.Cell)) throw new ArgumentException( + "Missing Durable.Octree.Cell. Invariant f6f13915-c254-4d88-b60f-5e673c13ef59." + ); - var isObsoleteFormat = data.ContainsKey(Durable.Octree.PointRkdTreeDDataReference); + var isObsoleteFormat = data.ContainsKey(Durable.Octree.PointRkdTreeDDataReference); - storage ??= Storage.None; + storage ??= Storage.None; - Storage = storage; - Data = data; + Storage = storage; + Data = data; - //Report.Line($"{this.Id} {this.Cell}"); - //if (Id == Guid.Empty) Debugger.Break(); - //if (IsLeaf && !HasClassifications) Debugger.Break(); - //if (!IsTemporaryImportNode) Debugger.Break(); + //Report.Line($"{this.Id} {this.Cell}"); + //if (Id == Guid.Empty) Debugger.Break(); + //if (IsLeaf && !HasClassifications) Debugger.Break(); + //if (!IsTemporaryImportNode) Debugger.Break(); - var bboxCell = Cell.BoundingBox; - Center = bboxCell.Center; - //if (Center.IsNaN) throw new Exception("NaN."); - Corners = bboxCell.ComputeCorners(); + var bboxCell = Cell.BoundingBox; + Center = bboxCell.Center; + //if (Center.IsNaN) throw new Exception("NaN."); + Corners = bboxCell.ComputeCorners(); #if DEBUG - if (PositionsAbsolute?.Length > 0) // if not PointSetNode.Empty - { - // Invariant: bounding box of global positions MUST BE CONTAINED within bounding box of node cell - var bb = new Box3d(PositionsAbsolute); - if (!Cell.BoundingBox.Contains(bb)) throw new Exception($"Invariant 71e949d7-21b1-4150-bfe3-337db52c4c7d."); + if (PositionsAbsolute?.Length > 0) // if not PointSetNode.Empty + { + // Invariant: bounding box of global positions MUST BE CONTAINED within bounding box of node cell + var bb = new Box3d(PositionsAbsolute); + if (!Cell.BoundingBox.Contains(bb)) throw new Exception($"Invariant 71e949d7-21b1-4150-bfe3-337db52c4c7d."); - //// Invariant: cell of bounding box of global positions MUST EQUAL node cell - //var bbCell = new Cell(bb); - //if (Cell != bbCell) throw new InvalidOperationException($"Invariant 7564f5b1-1b70-45cf-bfbe-5c955b90d1fe."); - } + //// Invariant: cell of bounding box of global positions MUST EQUAL node cell + //var bbCell = new Cell(bb); + //if (Cell != bbCell) throw new InvalidOperationException($"Invariant 7564f5b1-1b70-45cf-bfbe-5c955b90d1fe."); + } #endif #if DEBUG && NEVERMORE - if (isObsoleteFormat) - { - Report.Line($"new PointSetNode({Id}), obsolete format"); - } + if (isObsoleteFormat) + { + Report.Line($"new PointSetNode({Id}), obsolete format"); + } #endif - #region Subnodes + #region Subnodes - if (Data.TryGetValue(Durable.Octree.SubnodesGuids, out var o) && o is Guid[] subNodeIds) + if (Data.TryGetValue(Durable.Octree.SubnodesGuids, out var o) && o is Guid[] subNodeIds) + { + Subnodes = new PersistentRef[8]; + for (var i = 0; i < 8; i++) { - Subnodes = new PersistentRef[8]; - for (var i = 0; i < 8; i++) - { - if (subNodeIds[i] == Guid.Empty) continue; - var pRef = new PersistentRef(subNodeIds[i].ToString(), storage.GetPointCloudNode, storage.TryGetPointCloudNode); - Subnodes[i] = pRef; + if (subNodeIds[i] == Guid.Empty) continue; + var pRef = new PersistentRef(subNodeIds[i].ToString(), storage.GetPointCloudNode, storage.TryGetPointCloudNode); + Subnodes[i] = pRef; #if DEBUG - // Invariant: child cells MUST be direct sub-cells - //if (pRef.Value.Cell != this.Cell.GetOctant(i)) throw new InvalidOperationException("Invariant a5308834-2509-4af5-8986-c717da792611."); + // Invariant: child cells MUST be direct sub-cells + //if (pRef.Value.Cell != this.Cell.GetOctant(i)) throw new InvalidOperationException("Invariant a5308834-2509-4af5-8986-c717da792611."); #endif - } - } - else - { - //Debugger.Break(); } + } + else + { + //Debugger.Break(); + } #endregion - #region Per-point attributes + #region Per-point attributes - var psId = PositionsId; - var csId = ColorsId; - var kdId = KdTreeId; - var nsId = NormalsId; - var isId = IntensitiesId; - var ksId = ClassificationsId; + var psId = PositionsId; + var csId = ColorsId; + var kdId = KdTreeId; + var nsId = NormalsId; + var isId = IntensitiesId; + var ksId = ClassificationsId; - Func throwWhenNull(Func f) where T : notnull - => key => f(key) ?? throw new Exception(); + Func throwWhenNull(Func f) where T : notnull + => key => f(key) ?? throw new Exception(); - if (psId != null) PersistentRefs[Durable.Octree.PositionsLocal3fReference] = new PersistentRef(psId.Value, throwWhenNull(storage.GetV3fArray), storage.TryGetV3fArrayFromCache); - if (csId != null) PersistentRefs[Durable.Octree.Colors4bReference ] = new PersistentRef(csId.Value, throwWhenNull(storage.GetC4bArray), storage.TryGetC4bArrayFromCache); - if (kdId != null) + if (psId != null) PersistentRefs[Durable.Octree.PositionsLocal3fReference] = new PersistentRef(psId.Value, throwWhenNull(storage.GetV3fArray), storage.TryGetV3fArrayFromCache); + if (csId != null) PersistentRefs[Durable.Octree.Colors4bReference ] = new PersistentRef(csId.Value, throwWhenNull(storage.GetC4bArray), storage.TryGetC4bArrayFromCache); + if (kdId != null) + { + if (isObsoleteFormat) { - if (isObsoleteFormat) - { - PersistentRefs[Durable.Octree.PointRkdTreeFDataReference] = - new PersistentRef>(kdId.Value, LoadKdTreeObsolete, TryLoadKdTreeObsoleteOut) - ; - } - else - { - PersistentRefs[Durable.Octree.PointRkdTreeFDataReference] = - new PersistentRef>(kdId.Value, LoadKdTree, TryLoadKdTreeOut) - ; - } + PersistentRefs[Durable.Octree.PointRkdTreeFDataReference] = + new PersistentRef>(kdId.Value, LoadKdTreeObsolete, TryLoadKdTreeObsoleteOut) + ; } - if (nsId != null) PersistentRefs[Durable.Octree.Normals3fReference ] = new PersistentRef(nsId.Value, throwWhenNull(storage.GetV3fArray ), storage.TryGetV3fArrayFromCache ); - if (isId != null) PersistentRefs[Durable.Octree.Intensities1iReference ] = new PersistentRef(isId.Value, throwWhenNull(storage.GetIntArray ), storage.TryGetIntArrayFromCache ); - if (ksId != null) PersistentRefs[Durable.Octree.Classifications1bReference] = new PersistentRef(ksId.Value, throwWhenNull(storage.GetByteArray), storage.TryGetByteArrayFromCache); + else + { + PersistentRefs[Durable.Octree.PointRkdTreeFDataReference] = + new PersistentRef>(kdId.Value, LoadKdTree, TryLoadKdTreeOut) + ; + } + } + if (nsId != null) PersistentRefs[Durable.Octree.Normals3fReference ] = new PersistentRef(nsId.Value, throwWhenNull(storage.GetV3fArray ), storage.TryGetV3fArrayFromCache ); + if (isId != null) PersistentRefs[Durable.Octree.Intensities1iReference ] = new PersistentRef(isId.Value, throwWhenNull(storage.GetIntArray ), storage.TryGetIntArrayFromCache ); + if (ksId != null) PersistentRefs[Durable.Octree.Classifications1bReference] = new PersistentRef(ksId.Value, throwWhenNull(storage.GetByteArray), storage.TryGetByteArrayFromCache); - #endregion + #endregion - #region Durable.Octree.BoundingBoxExactLocal, Durable.Octree.BoundingBoxExactGlobal + #region Durable.Octree.BoundingBoxExactLocal, Durable.Octree.BoundingBoxExactGlobal - if (HasPositions && (!HasBoundingBoxExactLocal || !HasBoundingBoxExactGlobal)) + if (HasPositions && (!HasBoundingBoxExactLocal || !HasBoundingBoxExactGlobal)) + { + if (!HasBoundingBoxExactLocal) // why only for new format? (!isObsoleteFormat && !HasBoundingBoxExactLocal) { - if (!HasBoundingBoxExactLocal) // why only for new format? (!isObsoleteFormat && !HasBoundingBoxExactLocal) + var bboxExactLocal = Positions.Value!.Length > 0 ? new Box3f(Positions.Value) : Box3f.Invalid; + Data = Data.Add(Durable.Octree.BoundingBoxExactLocal, bboxExactLocal); + } + + if (!HasBoundingBoxExactGlobal) + { + if (HasBoundingBoxExactLocal && IsLeaf) { - var bboxExactLocal = Positions.Value!.Length > 0 ? new Box3f(Positions.Value) : Box3f.Invalid; - Data = Data.Add(Durable.Octree.BoundingBoxExactLocal, bboxExactLocal); + var bboxExactGlobal = (Box3d)BoundingBoxExactLocal + Center; + Data = Data.Add(Durable.Octree.BoundingBoxExactGlobal, bboxExactGlobal); } - - if (!HasBoundingBoxExactGlobal) + else if (isObsoleteFormat) { - if (HasBoundingBoxExactLocal && IsLeaf) - { - var bboxExactGlobal = (Box3d)BoundingBoxExactLocal + Center; - Data = Data.Add(Durable.Octree.BoundingBoxExactGlobal, bboxExactGlobal); - } - else if (isObsoleteFormat) - { - var bboxExactGlobal = Cell.BoundingBox; - Data = Data.Add(Durable.Octree.BoundingBoxExactGlobal, bboxExactGlobal); - } - else - { - var bboxExactGlobal = new Box3d(Subnodes.Where(x => x != null).Select(x => x!.Value!.BoundingBoxExactGlobal)); - Data = Data.Add(Durable.Octree.BoundingBoxExactGlobal, bboxExactGlobal); - } + var bboxExactGlobal = Cell.BoundingBox; + Data = Data.Add(Durable.Octree.BoundingBoxExactGlobal, bboxExactGlobal); + } + else + { + var bboxExactGlobal = new Box3d(Subnodes.Where(x => x != null).Select(x => x!.Value!.BoundingBoxExactGlobal)); + Data = Data.Add(Durable.Octree.BoundingBoxExactGlobal, bboxExactGlobal); } } + } - #endregion + #endregion - #region Durable.Octree.PointCountCell + #region Durable.Octree.PointCountCell - if (!Has(Durable.Octree.PointCountCell)) + if (!Has(Durable.Octree.PointCountCell)) + { + Data = Data.Add(Durable.Octree.PointCountCell, HasPositions ? Positions.Value!.Length : 0); + } + + #endregion + + #region Durable.Octree.PointCountTreeLeafs + + if (!Has(Durable.Octree.PointCountTreeLeafs)) + { + if (IsLeaf) { - Data = Data.Add(Durable.Octree.PointCountCell, HasPositions ? Positions.Value!.Length : 0); + Data = Data.Add(Durable.Octree.PointCountTreeLeafs, (long)PointCountCell); } + else + { + Data = Data.Add(Durable.Octree.PointCountTreeLeafs, Subnodes.Where(x => x != null).Sum(x => x.Value!.PointCountTree)); + } + } - #endregion + #endregion - #region Durable.Octree.PointCountTreeLeafs + #region Centroid* - if (!Has(Durable.Octree.PointCountTreeLeafs)) + if (HasPositions && (!HasCentroidLocal || !HasCentroidLocalStdDev)) + { + if (!isObsoleteFormat) { - if (IsLeaf) + var ps = Positions.Value; + var centroid = ps.ComputeCentroid(); + + if (!HasCentroidLocal) { - Data = Data.Add(Durable.Octree.PointCountTreeLeafs, (long)PointCountCell); + Data = Data.Add(Durable.Octree.PositionsLocal3fCentroid, centroid); } - else + + var dists = ps.Map(p => (p - centroid).Length); + var (avg, sd) = dists.ComputeAvgAndStdDev(); + + if (!HasCentroidLocalStdDev) { - Data = Data.Add(Durable.Octree.PointCountTreeLeafs, Subnodes.Where(x => x != null).Sum(x => x.Value!.PointCountTree)); + Data = Data.Add(Durable.Octree.PositionsLocal3fDistToCentroidStdDev, sd); } } + } - #endregion + #endregion - #region Centroid* + #region TreeDepth* - if (HasPositions && (!HasCentroidLocal || !HasCentroidLocalStdDev)) + if (!HasMinTreeDepth || !HasMaxTreeDepth) + { + if (IsLeaf) { - if (!isObsoleteFormat) + if (!HasMinTreeDepth) { - var ps = Positions.Value; - var centroid = ps.ComputeCentroid(); - - if (!HasCentroidLocal) - { - Data = Data.Add(Durable.Octree.PositionsLocal3fCentroid, centroid); - } - - var dists = ps.Map(p => (p - centroid).Length); - var (avg, sd) = dists.ComputeAvgAndStdDev(); - - if (!HasCentroidLocalStdDev) - { - Data = Data.Add(Durable.Octree.PositionsLocal3fDistToCentroidStdDev, sd); - } + Data = Data.Add(Durable.Octree.MinTreeDepth, 0); + } + if (!HasMaxTreeDepth) + { + Data = Data.Add(Durable.Octree.MaxTreeDepth, 0); } } - - #endregion - - #region TreeDepth* - - if (!HasMinTreeDepth || !HasMaxTreeDepth) + else { - if (IsLeaf) + if (isObsoleteFormat) { - if (!HasMinTreeDepth) - { - Data = Data.Add(Durable.Octree.MinTreeDepth, 0); - } - if (!HasMaxTreeDepth) - { - Data = Data.Add(Durable.Octree.MaxTreeDepth, 0); - } + Data = Data.Add(Durable.Octree.MinTreeDepth, 0); + Data = Data.Add(Durable.Octree.MaxTreeDepth, 0); } else { - if (isObsoleteFormat) + var min = 1; + var max = 0; + for (var i = 0; i < 8; i++) + { + var r = Subnodes![i]; + if (r == null) continue; + var n = r.Value!; + min = Math.Min(min, 1 + n.MinTreeDepth); + max = Math.Max(max, 1 + n.MaxTreeDepth); + } + if (!HasMinTreeDepth) { - Data = Data.Add(Durable.Octree.MinTreeDepth, 0); - Data = Data.Add(Durable.Octree.MaxTreeDepth, 0); + Data = Data.Add(Durable.Octree.MinTreeDepth, min); } - else + if (!HasMaxTreeDepth) { - var min = 1; - var max = 0; - for (var i = 0; i < 8; i++) - { - var r = Subnodes![i]; - if (r == null) continue; - var n = r.Value!; - min = Math.Min(min, 1 + n.MinTreeDepth); - max = Math.Max(max, 1 + n.MaxTreeDepth); - } - if (!HasMinTreeDepth) - { - Data = Data.Add(Durable.Octree.MinTreeDepth, min); - } - if (!HasMaxTreeDepth) - { - Data = Data.Add(Durable.Octree.MaxTreeDepth, max); - } + Data = Data.Add(Durable.Octree.MaxTreeDepth, max); } } } + } - #endregion + #endregion - #region KdTree + #region KdTree - if (HasPositions && !HasKdTree && !IsTemporaryImportNode) - { + if (HasPositions && !HasKdTree && !IsTemporaryImportNode) + { #if !READONLY - kdId = ComputeAndStoreKdTree(Storage, Positions.Value); - Data = Data.Add(Durable.Octree.PointRkdTreeFDataReference, kdId); - PersistentRefs[Durable.Octree.PointRkdTreeFDataReference] = - new PersistentRef>(kdId.Value, LoadKdTree, TryLoadKdTreeOut) - ; + kdId = ComputeAndStoreKdTree(Storage, Positions.Value); + Data = Data.Add(Durable.Octree.PointRkdTreeFDataReference, kdId); + PersistentRefs[Durable.Octree.PointRkdTreeFDataReference] = + new PersistentRef>(kdId.Value, LoadKdTree, TryLoadKdTreeOut) + ; #else - //Debugger.Break(); + //Debugger.Break(); #endif - } + } - #endregion + #endregion - if ((HasPositions && Positions.Value!.Length != PointCountCell) || - (HasColors && Colors.Value!.Length != PointCountCell) || - (HasNormals && Normals.Value!.Length != PointCountCell) || - (HasIntensities && Intensities.Value!.Length != PointCountCell) || - (HasClassifications && Classifications.Value!.Length != PointCountCell) - ) - { + if ((HasPositions && Positions.Value!.Length != PointCountCell) || + (HasColors && Colors.Value!.Length != PointCountCell) || + (HasNormals && Normals.Value!.Length != PointCountCell) || + (HasIntensities && Intensities.Value!.Length != PointCountCell) || + (HasClassifications && Classifications.Value!.Length != PointCountCell) + ) + { #if DEBUG - throw new InvalidOperationException( + throw new InvalidOperationException( #else - Report.Error( + Report.Error( #endif - $"[PointSetNode] Inconsistent counts. Id = {Id}. " + - $"PointCountCell={PointCountCell}, " + - $"Positions={Positions?.Value?.Length}, " + - $"Colors={Colors?.Value?.Length}, " + - $"Normals={Normals?.Value?.Length}, " + - $"Intensities={Intensities?.Value?.Length}, " + - $"Classifications={Classifications?.Value?.Length}, " + - $"Invariant b714b13e-35fb-4186-9c6c-ca6abbc46a4d." - ); - } + $"[PointSetNode] Inconsistent counts. Id = {Id}. " + + $"PointCountCell={PointCountCell}, " + + $"Positions={Positions?.Value?.Length}, " + + $"Colors={Colors?.Value?.Length}, " + + $"Normals={Normals?.Value?.Length}, " + + $"Intensities={Intensities?.Value?.Length}, " + + $"Classifications={Classifications?.Value?.Length}, " + + $"Invariant b714b13e-35fb-4186-9c6c-ca6abbc46a4d." + ); + } - if (writeToStore) - { - //Report.Warn($"[writeToStore] {Id}"); - WriteToStore(); - } + if (writeToStore) + { + //Report.Warn($"[writeToStore] {Id}"); + WriteToStore(); + } #if DEBUG - if (IsLeaf) - { - if (PointCountCell != PointCountTree) - throw new InvalidOperationException("Invariant 9464f38c-dc98-4d68-a8ac-0baed9f182b4."); + if (IsLeaf) + { + if (PointCountCell != PointCountTree) + throw new InvalidOperationException("Invariant 9464f38c-dc98-4d68-a8ac-0baed9f182b4."); - if (Id != Guid.Empty) - { - if (!( - Has(Durable.Octree.PositionsLocal3fReference) || - Has(Durable.Octree.PositionsLocal3f) || - Has(Durable.Octree.PositionsLocal3b) || - Has(Durable.Octree.PositionsLocal3us) || - Has(Durable.Octree.PositionsLocal3ui) || - Has(Durable.Octree.PositionsLocal3ul) - )) - throw new ArgumentException("Invariant 663c45a4-1286-45ba-870c-fb4ceebdf318."); - - if (Has(Durable.Octree.PositionsLocal3fReference) && PositionsId == null) - throw new InvalidOperationException("Invariant ba64ffe9-ada4-4fff-a4e9-0916c1cc9992."); - - #if !READONLY - if (KdTreeId == null && !Has(TemporaryImportNode)) - throw new InvalidOperationException("Invariant 606e8a7b-6e75-496a-bc2a-dfbe6e2c9b10."); - #endif - } + if (Id != Guid.Empty) + { + if (!( + Has(Durable.Octree.PositionsLocal3fReference) || + Has(Durable.Octree.PositionsLocal3f) || + Has(Durable.Octree.PositionsLocal3b) || + Has(Durable.Octree.PositionsLocal3us) || + Has(Durable.Octree.PositionsLocal3ui) || + Has(Durable.Octree.PositionsLocal3ul) + )) + throw new ArgumentException("Invariant 663c45a4-1286-45ba-870c-fb4ceebdf318."); + + if (Has(Durable.Octree.PositionsLocal3fReference) && PositionsId == null) + throw new InvalidOperationException("Invariant ba64ffe9-ada4-4fff-a4e9-0916c1cc9992."); + + #if !READONLY + if (KdTreeId == null && !Has(TemporaryImportNode)) + throw new InvalidOperationException("Invariant 606e8a7b-6e75-496a-bc2a-dfbe6e2c9b10."); + #endif } + } #endif - PointRkdTreeF LoadKdTree(string key) + PointRkdTreeF LoadKdTree(string key) + { + var value = Storage.GetPointRkdTreeFData(key); + var ps = Positions.Value!; + return new PointRkdTreeF( + 3, ps.Length, ps, + (xs, i) => xs[(int)i], (v, i) => (float)v[i], + (a, b) => Vec.Distance(a, b), (i, a, b) => b - a, + (a, b, c) => Vec.DistanceToLine(a, b, c), Fun.Lerp, 1e-6f, + value + ); + } + + PointRkdTreeF LoadKdTreeObsolete(string key) + { + var value = Storage.GetPointRkdTreeFDataFromD(key); + var ps = Positions.Value!; + return new PointRkdTreeF( + 3, ps.Length, ps, + (xs, i) => xs[(int)i], (v, i) => (float)v[i], + (a, b) => Vec.Distance(a, b), (i, a, b) => b - a, + (a, b, c) => Vec.DistanceToLine(a, b, c), Fun.Lerp, 1e-6f, + value + ); + } + + bool TryLoadKdTreeOut(string key, [NotNullWhen(true)] out PointRkdTreeF? result) + { + var (ok, value) = Storage.TryGetPointRkdTreeFData(key); + if (ok == false) + { + result = default; + return false; + } + else { - var value = Storage.GetPointRkdTreeFData(key); var ps = Positions.Value!; - return new PointRkdTreeF( + result = new PointRkdTreeF( 3, ps.Length, ps, (xs, i) => xs[(int)i], (v, i) => (float)v[i], (a, b) => Vec.Distance(a, b), (i, a, b) => b - a, (a, b, c) => Vec.DistanceToLine(a, b, c), Fun.Lerp, 1e-6f, value ); + return true; } + } - PointRkdTreeF LoadKdTreeObsolete(string key) + bool TryLoadKdTreeObsoleteOut(string key, [NotNullWhen(true)] out PointRkdTreeF? result) + { + var (ok, value) = Storage.TryGetPointRkdTreeFDataFromD(key); + if (ok == false) + { + result = default; + return false; + } + else { - var value = Storage.GetPointRkdTreeFDataFromD(key); var ps = Positions.Value!; - return new PointRkdTreeF( + result = new PointRkdTreeF( 3, ps.Length, ps, (xs, i) => xs[(int)i], (v, i) => (float)v[i], (a, b) => Vec.Distance(a, b), (i, a, b) => b - a, (a, b, c) => Vec.DistanceToLine(a, b, c), Fun.Lerp, 1e-6f, value ); - } - - bool TryLoadKdTreeOut(string key, [NotNullWhen(true)] out PointRkdTreeF? result) - { - var (ok, value) = Storage.TryGetPointRkdTreeFData(key); - if (ok == false) - { - result = default; - return false; - } - else - { - var ps = Positions.Value!; - result = new PointRkdTreeF( - 3, ps.Length, ps, - (xs, i) => xs[(int)i], (v, i) => (float)v[i], - (a, b) => Vec.Distance(a, b), (i, a, b) => b - a, - (a, b, c) => Vec.DistanceToLine(a, b, c), Fun.Lerp, 1e-6f, - value - ); - return true; - } - } - - bool TryLoadKdTreeObsoleteOut(string key, [NotNullWhen(true)] out PointRkdTreeF? result) - { - var (ok, value) = Storage.TryGetPointRkdTreeFDataFromD(key); - if (ok == false) - { - result = default; - return false; - } - else - { - var ps = Positions.Value!; - result = new PointRkdTreeF( - 3, ps.Length, ps, - (xs, i) => xs[(int)i], (v, i) => (float)v[i], - (a, b) => Vec.Distance(a, b), (i, a, b) => b - a, - (a, b, c) => Vec.DistanceToLine(a, b, c), Fun.Lerp, 1e-6f, - value - ); - return true; - } + return true; } } + } - /// - /// Writes this node to store. - /// - public PointSetNode WriteToStore() - { - this.CheckDerivedAttributes(); - Storage?.Add(Id.ToString(), this); - return this; - } + /// + /// Writes this node to store. + /// + public PointSetNode WriteToStore() + { + this.CheckDerivedAttributes(); + Storage?.Add(Id.ToString(), this); + return this; + } - IPointCloudNode IPointCloudNode.WriteToStore() => WriteToStore(); + IPointCloudNode IPointCloudNode.WriteToStore() => WriteToStore(); #endregion - #region Properties (state to serialize) + #region Properties (state to serialize) - /// - /// Durable properties. - /// - private ImmutableDictionary Data { get; } = ImmutableDictionary.Empty; + /// + /// Durable properties. + /// + private ImmutableDictionary Data { get; } = ImmutableDictionary.Empty; - #endregion + #endregion + + #region Properties (derived/runtime, non-serialized) + + /// + /// Runtime. + /// + [JsonIgnore] + public readonly Storage Storage; + + /// + /// Runtime. + /// + [JsonIgnore] + private Dictionary PersistentRefs { get; } = []; - #region Properties (derived/runtime, non-serialized) - - /// - /// Runtime. - /// - [JsonIgnore] - public readonly Storage Storage; - - /// - /// Runtime. - /// - [JsonIgnore] - private Dictionary PersistentRefs { get; } = new Dictionary(); - - #region Cell attributes - - /// - /// This node's unique id (16 bytes). - /// - [JsonIgnore] - public Guid Id => (Guid)Data.Get(Durable.Octree.NodeId); - - /// - /// Returns true if this a temporary import node (without computed properties like kd-tree). - /// - [JsonIgnore] - public bool IsTemporaryImportNode => Has(TemporaryImportNode); - - /// - /// This node's index/bounds. - /// - [JsonIgnore] - public Cell Cell => (Cell)Data.Get(Durable.Octree.Cell); - - /// - /// Octree. Number of points in this cell. - /// Durable definition 172e1f20-0ffc-4d9c-9b3d-903fca41abe3. - /// - [JsonIgnore] - public int PointCountCell => (int)Data.Get(Durable.Octree.PointCountCell); - - - /// - /// Number of points in this tree (sum of leaves). - /// - [JsonIgnore] - public long PointCountTree => (long)Data.Get(Durable.Octree.PointCountTreeLeafs); - - /// - /// Subnodes (8), or null if leaf. - /// - [JsonIgnore] - public Guid?[]? SubnodeIds + #region Cell attributes + + /// + /// This node's unique id (16 bytes). + /// + [JsonIgnore] + public Guid Id => (Guid)Data.Get(Durable.Octree.NodeId); + + /// + /// Returns true if this a temporary import node (without computed properties like kd-tree). + /// + [JsonIgnore] + public bool IsTemporaryImportNode => Has(TemporaryImportNode); + + /// + /// This node's index/bounds. + /// + [JsonIgnore] + public Cell Cell => (Cell)Data.Get(Durable.Octree.Cell); + + /// + /// Octree. Number of points in this cell. + /// Durable definition 172e1f20-0ffc-4d9c-9b3d-903fca41abe3. + /// + [JsonIgnore] + public int PointCountCell => (int)Data.Get(Durable.Octree.PointCountCell); + + + /// + /// Number of points in this tree (sum of leaves). + /// + [JsonIgnore] + public long PointCountTree => (long)Data.Get(Durable.Octree.PointCountTreeLeafs); + + /// + /// Subnodes (8), or null if leaf. + /// + [JsonIgnore] + public Guid?[]? SubnodeIds + { + get { - get + if (Data.TryGetValue(Durable.Octree.SubnodesGuids, out var o)) { - if (Data.TryGetValue(Durable.Octree.SubnodesGuids, out var o)) - { - return ((Guid[])o).Map(x => x != Guid.Empty ? x : (Guid?)null); - } - else - { - return null; - } + return ((Guid[])o).Map(x => x != Guid.Empty ? x : (Guid?)null); + } + else + { + return null; } } + } - #endregion + #endregion - #region Positions + #region Positions - /// - [JsonIgnore] - public bool HasPositions => - Data.ContainsKey(Durable.Octree.PositionsLocal3fReference) || - Data.ContainsKey(Durable.Octree.PositionsLocal3f) || - Data.ContainsKey(Durable.Octree.PositionsLocal3b) || - Data.ContainsKey(Durable.Octree.PositionsLocal3us) || - Data.ContainsKey(Durable.Octree.PositionsLocal3ui) || - Data.ContainsKey(Durable.Octree.PositionsLocal3ul) - ; + /// + [JsonIgnore] + public bool HasPositions => + Data.ContainsKey(Durable.Octree.PositionsLocal3fReference) || + Data.ContainsKey(Durable.Octree.PositionsLocal3f) || + Data.ContainsKey(Durable.Octree.PositionsLocal3b) || + Data.ContainsKey(Durable.Octree.PositionsLocal3us) || + Data.ContainsKey(Durable.Octree.PositionsLocal3ui) || + Data.ContainsKey(Durable.Octree.PositionsLocal3ul) + ; - /// - [JsonIgnore] - public Guid? PositionsId => Data.TryGetValue(Durable.Octree.PositionsLocal3fReference, out var id) ? (Guid?)id : null; - - /// - /// Point positions relative to cell's center, or null if no positions. - /// - [JsonIgnore] - public PersistentRef Positions + /// + [JsonIgnore] + public Guid? PositionsId => Data.TryGetValue(Durable.Octree.PositionsLocal3fReference, out var id) ? (Guid?)id : null; + + /// + /// Point positions relative to cell's center, or null if no positions. + /// + [JsonIgnore] + public PersistentRef Positions + { + get { - get + if (PersistentRefs.TryGetValue(Durable.Octree.PositionsLocal3fReference, out object? o)) { - if (PersistentRefs.TryGetValue(Durable.Octree.PositionsLocal3fReference, out object? o)) + if (((PersistentRef)o).Id != GuidEmptyString) { - if (((PersistentRef)o).Id != GuidEmptyString) - { - return (PersistentRef)o; - } - else - { - var ps = Array.Empty(); - return new PersistentRef(Guid.Empty, ps); - } - } - else if (Data.TryGetValue(Durable.Octree.PositionsLocal3f, out o)) - { - var ps = (V3f[])o; - return new PersistentRef(Guid.Empty, ps); - } - else if (Data.TryGetValue(Durable.Octree.PositionsLocal3b, out o)) - { - var qs = (byte[])o; - var hsize = Math.Pow(2.0, Cell.Exponent - 1); - var step = (2.0 * hsize) / (byte.MaxValue + 1); - var pCount = PointCountCell; - var ps = new V3f[pCount]; - for (int i = 0, j = 0; i < pCount; i++) - ps[i] = new V3f(qs[j++] * step - hsize, qs[j++] * step - hsize, qs[j++] * step - hsize); - return new PersistentRef(Guid.Empty, ps); - } - else if (Data.TryGetValue(Durable.Octree.PositionsLocal3us, out o)) - { - var qs = (ushort[])o; - var hsize = Math.Pow(2.0, Cell.Exponent - 1); - var step = (2.0 * hsize) / (ushort.MaxValue + 1); - var pCount = PointCountCell; - var ps = new V3f[pCount]; - for (int i = 0, j = 0; i < pCount; i++) - ps[i] = new V3f(qs[j++] * step - hsize, qs[j++] * step - hsize, qs[j++] * step - hsize); - return new PersistentRef(Guid.Empty, ps); - } - else if (Data.TryGetValue(Durable.Octree.PositionsLocal3ui, out o)) - { - var qs = (uint[])o; - var hsize = Math.Pow(2.0, Cell.Exponent - 1); - var step = (2.0 * hsize) / ((ulong)uint.MaxValue + 1); - var pCount = PointCountCell; - var ps = new V3f[pCount]; - for (int i = 0, j = 0; i < pCount; i++) - ps[i] = new V3f(qs[j++] * step - hsize, qs[j++] * step - hsize, qs[j++] * step - hsize); - return new PersistentRef(Guid.Empty, ps); + return (PersistentRef)o; } - else if (Data.TryGetValue(Durable.Octree.PositionsLocal3ul, out o)) + else { - var qs = (ulong[])o; - var hsize = Math.Pow(2.0, Cell.Exponent - 1); - var step = (2.0 * hsize) / ((double)ulong.MaxValue + 1); - var pCount = PointCountCell; - var ps = new V3f[pCount]; - for (int i = 0, j = 0; i < pCount; i++) - ps[i] = new V3f(qs[j++] * step - hsize, qs[j++] * step - hsize, qs[j++] * step - hsize); + var ps = Array.Empty(); return new PersistentRef(Guid.Empty, ps); } - else - return new PersistentRef(Guid.Empty, Array.Empty()); } + else if (Data.TryGetValue(Durable.Octree.PositionsLocal3f, out o)) + { + var ps = (V3f[])o; + return new PersistentRef(Guid.Empty, ps); + } + else if (Data.TryGetValue(Durable.Octree.PositionsLocal3b, out o)) + { + var qs = (byte[])o; + var hsize = Math.Pow(2.0, Cell.Exponent - 1); + var step = (2.0 * hsize) / (byte.MaxValue + 1); + var pCount = PointCountCell; + var ps = new V3f[pCount]; + for (int i = 0, j = 0; i < pCount; i++) + ps[i] = new V3f(qs[j++] * step - hsize, qs[j++] * step - hsize, qs[j++] * step - hsize); + return new PersistentRef(Guid.Empty, ps); + } + else if (Data.TryGetValue(Durable.Octree.PositionsLocal3us, out o)) + { + var qs = (ushort[])o; + var hsize = Math.Pow(2.0, Cell.Exponent - 1); + var step = (2.0 * hsize) / (ushort.MaxValue + 1); + var pCount = PointCountCell; + var ps = new V3f[pCount]; + for (int i = 0, j = 0; i < pCount; i++) + ps[i] = new V3f(qs[j++] * step - hsize, qs[j++] * step - hsize, qs[j++] * step - hsize); + return new PersistentRef(Guid.Empty, ps); + } + else if (Data.TryGetValue(Durable.Octree.PositionsLocal3ui, out o)) + { + var qs = (uint[])o; + var hsize = Math.Pow(2.0, Cell.Exponent - 1); + var step = (2.0 * hsize) / ((ulong)uint.MaxValue + 1); + var pCount = PointCountCell; + var ps = new V3f[pCount]; + for (int i = 0, j = 0; i < pCount; i++) + ps[i] = new V3f(qs[j++] * step - hsize, qs[j++] * step - hsize, qs[j++] * step - hsize); + return new PersistentRef(Guid.Empty, ps); + } + else if (Data.TryGetValue(Durable.Octree.PositionsLocal3ul, out o)) + { + var qs = (ulong[])o; + var hsize = Math.Pow(2.0, Cell.Exponent - 1); + var step = (2.0 * hsize) / ((double)ulong.MaxValue + 1); + var pCount = PointCountCell; + var ps = new V3f[pCount]; + for (int i = 0, j = 0; i < pCount; i++) + ps[i] = new V3f(qs[j++] * step - hsize, qs[j++] * step - hsize, qs[j++] * step - hsize); + return new PersistentRef(Guid.Empty, ps); + } + else + return new PersistentRef(Guid.Empty, []); } + } - /// - /// Point positions (absolute), or null if no positions. - /// - [JsonIgnore] - public V3d[] PositionsAbsolute => Positions.Value.Map(p => new V3d(Center.X + p.X, Center.Y + p.Y, Center.Z + p.Z))!; + /// + /// Point positions (absolute), or null if no positions. + /// + [JsonIgnore] + public V3d[] PositionsAbsolute => Positions.Value.Map(p => new V3d(Center.X + p.X, Center.Y + p.Y, Center.Z + p.Z))!; - #endregion + #endregion - #region BoundingBoxExactLocal + #region BoundingBoxExactLocal - /// - [JsonIgnore] - [MemberNotNullWhen(true, nameof(BoundingBoxExactLocal))] - public bool HasBoundingBoxExactLocal => Data.ContainsKey(Durable.Octree.BoundingBoxExactLocal); + /// + [JsonIgnore] + [MemberNotNullWhen(true, nameof(BoundingBoxExactLocal))] + public bool HasBoundingBoxExactLocal => Data.ContainsKey(Durable.Octree.BoundingBoxExactLocal); - /// - [JsonIgnore] - public Box3f BoundingBoxExactLocal => Data.TryGetValue(Durable.Octree.BoundingBoxExactLocal, out var o) ? (Box3f)o : default; + /// + [JsonIgnore] + public Box3f BoundingBoxExactLocal => Data.TryGetValue(Durable.Octree.BoundingBoxExactLocal, out var o) ? (Box3f)o : default; - #endregion + #endregion - #region BoundingBoxExactGlobal + #region BoundingBoxExactGlobal - /// - [JsonIgnore] - [MemberNotNullWhen(true, nameof(BoundingBoxExactGlobal))] - public bool HasBoundingBoxExactGlobal => Data.ContainsKey(Durable.Octree.BoundingBoxExactGlobal); + /// + [JsonIgnore] + [MemberNotNullWhen(true, nameof(BoundingBoxExactGlobal))] + public bool HasBoundingBoxExactGlobal => Data.ContainsKey(Durable.Octree.BoundingBoxExactGlobal); - /// - [JsonIgnore] - public Box3d BoundingBoxExactGlobal => Data.TryGetValue(Durable.Octree.BoundingBoxExactGlobal, out var o) ? (Box3d)o : default; + /// + [JsonIgnore] + public Box3d BoundingBoxExactGlobal => Data.TryGetValue(Durable.Octree.BoundingBoxExactGlobal, out var o) ? (Box3d)o : default; - #endregion + #endregion - #region Colors + #region Colors - /// - [JsonIgnore] - - [MemberNotNullWhen(true, nameof(Colors))] - public bool HasColors => - Data.ContainsKey(Durable.Octree.Colors4bReference) || - Data.ContainsKey(Durable.Octree.Colors4b) || - Data.ContainsKey(Durable.Octree.Colors3bReference) || - Data.ContainsKey(Durable.Octree.Colors3b) - ; + /// + [JsonIgnore] - /// - [JsonIgnore] - public Guid? ColorsId + [MemberNotNullWhen(true, nameof(Colors))] + public bool HasColors => + Data.ContainsKey(Durable.Octree.Colors4bReference) || + Data.ContainsKey(Durable.Octree.Colors4b) || + Data.ContainsKey(Durable.Octree.Colors3bReference) || + Data.ContainsKey(Durable.Octree.Colors3b) + ; + + /// + [JsonIgnore] + public Guid? ColorsId + { + get { - get + if (Data.TryGetValue(Durable.Octree.Colors4bReference, out var id4b)) { - if (Data.TryGetValue(Durable.Octree.Colors4bReference, out var id4b)) - { - return (Guid?)id4b; - } - else if (Data.TryGetValue(Durable.Octree.Colors3bReference, out var id3b)) - { - return (Guid?)id3b; - } - else - { - return null; - } + return (Guid?)id4b; + } + else if (Data.TryGetValue(Durable.Octree.Colors3bReference, out var id3b)) + { + return (Guid?)id3b; + } + else + { + return null; } } + } - /// - /// Point colors, or null if no points. - /// - [JsonIgnore] - public PersistentRef? Colors + /// + /// Point colors, or null if no points. + /// + [JsonIgnore] + public PersistentRef? Colors + { + get { - get + if (PersistentRefs.TryGetValue(Durable.Octree.Colors4bReference, out object? o) && ((PersistentRef)o).Id != GuidEmptyString) { - if (PersistentRefs.TryGetValue(Durable.Octree.Colors4bReference, out object? o) && ((PersistentRef)o).Id != GuidEmptyString) - { - return (PersistentRef)o; - } - else if (Data.TryGetValue(Durable.Octree.Colors4b, out o)) - { - var xs = (C4b[])o; - return new PersistentRef(Guid.Empty, xs); - } - else if (PersistentRefs.TryGetValue(Durable.Octree.Colors3bReference, out o) && ((PersistentRef)o).Id != GuidEmptyString) - { - // convert on the fly ... - var pref = (PersistentRef)o; - var xs = pref.Value.Map(c => new C4b(c)); - return new PersistentRef(Guid.Empty, xs); - } - else if (Data.TryGetValue(Durable.Octree.Colors3b, out o)) - { - // convert on the fly - var xs = ((C3b[])o).Map(c => new C4b(c)); - return new PersistentRef(Guid.Empty, xs); - } - else - { - return null; - } + return (PersistentRef)o; + } + else if (Data.TryGetValue(Durable.Octree.Colors4b, out o)) + { + var xs = (C4b[])o; + return new PersistentRef(Guid.Empty, xs); + } + else if (PersistentRefs.TryGetValue(Durable.Octree.Colors3bReference, out o) && ((PersistentRef)o).Id != GuidEmptyString) + { + // convert on the fly ... + var pref = (PersistentRef)o; + var xs = pref.Value.Map(c => new C4b(c)); + return new PersistentRef(Guid.Empty, xs); + } + else if (Data.TryGetValue(Durable.Octree.Colors3b, out o)) + { + // convert on the fly + var xs = ((C3b[])o).Map(c => new C4b(c)); + return new PersistentRef(Guid.Empty, xs); + } + else + { + return null; } } + } - #endregion + #endregion - #region Normals + #region Normals - /// - [JsonIgnore] + /// + [JsonIgnore] - [MemberNotNullWhen(true, nameof(Normals))] - public bool HasNormals => - Data.ContainsKey(Durable.Octree.Normals3fReference) || - Data.ContainsKey(Durable.Octree.Normals3f) - ; + [MemberNotNullWhen(true, nameof(Normals))] + public bool HasNormals => + Data.ContainsKey(Durable.Octree.Normals3fReference) || + Data.ContainsKey(Durable.Octree.Normals3f) + ; - /// - [JsonIgnore] - public Guid? NormalsId => Data.TryGetValue(Durable.Octree.Normals3fReference, out var id) ? (Guid?)id : null; + /// + [JsonIgnore] + public Guid? NormalsId => Data.TryGetValue(Durable.Octree.Normals3fReference, out var id) ? (Guid?)id : null; - /// - [JsonIgnore] - public PersistentRef? Normals + /// + [JsonIgnore] + public PersistentRef? Normals + { + get { - get + if (PersistentRefs.TryGetValue(Durable.Octree.Normals3fReference, out object? o) && ((PersistentRef)o).Id != GuidEmptyString) { - if (PersistentRefs.TryGetValue(Durable.Octree.Normals3fReference, out object? o) && ((PersistentRef)o).Id != GuidEmptyString) - { - return (PersistentRef)o; - } - else if (Data.TryGetValue(Durable.Octree.Normals3f, out o)) - { - var xs = (V3f[])o; - return new PersistentRef(Guid.Empty, xs); - } - else - { - return null; - } + return (PersistentRef)o; + } + else if (Data.TryGetValue(Durable.Octree.Normals3f, out o)) + { + var xs = (V3f[])o; + return new PersistentRef(Guid.Empty, xs); + } + else + { + return null; } } + } - #endregion + #endregion - #region Intensities + #region Intensities - /// - [JsonIgnore] + /// + [JsonIgnore] - [MemberNotNullWhen(true, nameof(Intensities))] - public bool HasIntensities => - Data.ContainsKey(Durable.Octree.Intensities1iReference) || - Data.ContainsKey(Durable.Octree.Intensities1i) - ; + [MemberNotNullWhen(true, nameof(Intensities))] + public bool HasIntensities => + Data.ContainsKey(Durable.Octree.Intensities1iReference) || + Data.ContainsKey(Durable.Octree.Intensities1i) + ; - /// - [JsonIgnore] - public Guid? IntensitiesId => Data.TryGetValue(Durable.Octree.Intensities1iReference, out var id) ? (Guid?)id : null; + /// + [JsonIgnore] + public Guid? IntensitiesId => Data.TryGetValue(Durable.Octree.Intensities1iReference, out var id) ? (Guid?)id : null; - /// - [JsonIgnore] - public PersistentRef? Intensities + /// + [JsonIgnore] + public PersistentRef? Intensities + { + get { - get + if (PersistentRefs.TryGetValue(Durable.Octree.Intensities1iReference, out object? o) && ((PersistentRef)o).Id != GuidEmptyString) { - if (PersistentRefs.TryGetValue(Durable.Octree.Intensities1iReference, out object? o) && ((PersistentRef)o).Id != GuidEmptyString) - { - return (PersistentRef)o; - } - else if (Data.TryGetValue(Durable.Octree.Intensities1i, out o)) - { - var xs = (int[])o; - return new PersistentRef(Guid.Empty, xs); - } - else - { - return null; - } + return (PersistentRef)o; + } + else if (Data.TryGetValue(Durable.Octree.Intensities1i, out o)) + { + var xs = (int[])o; + return new PersistentRef(Guid.Empty, xs); + } + else + { + return null; } } + } - #endregion + #endregion - #region Classifications + #region Classifications - /// - [JsonIgnore] + /// + [JsonIgnore] - [MemberNotNullWhen(true, nameof(Classifications))] - public bool HasClassifications => - Data.ContainsKey(Durable.Octree.Classifications1bReference) || - Data.ContainsKey(Durable.Octree.Classifications1b) - ; + [MemberNotNullWhen(true, nameof(Classifications))] + public bool HasClassifications => + Data.ContainsKey(Durable.Octree.Classifications1bReference) || + Data.ContainsKey(Durable.Octree.Classifications1b) + ; - /// - [JsonIgnore] - public Guid? ClassificationsId => Data.TryGetValue(Durable.Octree.Classifications1bReference, out var id) ? (Guid?)id : null; + /// + [JsonIgnore] + public Guid? ClassificationsId => Data.TryGetValue(Durable.Octree.Classifications1bReference, out var id) ? (Guid?)id : null; - /// - [JsonIgnore] - public PersistentRef? Classifications + /// + [JsonIgnore] + public PersistentRef? Classifications + { + get { - get + if (PersistentRefs.TryGetValue(Durable.Octree.Classifications1bReference, out var o) && ((PersistentRef)o).Id != GuidEmptyString) { - if (PersistentRefs.TryGetValue(Durable.Octree.Classifications1bReference, out var o) && ((PersistentRef)o).Id != GuidEmptyString) - { - return (PersistentRef?)o; - } - else if (Data.TryGetValue(Durable.Octree.Classifications1b, out o)) - { - var xs = (byte[])o; - return new PersistentRef(Guid.Empty, xs); - } - else - { - return null; - } + return (PersistentRef?)o; + } + else if (Data.TryGetValue(Durable.Octree.Classifications1b, out o)) + { + var xs = (byte[])o; + return new PersistentRef(Guid.Empty, xs); + } + else + { + return null; } } + } - #endregion + #endregion - #region PartIndices - - /// - /// True if this node has a PartIndexRange. - /// - [JsonIgnore] - [MemberNotNullWhen(true, nameof(PartIndexRange))] - public bool HasPartIndexRange => Data.ContainsKey(Durable.Octree.PartIndexRange); - - /// - /// Octree. Min and max part index in octree. - /// - public Range1i? PartIndexRange => Data.TryGetValue(Durable.Octree.PartIndexRange, out var o) ? (Range1i)o : null; - - /// - /// True if this node has part indices. - /// - [JsonIgnore] - [MemberNotNullWhen(true, nameof(PartIndices))] - public bool HasPartIndices => - Data.ContainsKey(Durable.Octree.PerCellPartIndex1i ) || - Data.ContainsKey(Durable.Octree.PerCellPartIndex1ui) || - Data.ContainsKey(Durable.Octree.PerPointPartIndex1b) || - Data.ContainsKey(Durable.Octree.PerPointPartIndex1bReference) || - Data.ContainsKey(Durable.Octree.PerPointPartIndex1s) || - Data.ContainsKey(Durable.Octree.PerPointPartIndex1sReference) || - Data.ContainsKey(Durable.Octree.PerPointPartIndex1i) || - Data.ContainsKey(Durable.Octree.PerPointPartIndex1iReference) - ; + #region PartIndices + + /// + /// True if this node has a PartIndexRange. + /// + [JsonIgnore] + [MemberNotNullWhen(true, nameof(PartIndexRange))] + public bool HasPartIndexRange => Data.ContainsKey(Durable.Octree.PartIndexRange); + + /// + /// Octree. Min and max part index in octree. + /// + public Range1i? PartIndexRange => Data.TryGetValue(Durable.Octree.PartIndexRange, out var o) ? (Range1i)o : null; + + /// + /// True if this node has part indices. + /// + [JsonIgnore] + [MemberNotNullWhen(true, nameof(PartIndices))] + public bool HasPartIndices => + Data.ContainsKey(Durable.Octree.PerCellPartIndex1i ) || + Data.ContainsKey(Durable.Octree.PerCellPartIndex1ui) || + Data.ContainsKey(Durable.Octree.PerPointPartIndex1b) || + Data.ContainsKey(Durable.Octree.PerPointPartIndex1bReference) || + Data.ContainsKey(Durable.Octree.PerPointPartIndex1s) || + Data.ContainsKey(Durable.Octree.PerPointPartIndex1sReference) || + Data.ContainsKey(Durable.Octree.PerPointPartIndex1i) || + Data.ContainsKey(Durable.Octree.PerPointPartIndex1iReference) + ; - /// - /// Octree. Per-point or per-cell part indices. - /// - [JsonIgnore] - public object? PartIndices + /// + /// Octree. Per-point or per-cell part indices. + /// + [JsonIgnore] + public object? PartIndices + { + get { - get - { - if (Data.TryGetValue(Durable.Octree.PerCellPartIndex1i, out var pcpi)) return pcpi; - else if (Data.TryGetValue(Durable.Octree.PerCellPartIndex1ui, out var pcpui)) return pcpui; - else if (Data.TryGetValue(Durable.Octree.PerPointPartIndex1b, out var pppi1b)) return pppi1b; - else if (Data.TryGetValue(Durable.Octree.PerPointPartIndex1bReference, out var key1b)) return Storage.GetByteArray((Guid)key1b); - else if (Data.TryGetValue(Durable.Octree.PerPointPartIndex1s, out var pppi1s)) return pppi1s; - else if (Data.TryGetValue(Durable.Octree.PerPointPartIndex1sReference, out var key1s)) return Storage.GetInt16Array((Guid)key1s); - else if (Data.TryGetValue(Durable.Octree.PerPointPartIndex1i, out var pppi1i)) return pppi1i; - else if (Data.TryGetValue(Durable.Octree.PerPointPartIndex1iReference, out var key1i)) return Storage.GetIntArray((Guid)key1i); - else return null; - } + if (Data.TryGetValue(Durable.Octree.PerCellPartIndex1i, out var pcpi)) return pcpi; + else if (Data.TryGetValue(Durable.Octree.PerCellPartIndex1ui, out var pcpui)) return pcpui; + else if (Data.TryGetValue(Durable.Octree.PerPointPartIndex1b, out var pppi1b)) return pppi1b; + else if (Data.TryGetValue(Durable.Octree.PerPointPartIndex1bReference, out var key1b)) return Storage.GetByteArray((Guid)key1b); + else if (Data.TryGetValue(Durable.Octree.PerPointPartIndex1s, out var pppi1s)) return pppi1s; + else if (Data.TryGetValue(Durable.Octree.PerPointPartIndex1sReference, out var key1s)) return Storage.GetInt16Array((Guid)key1s); + else if (Data.TryGetValue(Durable.Octree.PerPointPartIndex1i, out var pppi1i)) return pppi1i; + else if (Data.TryGetValue(Durable.Octree.PerPointPartIndex1iReference, out var key1i)) return Storage.GetIntArray((Guid)key1i); + else return null; } + } - [JsonIgnore] - public Guid? PerPointPartIndex1bId => Data.TryGetValue(Durable.Octree.PerPointPartIndex1bReference, out var id) ? (Guid?)id : null; + [JsonIgnore] + public Guid? PerPointPartIndex1bId => Data.TryGetValue(Durable.Octree.PerPointPartIndex1bReference, out var id) ? (Guid?)id : null; - [JsonIgnore] - public Guid? PerPointPartIndex1sId => Data.TryGetValue(Durable.Octree.PerPointPartIndex1sReference, out var id) ? (Guid?)id : null; + [JsonIgnore] + public Guid? PerPointPartIndex1sId => Data.TryGetValue(Durable.Octree.PerPointPartIndex1sReference, out var id) ? (Guid?)id : null; - [JsonIgnore] - public Guid? PerPointPartIndex1iId => Data.TryGetValue(Durable.Octree.PerPointPartIndex1iReference, out var id) ? (Guid?)id : null; + [JsonIgnore] + public Guid? PerPointPartIndex1iId => Data.TryGetValue(Durable.Octree.PerPointPartIndex1iReference, out var id) ? (Guid?)id : null; - /// - /// Get per-point part indices as an int array (regardless of internal representation). - /// Returns false if node has no part indices. - /// - public bool TryGetPartIndices([NotNullWhen(true)] out int[]? result) - { - result = PartIndexUtils.Expand(PartIndices, PointCountCell); - return result != null; - } + /// + /// Get per-point part indices as an int array (regardless of internal representation). + /// Returns false if node has no part indices. + /// + public bool TryGetPartIndices([NotNullWhen(true)] out int[]? result) + { + result = PartIndexUtils.Expand(PartIndices, PointCountCell); + return result != null; + } - #endregion + #endregion - #region Velocities + #region Velocities - /// - /// Deprecated. Always returns false. Use custom attributes instead. - /// - [Obsolete("Use custom attributes instead.")] + /// + /// Deprecated. Always returns false. Use custom attributes instead. + /// + [Obsolete("Use custom attributes instead.")] #pragma warning disable CS0618 // Type or member is obsolete - [MemberNotNullWhen(true, nameof(Velocities))] + [MemberNotNullWhen(true, nameof(Velocities))] #pragma warning restore CS0618 // Type or member is obsolete - public bool HasVelocities => false; + public bool HasVelocities => false; + + /// + /// Deprecated. Always returns null. Use custom attributes instead. + /// + [Obsolete("Use custom attributes instead.")] + public PersistentRef? Velocities => null; - /// - /// Deprecated. Always returns null. Use custom attributes instead. - /// - [Obsolete("Use custom attributes instead.")] - public PersistentRef? Velocities => null; + #endregion - #endregion + #region KdTree - #region KdTree + /// + [JsonIgnore] - /// - [JsonIgnore] + [MemberNotNullWhen(true, nameof(KdTree))] + public bool HasKdTree => + Data.ContainsKey(Durable.Octree.PointRkdTreeFDataReference) || + Data.ContainsKey(Durable.Octree.PointRkdTreeDDataReference) + ; - [MemberNotNullWhen(true, nameof(KdTree))] - public bool HasKdTree => - Data.ContainsKey(Durable.Octree.PointRkdTreeFDataReference) || - Data.ContainsKey(Durable.Octree.PointRkdTreeDDataReference) - ; + /// + [JsonIgnore] + public Guid? KdTreeId => + Data.TryGetValue(Durable.Octree.PointRkdTreeFDataReference, out var id) + ? (Guid?)id + : (Data.TryGetValue(Durable.Octree.PointRkdTreeDDataReference, out var id2) ? (Guid?)id2 : null) + ; - /// - [JsonIgnore] - public Guid? KdTreeId => - Data.TryGetValue(Durable.Octree.PointRkdTreeFDataReference, out var id) - ? (Guid?)id - : (Data.TryGetValue(Durable.Octree.PointRkdTreeDDataReference, out var id2) ? (Guid?)id2 : null) - ; + /// + [JsonIgnore] + public PersistentRef>? KdTree => + PersistentRefs.TryGetValue(Durable.Octree.PointRkdTreeFDataReference, out var x) + ? (PersistentRef>?)x + : null + ; - /// - [JsonIgnore] - public PersistentRef>? KdTree => - PersistentRefs.TryGetValue(Durable.Octree.PointRkdTreeFDataReference, out var x) - ? (PersistentRef>?)x - : null - ; + #endregion - #endregion + #region CentroidLocal - #region CentroidLocal + /// + public bool HasCentroidLocal => PointCountCell > 0 || Data.ContainsKey(Durable.Octree.PositionsLocal3fCentroid); - /// - public bool HasCentroidLocal => PointCountCell > 0 || Data.ContainsKey(Durable.Octree.PositionsLocal3fCentroid); + private V3f? m_centroid; + private float? m_centroidStdDev; - private V3f? m_centroid; - private float? m_centroidStdDev; + private static (V3f, float) ComputeStuffs(V3f[] ps) + { + if(ps.Length <= 0) return (V3f.NaN, 0); + if(ps.Length <= 1) return (ps[0], 0); - private static (V3f, float) ComputeStuffs(V3f[] ps) + var sum = V3d.Zero; + var sumSq = 0.0; + foreach(var p in ps) { - if(ps.Length <= 0) return (V3f.NaN, 0); - if(ps.Length <= 1) return (ps[0], 0); - - var sum = V3d.Zero; - var sumSq = 0.0; - foreach(var p in ps) - { - var pd = (V3d)p; - //Debug.Assert(!pd.IsNaN); - sum += pd; - sumSq += pd.LengthSquared; - } + var pd = (V3d)p; + //Debug.Assert(!pd.IsNaN); + sum += pd; + sumSq += pd.LengthSquared; + } - var avg = sum / ps.Length; - var avgSq = sumSq / (ps.Length - 1); - var factor = (double)ps.Length / (ps.Length - 1); + var avg = sum / ps.Length; + var avgSq = sumSq / (ps.Length - 1); + var factor = (double)ps.Length / (ps.Length - 1); - var variance = avgSq - factor * avg.LengthSquared; + var variance = avgSq - factor * avg.LengthSquared; - return ((V3f)avg, (float)Fun.Sqrt(variance)); - } + return ((V3f)avg, (float)Fun.Sqrt(variance)); + } - /// - public V3f CentroidLocal + /// + public V3f CentroidLocal + { + get { - get + if (m_centroid != null) return m_centroid.Value; + if (Data.TryGetValue(Durable.Octree.PositionsLocal3fCentroid, out var local)) { - if (m_centroid != null) return m_centroid.Value; - if (Data.TryGetValue(Durable.Octree.PositionsLocal3fCentroid, out var local)) - { - m_centroid = (V3f)local; - return m_centroid.Value; - } - var (centroid, stddev) = ComputeStuffs(Positions.Value); - m_centroid = centroid; - m_centroidStdDev = stddev; + m_centroid = (V3f)local; return m_centroid.Value; } + var (centroid, stddev) = ComputeStuffs(Positions.Value); + m_centroid = centroid; + m_centroidStdDev = stddev; + return m_centroid.Value; } + } - /// - public bool HasCentroidLocalStdDev => PointCountCell > 0 || Data.ContainsKey(Durable.Octree.PositionsLocal3fDistToCentroidStdDev); + /// + public bool HasCentroidLocalStdDev => PointCountCell > 0 || Data.ContainsKey(Durable.Octree.PositionsLocal3fDistToCentroidStdDev); - /// - public float CentroidLocalStdDev + /// + public float CentroidLocalStdDev + { + get { - get + if (m_centroidStdDev != null) return m_centroidStdDev.Value; + if (Data.TryGetValue(Durable.Octree.PositionsLocal3fDistToCentroidStdDev, out var local)) { - if (m_centroidStdDev != null) return m_centroidStdDev.Value; - if (Data.TryGetValue(Durable.Octree.PositionsLocal3fDistToCentroidStdDev, out var local)) - { - m_centroidStdDev = (float)local; - return m_centroidStdDev.Value; - } - var (centroid, stddev) = ComputeStuffs(Positions.Value); - m_centroid = centroid; - m_centroidStdDev = stddev; + m_centroidStdDev = (float)local; return m_centroidStdDev.Value; } + var (centroid, stddev) = ComputeStuffs(Positions.Value); + m_centroid = centroid; + m_centroidStdDev = stddev; + return m_centroidStdDev.Value; } + } - #endregion - - #region TreeDepth + #endregion - /// - public bool HasMinTreeDepth => Data.ContainsKey(Durable.Octree.MinTreeDepth); + #region TreeDepth /// - public int MinTreeDepth => (int)Data.Get(Durable.Octree.MinTreeDepth); + public bool HasMinTreeDepth => Data.ContainsKey(Durable.Octree.MinTreeDepth); - /// - public bool HasMaxTreeDepth => Data.ContainsKey(Durable.Octree.MaxTreeDepth); + /// + public int MinTreeDepth => (int)Data.Get(Durable.Octree.MinTreeDepth); - /// - public int MaxTreeDepth => (int)Data.Get(Durable.Octree.MaxTreeDepth); + /// + public bool HasMaxTreeDepth => Data.ContainsKey(Durable.Octree.MaxTreeDepth); - #endregion + /// + public int MaxTreeDepth => (int)Data.Get(Durable.Octree.MaxTreeDepth); - #region PointDistance + #endregion - /// - public bool HasPointDistanceAverage => Data.ContainsKey(Durable.Octree.AveragePointDistance); + #region PointDistance - /// - public float PointDistanceAverage => (Data.TryGetValue(Durable.Octree.AveragePointDistance, out var value) && value is float x) ? x : -1.0f; + /// + public bool HasPointDistanceAverage => Data.ContainsKey(Durable.Octree.AveragePointDistance); - /// - public bool HasPointDistanceStandardDeviation => Data.ContainsKey(Durable.Octree.AveragePointDistanceStdDev); + /// + public float PointDistanceAverage => (Data.TryGetValue(Durable.Octree.AveragePointDistance, out var value) && value is float x) ? x : -1.0f; - /// - public float PointDistanceStandardDeviation => (Data.TryGetValue(Durable.Octree.AveragePointDistanceStdDev, out var value) && value is float x) ? x : -1.0f; + /// + public bool HasPointDistanceStandardDeviation => Data.ContainsKey(Durable.Octree.AveragePointDistanceStdDev); - #endregion + /// + public float PointDistanceStandardDeviation => (Data.TryGetValue(Durable.Octree.AveragePointDistanceStdDev, out var value) && value is float x) ? x : -1.0f; - /// - /// Subnodes (8), or null if leaf. - /// - [JsonIgnore] - public readonly PersistentRef[]? Subnodes; - - /// - /// Center of this node's cell. - /// - [JsonIgnore] - public readonly V3d Center; - - /// - /// Corners of this node's cell. - /// - [JsonIgnore] - public readonly V3d[] Corners; - - /// - /// Node has no subnodes. - /// - [JsonIgnore] - public bool IsLeaf => SubnodeIds == null; - - /// - /// Node has subnodes. - /// - [JsonIgnore] - public bool IsNotLeaf => SubnodeIds != null; - - /// - /// Gets whether this node is centered at the origin. - /// - [JsonIgnore] - public bool IsCenteredAtOrigin => Cell.IsCenteredAtOrigin; - - /// - /// Gets number of subnodes. - /// - [JsonIgnore] - public int SubnodeCount => SubnodeIds == null ? 0 : SubnodeIds.Count(x => x != null); + #endregion - #endregion + /// + /// Subnodes (8), or null if leaf. + /// + [JsonIgnore] + public readonly PersistentRef[]? Subnodes; - #region Immutable updates (With...) + /// + /// Center of this node's cell. + /// + [JsonIgnore] + public readonly V3d Center; - /// - /// Returns new node with replaced subnodes. - /// Attention: - /// All node properties (except Cell, BoundingBoxExactGlobal, PointCountTreeLeafs, and PartIndexRange) are removed, because they would no longer be valid for new subnode data. - /// Use LodExtensions.GenerateLod to recompute these properties. - /// - public IPointCloudNode WithSubNodes(IPointCloudNode?[] subnodes) - { - if (subnodes == null) throw new ArgumentNullException(nameof(subnodes)); + /// + /// Corners of this node's cell. + /// + [JsonIgnore] + public readonly V3d[] Corners; + + /// + /// Node has no subnodes. + /// + [JsonIgnore] + public bool IsLeaf => SubnodeIds == null; + + /// + /// Node has subnodes. + /// + [JsonIgnore] + public bool IsNotLeaf => SubnodeIds != null; + + /// + /// Gets whether this node is centered at the origin. + /// + [JsonIgnore] + public bool IsCenteredAtOrigin => Cell.IsCenteredAtOrigin; + + /// + /// Gets number of subnodes. + /// + [JsonIgnore] + public int SubnodeCount => SubnodeIds == null ? 0 : SubnodeIds.Count(x => x != null); + + #endregion + + #region Immutable updates (With...) + + /// + /// Returns new node with replaced subnodes. + /// Attention: + /// All node properties (except Cell, BoundingBoxExactGlobal, PointCountTreeLeafs, and PartIndexRange) are removed, because they would no longer be valid for new subnode data. + /// Use LodExtensions.GenerateLod to recompute these properties. + /// + public IPointCloudNode WithSubNodes(IPointCloudNode?[] subnodes) + { + if (subnodes == null) throw new ArgumentNullException(nameof(subnodes)); #if DEBUG - for (var i = 0; i < 8; i++) + for (var i = 0; i < 8; i++) + { + var sn = subnodes[i]; if (sn == null) continue; + if (sn.Cell.Exponent != this.Cell.Exponent - 1) { - var sn = subnodes[i]; if (sn == null) continue; - if (sn.Cell.Exponent != this.Cell.Exponent - 1) - { - throw new InvalidOperationException("Invariant c79cd9a4-3e44-46c8-9a7f-5f7e09627f1a."); - } + throw new InvalidOperationException("Invariant c79cd9a4-3e44-46c8-9a7f-5f7e09627f1a."); } + } #endif - var pointCountTree = subnodes.Sum(x => x?.PointCountTree); - var bbExactGlobal = new Box3d(subnodes.Where(x => x != null).Select(x => x!.BoundingBoxExactGlobal)); + var pointCountTree = subnodes.Sum(x => x?.PointCountTree); + var bbExactGlobal = new Box3d(subnodes.Where(x => x != null).Select(x => x!.BoundingBoxExactGlobal)); - var data = ImmutableDictionary.Empty - .Add(Durable.Octree.NodeId, Guid.NewGuid()) - .Add(Durable.Octree.Cell, Cell) - .Add(Durable.Octree.BoundingBoxExactGlobal, bbExactGlobal) - .Add(Durable.Octree.PointCountTreeLeafs, pointCountTree ?? 0L) - .Add(Durable.Octree.SubnodesGuids, subnodes.Map(x => x?.Id ?? Guid.Empty)) - ; + var data = ImmutableDictionary.Empty + .Add(Durable.Octree.NodeId, Guid.NewGuid()) + .Add(Durable.Octree.Cell, Cell) + .Add(Durable.Octree.BoundingBoxExactGlobal, bbExactGlobal) + .Add(Durable.Octree.PointCountTreeLeafs, pointCountTree ?? 0L) + .Add(Durable.Octree.SubnodesGuids, subnodes.Map(x => x?.Id ?? Guid.Empty)) + ; - // part indices ... + // part indices ... + { + Range1i? qsRange = null; + for (var i = 0; i < 8; i++) { - Range1i? qsRange = null; - for (var i = 0; i < 8; i++) - { - var subNode = subnodes[i]; if (subNode == null) continue; - qsRange = PartIndexUtils.MergeRanges(qsRange, subNode.PartIndexRange); - } - if (qsRange != null) data = data.Add(Durable.Octree.PartIndexRange, qsRange); + var subNode = subnodes[i]; if (subNode == null) continue; + qsRange = PartIndexUtils.MergeRanges(qsRange, subNode.PartIndexRange); } + if (qsRange != null) data = data.Add(Durable.Octree.PartIndexRange, qsRange); + } - if (IsTemporaryImportNode) data = data.Add(TemporaryImportNode, 0); + if (IsTemporaryImportNode) data = data.Add(TemporaryImportNode, 0); - var result = new PointSetNode(data, this.Storage, writeToStore: true); - return result; - } + var result = new PointSetNode(data, this.Storage, writeToStore: true); + return result; + } - /// - /// Returns new node (with new id) with added/replaced data. - /// Node is NOT written to store. - /// Call WriteToStore on the result if you want this. - /// - public PointSetNode With(IReadOnlyDictionary replacements) - { - if (replacements.ContainsKey(Durable.Octree.NodeId)) - throw new InvalidOperationException($"Node id must not be assigned manually. Error 20855582-cbbc-496a-8730-fa49d7af8f5b."); + /// + /// Returns new node (with new id) with added/replaced data. + /// Node is NOT written to store. + /// Call WriteToStore on the result if you want this. + /// + public PointSetNode With(IReadOnlyDictionary replacements) + { + if (replacements.ContainsKey(Durable.Octree.NodeId)) + throw new InvalidOperationException($"Node id must not be assigned manually. Error 20855582-cbbc-496a-8730-fa49d7af8f5b."); - var data = Data - .Remove(Durable.Octree.NodeId) - .Add(Durable.Octree.NodeId, Guid.NewGuid()) - ; - foreach (var kv in replacements) data = data.SetItem(kv.Key, kv.Value); + var data = Data + .Remove(Durable.Octree.NodeId) + .Add(Durable.Octree.NodeId, Guid.NewGuid()) + ; + foreach (var kv in replacements) data = data.SetItem(kv.Key, kv.Value); - return new PointSetNode(data, Storage, false); - } + return new PointSetNode(data, Storage, false); + } - /// - /// Returns new node with removed data. - /// - public PointSetNode Without(params Durable.Def[] defs) - { - var data = Data - .RemoveRange(defs) - .Remove(Durable.Octree.NodeId) - .Add(Durable.Octree.NodeId, Guid.NewGuid()) - ; - return new PointSetNode(data, Storage, false); - } + /// + /// Returns new node with removed data. + /// + public PointSetNode Without(params Durable.Def[] defs) + { + var data = Data + .RemoveRange(defs) + .Remove(Durable.Octree.NodeId) + .Add(Durable.Octree.NodeId, Guid.NewGuid()) + ; + return new PointSetNode(data, Storage, false); + } - /// - /// Adds kd-tree to temporary import node. - /// - public PointSetNode WithComputedKdTree() - { - if (!IsTemporaryImportNode) - throw new InvalidOperationException("Only allowed on temporary import nodes. Invariant de875e86-4c30-44d4-9e53-cbaf4e24854f."); + /// + /// Adds kd-tree to temporary import node. + /// + public PointSetNode WithComputedKdTree() + { + if (!IsTemporaryImportNode) + throw new InvalidOperationException("Only allowed on temporary import nodes. Invariant de875e86-4c30-44d4-9e53-cbaf4e24854f."); - if (HasKdTree) return this; + if (HasKdTree) return this; - var kdId = ComputeAndStoreKdTree(Storage, Positions.Value); - return With(ImmutableDictionary.Empty.Add(Durable.Octree.PointRkdTreeFDataReference, kdId)); - } + var kdId = ComputeAndStoreKdTree(Storage, Positions.Value); + return With(ImmutableDictionary.Empty.Add(Durable.Octree.PointRkdTreeFDataReference, kdId)); + } - /// - /// Computes and stores kd-tree from given points. - /// Returns id of stored kd-tree. - /// - private static Guid ComputeAndStoreKdTree(Storage storage, V3f[] ps) - { - var kdTree = new PointRkdTreeF( - 3, ps.Length, ps, - (xs, i) => xs[(int)i], (v, i) => (float)v[i], - (a, b) => Vec.Distance(a, b), (i, a, b) => b - a, - (a, b, c) => Vec.DistanceToLine(a, b, c), Fun.Lerp, 0.000001f - ); - Guid kdId = Guid.NewGuid(); - storage.Add(kdId, kdTree.Data); - return kdId; - } + /// + /// Computes and stores kd-tree from given points. + /// Returns id of stored kd-tree. + /// + private static Guid ComputeAndStoreKdTree(Storage storage, V3f[] ps) + { + var kdTree = new PointRkdTreeF( + 3, ps.Length, ps, + (xs, i) => xs[(int)i], (v, i) => (float)v[i], + (a, b) => Vec.Distance(a, b), (i, a, b) => b - a, + (a, b, c) => Vec.DistanceToLine(a, b, c), Fun.Lerp, 0.000001f + ); + Guid kdId = Guid.NewGuid(); + storage.Add(kdId, kdTree.Data); + return kdId; + } - #endregion + #endregion - #region Durable codec + #region Durable codec - /// - /// - public byte[] Encode() => Aardvark.Data.Codec.Serialize(Durable.Octree.Node, Data); + /// + /// + public byte[] Encode() => Aardvark.Data.Codec.Serialize(Durable.Octree.Node, Data); - /// - /// - public static PointSetNode Decode(Storage storage, byte[] buffer) - { - var (def, o) = Aardvark.Data.Codec.Deserialize(buffer); - if (def != Durable.Octree.Node) throw new InvalidOperationException("Invariant 5cc4cbfe-07c8-4b92-885d-b1d397210e41."); - var data = (ImmutableDictionary)o; - return new PointSetNode(data, storage, false); - } + /// + /// + public static PointSetNode Decode(Storage storage, byte[] buffer) + { + var (def, o) = Aardvark.Data.Codec.Deserialize(buffer); + if (def != Durable.Octree.Node) throw new InvalidOperationException("Invariant 5cc4cbfe-07c8-4b92-885d-b1d397210e41."); + var data = (ImmutableDictionary)o; + return new PointSetNode(data, storage, false); + } - #endregion + #endregion - #region IPointCloudNode + #region IPointCloudNode - Guid IPointCloudNode.Id => Id; + Guid IPointCloudNode.Id => Id; - Cell IPointCloudNode.Cell => Cell; + Cell IPointCloudNode.Cell => Cell; - V3d IPointCloudNode.Center => Center; + V3d IPointCloudNode.Center => Center; - long IPointCloudNode.PointCountTree => PointCountTree; + long IPointCloudNode.PointCountTree => PointCountTree; - /// - public bool Has(Durable.Def what) => Data.ContainsKey(what); + /// + public bool Has(Durable.Def what) => Data.ContainsKey(what); - /// - public bool TryGetValue(Durable.Def what, [NotNullWhen(true)]out object? o) => Data.TryGetValue(what, out o); + /// + public bool TryGetValue(Durable.Def what, [NotNullWhen(true)]out object? o) => Data.TryGetValue(what, out o); - /// - public IReadOnlyDictionary Properties => Data; + /// + public IReadOnlyDictionary Properties => Data; - Storage IPointCloudNode.Storage => Storage; + Storage IPointCloudNode.Storage => Storage; - /// - public bool IsMaterialized => true; + /// + public bool IsMaterialized => true; - public bool IsEmpty => Id == Guid.Empty; + public bool IsEmpty => Id == Guid.Empty; - /// - public IPointCloudNode Materialize() => this; + /// + public IPointCloudNode Materialize() => this; - PersistentRef[]? IPointCloudNode.Subnodes => Subnodes; + PersistentRef[]? IPointCloudNode.Subnodes => Subnodes; - public Box3d BoundingBoxApproximate + public Box3d BoundingBoxApproximate + { + get { - get + var cellBounds = Cell.BoundingBox; + if(HasBoundingBoxExactGlobal) { - var cellBounds = Cell.BoundingBox; - if(HasBoundingBoxExactGlobal) - { - var be = BoundingBoxExactGlobal; - if(be != cellBounds) return be; - } + var be = BoundingBoxExactGlobal; + if(be != cellBounds) return be; + } - if (HasBoundingBoxExactLocal) - { - return (Box3d)BoundingBoxExactLocal + Center; - } + if (HasBoundingBoxExactLocal) + { + return (Box3d)BoundingBoxExactLocal + Center; + } - if(HasPositions) - { - var ps = Positions.Value!; - if (ps.Length > 0) return (Box3d)(new Box3f(ps)) + Center; - } + if(HasPositions) + { + var ps = Positions.Value!; + if (ps.Length > 0) return (Box3d)(new Box3f(ps)) + Center; + } - return cellBounds; - } - } + return cellBounds; + } + } - IPointCloudNode IPointCloudNode.With(IReadOnlyDictionary replacements) => With(replacements); + IPointCloudNode IPointCloudNode.With(IReadOnlyDictionary replacements) => With(replacements); - #endregion - } + #endregion } diff --git a/src/Aardvark.Geometry.PointSet/Octrees/PointsNearObject.cs b/src/Aardvark.Geometry.PointSet/Octrees/PointsNearObject.cs index a89c38a1..e9c39243 100644 --- a/src/Aardvark.Geometry.PointSet/Octrees/PointsNearObject.cs +++ b/src/Aardvark.Geometry.PointSet/Octrees/PointsNearObject.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -15,169 +15,157 @@ You should have received a copy of the GNU Affero General Public License using Aardvark.Base; using Aardvark.Base.Sorting; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public class CellQueryResult(IPointCloudNode cell, bool isFullyInside) { /// /// - public class CellQueryResult - { - /// - /// - public static CellQueryResult Empty = new(null!, false); - - /// - /// - public IPointCloudNode Cell { get; } - - /// - /// - public bool IsFullyInside { get; } - - /// - /// Cell == null. - /// - public bool IsEmpty => Cell == null; - - /// - /// - public CellQueryResult(IPointCloudNode cell, bool isFullyInside) - { - Cell = cell; - IsFullyInside = isFullyInside; - } - } + public static CellQueryResult Empty = new(null!, false); /// /// - public class PointsNearObject - { - /// - public static readonly PointsNearObject Empty = new( - default!, 0.0, - Array.Empty(), Array.Empty(), Array.Empty(), - Array.Empty(), Array.Empty(), Array.Empty(), - Array.Empty() - ); + public IPointCloudNode Cell { get; } = cell; - /// - public T Object { get; } + /// + /// + public bool IsFullyInside { get; } = isFullyInside; - /// - public double MaxDistance { get; } + /// + /// Cell == null. + /// + public bool IsEmpty => Cell == null; +} - /// - public V3d[] Positions { get; } +/// +/// +public class PointsNearObject +{ + /// + public static readonly PointsNearObject Empty = new( + default!, 0.0, [], [], [], [], [], [], [] + ); - /// - public C4b[]? Colors { get; } + /// + public T Object { get; } - /// - public V3f[]? Normals { get; } + /// + public double MaxDistance { get; } - /// - public int[]? Intensities { get; } + /// + public V3d[] Positions { get; } - /// - public int[]? PartIndices { get; } + /// + public C4b[]? Colors { get; } - /// - public byte[]? Classifications { get; } + /// + public V3f[]? Normals { get; } - /// - public double[]? Distances { get; } + /// + public int[]? Intensities { get; } - /// - public PointsNearObject(T obj, double maxDistance, V3d[] positions, C4b[]? colors, V3f[]? normals, int[]? intensities, int[]? partIndices, byte[]? classifications, double[]? distances) - { - if (maxDistance < 0.0) throw new ArgumentOutOfRangeException(nameof(maxDistance), $"Parameter 'maxDistance' must not be less than 0.0, but is {maxDistance}."); - - Object = obj; - MaxDistance = maxDistance; - Positions = positions ?? throw new ArgumentNullException(nameof(positions)); - Colors = colors; - Normals = normals; - Intensities = intensities; - PartIndices = partIndices; - Classifications = classifications; - Distances = distances; - } + /// + public int[]? PartIndices { get; } - /// - /// - public int Count => Positions.Length; + /// + public byte[]? Classifications { get; } - /// - /// - public bool IsEmpty => Positions.Length == 0; + /// + public double[]? Distances { get; } - /// - /// Returns this PointsNearObject merged with other PointsNearObject. - /// - public PointsNearObject Merge(PointsNearObject other, int maxCount) - { - if (maxCount < 0) throw new ArgumentOutOfRangeException(nameof(maxCount)); - if (maxCount == 0) return Empty; - if (other == null || other.IsEmpty) return this; - - var merged = new PointsNearObject(Object, - Math.Max(MaxDistance, other.MaxDistance), - Positions.Append(other.Positions)!, - Colors?.Append(other.Colors), - Normals.Append(other.Normals), - Intensities.Append(other.Intensities), - PartIndices.Append(other.PartIndices), - Classifications.Append(other.Classifications), - Distances.Append(other.Distances) - ); - - if (Count + other.Count > maxCount) - { - // take 'maxCount' nearest items - merged = merged.OrderedByDistanceAscending().Take(maxCount); - } - - return merged; - } + /// + public PointsNearObject(T obj, double maxDistance, V3d[] positions, C4b[]? colors, V3f[]? normals, int[]? intensities, int[]? partIndices, byte[]? classifications, double[]? distances) + { + if (maxDistance < 0.0) throw new ArgumentOutOfRangeException(nameof(maxDistance), $"Parameter 'maxDistance' must not be less than 0.0, but is {maxDistance}."); + + Object = obj; + MaxDistance = maxDistance; + Positions = positions ?? throw new ArgumentNullException(nameof(positions)); + Colors = colors; + Normals = normals; + Intensities = intensities; + PartIndices = partIndices; + Classifications = classifications; + Distances = distances; + } + + /// + /// + public int Count => Positions.Length; + + /// + /// + public bool IsEmpty => Positions.Length == 0; - /// - /// Returns PointsNearObject ordered by ascending distance. - /// - public PointsNearObject OrderedByDistanceAscending() => Reordered(Distances.CreatePermutationQuickSortAscending()); - - /// - /// Returns PointsNearObject ordered by descending distance. - /// - public PointsNearObject OrderedByDistanceDescending() => Reordered(Distances.CreatePermutationQuickSortDescending()); - - /// - /// Returns PointsNearObject ordered by descending distance. - /// - public PointsNearObject Reordered(int[] ia) => new( - Object, MaxDistance, - Positions.Reordered(ia), - Colors?.Length > 0 ? Colors.Reordered(ia) : Colors, - Normals?.Length > 0 ? Normals.Reordered(ia) : Normals, - Intensities?.Length > 0 ? Intensities.Reordered(ia) : Intensities, - PartIndices?.Length > 0 ? PartIndices.Reordered(ia) : PartIndices, - Classifications?.Length > 0 ? Classifications.Reordered(ia) : Classifications, - Distances!.Reordered(ia) + /// + /// Returns this PointsNearObject merged with other PointsNearObject. + /// + public PointsNearObject Merge(PointsNearObject other, int maxCount) + { + if (maxCount < 0) throw new ArgumentOutOfRangeException(nameof(maxCount)); + if (maxCount == 0) return Empty; + if (other == null || other.IsEmpty) return this; + + var merged = new PointsNearObject(Object, + Math.Max(MaxDistance, other.MaxDistance), + Positions.Append(other.Positions)!, + Colors?.Append(other.Colors), + Normals.Append(other.Normals), + Intensities.Append(other.Intensities), + PartIndices.Append(other.PartIndices), + Classifications.Append(other.Classifications), + Distances.Append(other.Distances) ); - /// - /// Takes first 'count' items. - /// - public PointsNearObject Take(int count) + if (Count + other.Count > maxCount) { - if (count >= Count) return this; - var ds = Distances!.Take(count); - return new PointsNearObject(Object, ds.Max(), - Positions.Take(count), Colors?.Take(count), Normals?.Take(count), Intensities?.Take(count), PartIndices?.Take(count), Classifications!.Take(count), ds - ); + // take 'maxCount' nearest items + merged = merged.OrderedByDistanceAscending().Take(maxCount); } - /// - /// - public PointsNearObject WithObject(U other) - => new(other, MaxDistance, Positions, Colors, Normals, Intensities, PartIndices, Classifications, Distances); + return merged; } + + /// + /// Returns PointsNearObject ordered by ascending distance. + /// + public PointsNearObject OrderedByDistanceAscending() => Reordered(Distances.CreatePermutationQuickSortAscending()); + + /// + /// Returns PointsNearObject ordered by descending distance. + /// + public PointsNearObject OrderedByDistanceDescending() => Reordered(Distances.CreatePermutationQuickSortDescending()); + + /// + /// Returns PointsNearObject ordered by descending distance. + /// + public PointsNearObject Reordered(int[] ia) => new( + Object, MaxDistance, + Positions.Reordered(ia), + Colors?.Length > 0 ? Colors.Reordered(ia) : Colors, + Normals?.Length > 0 ? Normals.Reordered(ia) : Normals, + Intensities?.Length > 0 ? Intensities.Reordered(ia) : Intensities, + PartIndices?.Length > 0 ? PartIndices.Reordered(ia) : PartIndices, + Classifications?.Length > 0 ? Classifications.Reordered(ia) : Classifications, + Distances!.Reordered(ia) + ); + + /// + /// Takes first 'count' items. + /// + public PointsNearObject Take(int count) + { + if (count >= Count) return this; + var ds = Distances!.Take(count); + return new PointsNearObject(Object, ds.Max(), + Positions.Take(count), Colors?.Take(count), Normals?.Take(count), Intensities?.Take(count), PartIndices?.Take(count), Classifications!.Take(count), ds + ); + } + + /// + /// + public PointsNearObject WithObject(U other) + => new(other, MaxDistance, Positions, Colors, Normals, Intensities, PartIndices, Classifications, Distances); } diff --git a/src/Aardvark.Geometry.PointSet/Queries/QueriesAllPoints.cs b/src/Aardvark.Geometry.PointSet/Queries/QueriesAllPoints.cs index 8738f311..b13373f3 100644 --- a/src/Aardvark.Geometry.PointSet/Queries/QueriesAllPoints.cs +++ b/src/Aardvark.Geometry.PointSet/Queries/QueriesAllPoints.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -14,28 +14,27 @@ You should have received a copy of the GNU Affero General Public License using Aardvark.Data.Points; using System.Collections.Generic; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static partial class Queries { /// + /// Enumerates (chunked) all points in pointset. /// - public static partial class Queries - { - /// - /// Enumerates (chunked) all points in pointset. - /// - public static IEnumerable QueryAllPoints(this PointSet self) - => QueryAllPoints(self.Root.Value); + public static IEnumerable QueryAllPoints(this PointSet self) + => QueryAllPoints(self.Root.Value); - /// - /// Enumerates (chunked) all points in tree. - /// - public static IEnumerable QueryAllPoints(this IPointCloudNode node) - => node.QueryPoints(_ => true, _ => false, _ => true); + /// + /// Enumerates (chunked) all points in tree. + /// + public static IEnumerable QueryAllPoints(this IPointCloudNode node) + => node.QueryPoints(_ => true, _ => false, _ => true); - /// - /// Enumerates (chunked) all points in tree. - /// - public static IEnumerable QueryAllPoints(this IPointCloudNode node, int minCellExponent) - => node.QueryPoints(_ => true, _ => false, _ => true, minCellExponent); - } + /// + /// Enumerates (chunked) all points in tree. + /// + public static IEnumerable QueryAllPoints(this IPointCloudNode node, int minCellExponent) + => node.QueryPoints(_ => true, _ => false, _ => true, minCellExponent); } diff --git a/src/Aardvark.Geometry.PointSet/Queries/QueriesBox2d.cs b/src/Aardvark.Geometry.PointSet/Queries/QueriesBox2d.cs index 3b9b64c2..d14f2b96 100644 --- a/src/Aardvark.Geometry.PointSet/Queries/QueriesBox2d.cs +++ b/src/Aardvark.Geometry.PointSet/Queries/QueriesBox2d.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -15,160 +15,159 @@ You should have received a copy of the GNU Affero General Public License using Aardvark.Data.Points; using System.Collections.Generic; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static partial class Queries { + #region Query points + + /// + /// All points p.XY inside axis-aligned box (including boundary). + /// + public static IEnumerable QueryPointsInsideBoxXY( + this PointSet self, Box2d query, int minCellExponent = int.MinValue + ) + => QueryPointsInsideBoxXY(self.Root.Value, query, minCellExponent); + + /// + /// All points p.XY inside axis-aligned box (including boundary). + /// + public static IEnumerable QueryPointsInsideBoxXY( + this IPointCloudNode self, Box2d query, int minCellExponent = int.MinValue + ) + => QueryPoints(self, + n => query.Contains(n.Cell.BoundingBox.XY), + n => !query.Intersects(n.Cell.BoundingBox.XY), + p => query.Contains(p.XY), + minCellExponent); + + /// + /// All points p.XY inside axis-aligned box (including boundary). + /// + public static bool QueryContainsPointsInsideBoxXY( + this IPointCloudNode self, Box2d query, int minCellExponent = int.MinValue + ) + => QueryContainsPoints(self, + n => query.Contains(n.Cell.BoundingBox.XY), + n => !query.Intersects(n.Cell.BoundingBox.XY), + p => query.Contains(p.XY), + minCellExponent); + + /// + /// All points p.XY outside axis-aligned box (excluding boundary). + /// + public static IEnumerable QueryPointsOutsideBoxXY( + this PointSet self, Box2d query, int minCellExponent = int.MinValue + ) + => QueryPointsOutsideBoxXY(self.Root.Value, query, minCellExponent); + + /// + /// All points p.Y outside axis-aligned box (excluding boundary). + /// + public static IEnumerable QueryPointsOutsideBoxXY( + this IPointCloudNode self, Box2d query, int minCellExponent = int.MinValue + ) + => QueryPoints(self, + n => !query.Intersects(n.BoundingBoxExactGlobal.XY), + n => query.Contains(n.BoundingBoxExactGlobal.XY), + p => !query.Contains(p.XY), + minCellExponent); + + #endregion + + #region Count exact + + /// + /// Counts points p.XY inside axis-aligned box. + /// + public static long CountPointsInsideBoxXY( + this PointSet self, Box2d query, int minCellExponent = int.MinValue + ) + => CountPointsInsideBoxXY(self.Root.Value, query, minCellExponent); + + /// + /// Counts points p.XY inside axis-aligned box. + /// + public static long CountPointsInsideBoxXY( + this IPointCloudNode self, Box2d query, int minCellExponent = int.MinValue + ) + => CountPoints(self, + n => query.Contains(n.BoundingBoxExactGlobal.XY), + n => !query.Intersects(n.BoundingBoxExactGlobal.XY), + p => query.Contains(p.XY), + minCellExponent); + + /// + /// Counts points p.XY outside axis-aligned box. + /// + public static long CountPointsOutsideBoxXY( + this PointSet self, Box2d query, int minCellExponent = int.MinValue + ) + => CountPointsOutsideBoxXY(self.Root.Value, query, minCellExponent); + + /// + /// Counts points p.XY outside axis-aligned box. + /// + public static long CountPointsOutsideBoxXY( + this IPointCloudNode self, Box2d query, int minCellExponent = int.MinValue + ) + => CountPoints(self, + n => !query.Intersects(n.BoundingBoxExactGlobal.XY), + n => query.Contains(n.BoundingBoxExactGlobal.XY), + p => !query.Contains(p.XY), + minCellExponent); + + #endregion + + #region Count approximately + + /// + /// Counts points p.XY approximately inside axis-aligned box (cell granularity). + /// Result is always equal or greater than exact number. + /// Faster than CountPointsInsideBoxXY. + /// + public static long CountPointsApproximatelyInsideBoxXY( + this PointSet self, Box2d query, int minCellExponent = int.MinValue + ) + => CountPointsApproximatelyInsideBoxXY(self.Root.Value, query, minCellExponent); + + /// + /// Counts points p.XY approximately inside axis-aligned box (cell granularity). + /// Result is always equal or greater than exact number. + /// Faster than CountPointsInsideBoxXY. + /// + public static long CountPointsApproximatelyInsideBoxXY( + this IPointCloudNode self, Box2d query, int minCellExponent = int.MinValue + ) + => CountPointsApproximately(self, + n => query.Contains(n.BoundingBoxExactGlobal.XY), + n => !query.Intersects(n.BoundingBoxExactGlobal.XY), + minCellExponent); + + /// + /// Counts points p.XY approximately outside axis-aligned box (cell granularity). + /// Result is always equal or greater than exact number. + /// Faster than CountPointsOutsideBoxXY. + /// + public static long CountPointsApproximatelyOutsideBoxXY( + this PointSet self, Box2d query, int minCellExponent = int.MinValue + ) + => CountPointsApproximatelyOutsideBoxXY(self.Root.Value, query, minCellExponent); + /// + /// Counts points p.XY approximately outside axis-aligned box (cell granularity). + /// Result is always equal or greater than exact number. + /// Faster than CountPointsOutsideBoxXY. /// - public static partial class Queries - { - #region Query points - - /// - /// All points p.XY inside axis-aligned box (including boundary). - /// - public static IEnumerable QueryPointsInsideBoxXY( - this PointSet self, Box2d query, int minCellExponent = int.MinValue - ) - => QueryPointsInsideBoxXY(self.Root.Value, query, minCellExponent); - - /// - /// All points p.XY inside axis-aligned box (including boundary). - /// - public static IEnumerable QueryPointsInsideBoxXY( - this IPointCloudNode self, Box2d query, int minCellExponent = int.MinValue - ) - => QueryPoints(self, - n => query.Contains(n.Cell.BoundingBox.XY), - n => !query.Intersects(n.Cell.BoundingBox.XY), - p => query.Contains(p.XY), - minCellExponent); - - /// - /// All points p.XY inside axis-aligned box (including boundary). - /// - public static bool QueryContainsPointsInsideBoxXY( - this IPointCloudNode self, Box2d query, int minCellExponent = int.MinValue - ) - => QueryContainsPoints(self, - n => query.Contains(n.Cell.BoundingBox.XY), - n => !query.Intersects(n.Cell.BoundingBox.XY), - p => query.Contains(p.XY), - minCellExponent); - - /// - /// All points p.XY outside axis-aligned box (excluding boundary). - /// - public static IEnumerable QueryPointsOutsideBoxXY( - this PointSet self, Box2d query, int minCellExponent = int.MinValue - ) - => QueryPointsOutsideBoxXY(self.Root.Value, query, minCellExponent); - - /// - /// All points p.Y outside axis-aligned box (excluding boundary). - /// - public static IEnumerable QueryPointsOutsideBoxXY( - this IPointCloudNode self, Box2d query, int minCellExponent = int.MinValue - ) - => QueryPoints(self, - n => !query.Intersects(n.BoundingBoxExactGlobal.XY), - n => query.Contains(n.BoundingBoxExactGlobal.XY), - p => !query.Contains(p.XY), - minCellExponent); - - #endregion - - #region Count exact - - /// - /// Counts points p.XY inside axis-aligned box. - /// - public static long CountPointsInsideBoxXY( - this PointSet self, Box2d query, int minCellExponent = int.MinValue - ) - => CountPointsInsideBoxXY(self.Root.Value, query, minCellExponent); - - /// - /// Counts points p.XY inside axis-aligned box. - /// - public static long CountPointsInsideBoxXY( - this IPointCloudNode self, Box2d query, int minCellExponent = int.MinValue - ) - => CountPoints(self, - n => query.Contains(n.BoundingBoxExactGlobal.XY), - n => !query.Intersects(n.BoundingBoxExactGlobal.XY), - p => query.Contains(p.XY), - minCellExponent); - - /// - /// Counts points p.XY outside axis-aligned box. - /// - public static long CountPointsOutsideBoxXY( - this PointSet self, Box2d query, int minCellExponent = int.MinValue - ) - => CountPointsOutsideBoxXY(self.Root.Value, query, minCellExponent); - - /// - /// Counts points p.XY outside axis-aligned box. - /// - public static long CountPointsOutsideBoxXY( - this IPointCloudNode self, Box2d query, int minCellExponent = int.MinValue - ) - => CountPoints(self, - n => !query.Intersects(n.BoundingBoxExactGlobal.XY), - n => query.Contains(n.BoundingBoxExactGlobal.XY), - p => !query.Contains(p.XY), - minCellExponent); - - #endregion - - #region Count approximately - - /// - /// Counts points p.XY approximately inside axis-aligned box (cell granularity). - /// Result is always equal or greater than exact number. - /// Faster than CountPointsInsideBoxXY. - /// - public static long CountPointsApproximatelyInsideBoxXY( - this PointSet self, Box2d query, int minCellExponent = int.MinValue - ) - => CountPointsApproximatelyInsideBoxXY(self.Root.Value, query, minCellExponent); - - /// - /// Counts points p.XY approximately inside axis-aligned box (cell granularity). - /// Result is always equal or greater than exact number. - /// Faster than CountPointsInsideBoxXY. - /// - public static long CountPointsApproximatelyInsideBoxXY( - this IPointCloudNode self, Box2d query, int minCellExponent = int.MinValue - ) - => CountPointsApproximately(self, - n => query.Contains(n.BoundingBoxExactGlobal.XY), - n => !query.Intersects(n.BoundingBoxExactGlobal.XY), - minCellExponent); - - /// - /// Counts points p.XY approximately outside axis-aligned box (cell granularity). - /// Result is always equal or greater than exact number. - /// Faster than CountPointsOutsideBoxXY. - /// - public static long CountPointsApproximatelyOutsideBoxXY( - this PointSet self, Box2d query, int minCellExponent = int.MinValue - ) - => CountPointsApproximatelyOutsideBoxXY(self.Root.Value, query, minCellExponent); - - /// - /// Counts points p.XY approximately outside axis-aligned box (cell granularity). - /// Result is always equal or greater than exact number. - /// Faster than CountPointsOutsideBoxXY. - /// - public static long CountPointsApproximatelyOutsideBoxXY( - this IPointCloudNode self, Box2d query, int minCellExponent = int.MinValue - ) - => CountPointsApproximately(self, - n => !query.Intersects(n.BoundingBoxExactGlobal.XY), - n => query.Contains(n.BoundingBoxExactGlobal.XY), - minCellExponent); - - #endregion - } + public static long CountPointsApproximatelyOutsideBoxXY( + this IPointCloudNode self, Box2d query, int minCellExponent = int.MinValue + ) + => CountPointsApproximately(self, + n => !query.Intersects(n.BoundingBoxExactGlobal.XY), + n => query.Contains(n.BoundingBoxExactGlobal.XY), + minCellExponent); + + #endregion } diff --git a/src/Aardvark.Geometry.PointSet/Queries/QueriesBox3d.cs b/src/Aardvark.Geometry.PointSet/Queries/QueriesBox3d.cs index 543b9041..3c7fc79e 100644 --- a/src/Aardvark.Geometry.PointSet/Queries/QueriesBox3d.cs +++ b/src/Aardvark.Geometry.PointSet/Queries/QueriesBox3d.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -13,151 +13,149 @@ You should have received a copy of the GNU Affero General Public License */ using Aardvark.Base; using Aardvark.Data.Points; -using System; using System.Collections.Generic; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static partial class Queries { + #region Query points + + /// + /// All points inside axis-aligned box (including boundary). + /// + public static IEnumerable QueryPointsInsideBox( + this PointSet self, Box3d query, int minCellExponent = int.MinValue + ) + => QueryPointsInsideBox(self.Root.Value, query, minCellExponent); + + /// + /// All points inside axis-aligned box (including boundary). + /// + public static IEnumerable QueryPointsInsideBox( + this IPointCloudNode self, Box3d query, int minCellExponent = int.MinValue + ) + => QueryPoints(self, + n => query.Contains(n.BoundingBoxExactGlobal), + n => !query.Intersects(n.BoundingBoxExactGlobal), + p => query.Contains(p), + minCellExponent); + + /// + /// All points outside axis-aligned box (excluding boundary). + /// + public static IEnumerable QueryPointsOutsideBox( + this PointSet self, Box3d query, int minCellExponent = int.MinValue + ) + => QueryPointsOutsideBox(self.Root.Value, query, minCellExponent); + + /// + /// All points outside axis-aligned box (excluding boundary). + /// + public static IEnumerable QueryPointsOutsideBox( + this IPointCloudNode self, Box3d query, int minCellExponent = int.MinValue + ) + => QueryPoints(self, + n => !query.Intersects(n.BoundingBoxExactGlobal), + n => query.Contains(n.BoundingBoxExactGlobal), + p => !query.Contains(p), + minCellExponent); + + #endregion + + #region Count exact + + /// + /// Counts points inside axis-aligned box. + /// + public static long CountPointsInsideBox( + this PointSet self, Box3d query, int minCellExponent = int.MinValue + ) + => CountPointsInsideBox(self.Root.Value, query, minCellExponent); + + /// + /// Counts points inside axis-aligned box. + /// + public static long CountPointsInsideBox( + this IPointCloudNode self, Box3d query, int minCellExponent = int.MinValue + ) + => CountPoints(self, + n => query.Contains(n.BoundingBoxExactGlobal), + n => !query.Intersects(n.BoundingBoxExactGlobal), + p => query.Contains(p), + minCellExponent); + + /// + /// Counts points outside axis-aligned box. + /// + public static long CountPointsOutsideBox( + this PointSet self, Box3d query, int minCellExponent = int.MinValue + ) + => CountPointsOutsideBox(self.Root.Value, query, minCellExponent); + + /// + /// Counts points outside axis-aligned box. + /// + public static long CountPointsOutsideBox( + this IPointCloudNode self, Box3d query, int minCellExponent = int.MinValue + ) + => CountPoints(self, + n => !query.Intersects(n.BoundingBoxExactGlobal), + n => query.Contains(n.BoundingBoxExactGlobal), + p => !query.Contains(p), + minCellExponent); + + #endregion + + #region Count approximately + + /// + /// Counts points approximately inside axis-aligned box (cell granularity). + /// Result is always equal or greater than exact number. + /// Faster than CountPointsInsideBox. + /// + public static long CountPointsApproximatelyInsideBox( + this PointSet self, Box3d query, int minCellExponent = int.MinValue + ) + => CountPointsApproximatelyInsideBox(self.Root.Value, query, minCellExponent); + + /// + /// Counts points approximately inside axis-aligned box (cell granularity). + /// Result is always equal or greater than exact number. + /// Faster than CountPointsInsideBox. + /// + public static long CountPointsApproximatelyInsideBox( + this IPointCloudNode self, Box3d query, int minCellExponent = int.MinValue + ) + => CountPointsApproximately(self, + n => query.Contains(n.BoundingBoxExactGlobal), + n => !query.Intersects(n.BoundingBoxExactGlobal), + minCellExponent); + + /// + /// Counts points approximately outside axis-aligned box (cell granularity). + /// Result is always equal or greater than exact number. + /// Faster than CountPointsOutsideBox. + /// + public static long CountPointsApproximatelyOutsideBox( + this PointSet self, Box3d query, int minCellExponent = int.MinValue + ) + => CountPointsApproximatelyOutsideBox(self.Root.Value, query, minCellExponent); + /// + /// Counts points approximately outside axis-aligned box (cell granularity). + /// Result is always equal or greater than exact number. + /// Faster than CountPointsOutsideBox. /// - public static partial class Queries - { - #region Query points - - /// - /// All points inside axis-aligned box (including boundary). - /// - public static IEnumerable QueryPointsInsideBox( - this PointSet self, Box3d query, int minCellExponent = int.MinValue - ) - => QueryPointsInsideBox(self.Root.Value, query, minCellExponent); - - /// - /// All points inside axis-aligned box (including boundary). - /// - public static IEnumerable QueryPointsInsideBox( - this IPointCloudNode self, Box3d query, int minCellExponent = int.MinValue - ) - => QueryPoints(self, - n => query.Contains(n.BoundingBoxExactGlobal), - n => !query.Intersects(n.BoundingBoxExactGlobal), - p => query.Contains(p), - minCellExponent); - - /// - /// All points outside axis-aligned box (excluding boundary). - /// - public static IEnumerable QueryPointsOutsideBox( - this PointSet self, Box3d query, int minCellExponent = int.MinValue - ) - => QueryPointsOutsideBox(self.Root.Value, query, minCellExponent); - - /// - /// All points outside axis-aligned box (excluding boundary). - /// - public static IEnumerable QueryPointsOutsideBox( - this IPointCloudNode self, Box3d query, int minCellExponent = int.MinValue - ) - => QueryPoints(self, - n => !query.Intersects(n.BoundingBoxExactGlobal), - n => query.Contains(n.BoundingBoxExactGlobal), - p => !query.Contains(p), - minCellExponent); - - #endregion - - #region Count exact - - /// - /// Counts points inside axis-aligned box. - /// - public static long CountPointsInsideBox( - this PointSet self, Box3d query, int minCellExponent = int.MinValue - ) - => CountPointsInsideBox(self.Root.Value, query, minCellExponent); - - /// - /// Counts points inside axis-aligned box. - /// - public static long CountPointsInsideBox( - this IPointCloudNode self, Box3d query, int minCellExponent = int.MinValue - ) - => CountPoints(self, - n => query.Contains(n.BoundingBoxExactGlobal), - n => !query.Intersects(n.BoundingBoxExactGlobal), - p => query.Contains(p), - minCellExponent); - - /// - /// Counts points outside axis-aligned box. - /// - public static long CountPointsOutsideBox( - this PointSet self, Box3d query, int minCellExponent = int.MinValue - ) - => CountPointsOutsideBox(self.Root.Value, query, minCellExponent); - - /// - /// Counts points outside axis-aligned box. - /// - public static long CountPointsOutsideBox( - this IPointCloudNode self, Box3d query, int minCellExponent = int.MinValue - ) - => CountPoints(self, - n => !query.Intersects(n.BoundingBoxExactGlobal), - n => query.Contains(n.BoundingBoxExactGlobal), - p => !query.Contains(p), - minCellExponent); - - #endregion - - #region Count approximately - - /// - /// Counts points approximately inside axis-aligned box (cell granularity). - /// Result is always equal or greater than exact number. - /// Faster than CountPointsInsideBox. - /// - public static long CountPointsApproximatelyInsideBox( - this PointSet self, Box3d query, int minCellExponent = int.MinValue - ) - => CountPointsApproximatelyInsideBox(self.Root.Value, query, minCellExponent); - - /// - /// Counts points approximately inside axis-aligned box (cell granularity). - /// Result is always equal or greater than exact number. - /// Faster than CountPointsInsideBox. - /// - public static long CountPointsApproximatelyInsideBox( - this IPointCloudNode self, Box3d query, int minCellExponent = int.MinValue - ) - => CountPointsApproximately(self, - n => query.Contains(n.BoundingBoxExactGlobal), - n => !query.Intersects(n.BoundingBoxExactGlobal), - minCellExponent); - - /// - /// Counts points approximately outside axis-aligned box (cell granularity). - /// Result is always equal or greater than exact number. - /// Faster than CountPointsOutsideBox. - /// - public static long CountPointsApproximatelyOutsideBox( - this PointSet self, Box3d query, int minCellExponent = int.MinValue - ) - => CountPointsApproximatelyOutsideBox(self.Root.Value, query, minCellExponent); - - /// - /// Counts points approximately outside axis-aligned box (cell granularity). - /// Result is always equal or greater than exact number. - /// Faster than CountPointsOutsideBox. - /// - public static long CountPointsApproximatelyOutsideBox( - this IPointCloudNode self, Box3d query, int minCellExponent = int.MinValue - ) - => CountPointsApproximately(self, - n => !query.Intersects(n.BoundingBoxExactGlobal), - n => query.Contains(n.BoundingBoxExactGlobal), - minCellExponent); - - #endregion - } + public static long CountPointsApproximatelyOutsideBox( + this IPointCloudNode self, Box3d query, int minCellExponent = int.MinValue + ) + => CountPointsApproximately(self, + n => !query.Intersects(n.BoundingBoxExactGlobal), + n => query.Contains(n.BoundingBoxExactGlobal), + minCellExponent); + + #endregion } diff --git a/src/Aardvark.Geometry.PointSet/Queries/QueriesCells.cs b/src/Aardvark.Geometry.PointSet/Queries/QueriesCells.cs index 91e51827..b43ff434 100644 --- a/src/Aardvark.Geometry.PointSet/Queries/QueriesCells.cs +++ b/src/Aardvark.Geometry.PointSet/Queries/QueriesCells.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -17,721 +17,720 @@ You should have received a copy of the GNU Affero General Public License using System.Collections.Generic; using System.Linq; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static partial class Queries { + #region query cells (3d) + /// /// - public static partial class Queries + public class CellQueryResult { - #region query cells (3d) + /// + /// Query root node. + /// + private readonly IPointCloudNode Root; /// + /// Result cell. /// - public class CellQueryResult - { - /// - /// Query root node. - /// - private readonly IPointCloudNode Root; + public readonly Cell Cell; - /// - /// Result cell. - /// - public readonly Cell Cell; + /// + /// Returns points inside cell from LoD at given relative depth, + /// where 0 means points in cell itself, 1 means points from subcells, aso. + /// + public IEnumerable GetPoints(int fromRelativeDepth) + { + if (fromRelativeDepth < 0) throw new ArgumentException( + $"Parameter 'fromRelativeDepth' must not be negative (but is {fromRelativeDepth}). " + + "Invariant 574c0596-82e0-4cc2-91ea-b5153c6d742c.", + nameof(fromRelativeDepth) + ); - /// - /// Returns points inside cell from LoD at given relative depth, - /// where 0 means points in cell itself, 1 means points from subcells, aso. - /// - public IEnumerable GetPoints(int fromRelativeDepth) - { - if (fromRelativeDepth < 0) throw new ArgumentException( - $"Parameter 'fromRelativeDepth' must not be negative (but is {fromRelativeDepth}). " - + "Invariant 574c0596-82e0-4cc2-91ea-b5153c6d742c.", - nameof(fromRelativeDepth) - ); + if (m_result == null) yield break; - if (m_result == null) yield break; + var chunks = m_result.Collect(fromRelativeDepth); - var chunks = m_result.Collect(fromRelativeDepth); + if (m_result.Cell != Cell) + { + chunks = chunks.Select(c => c.ImmutableFilterByCell(Cell)); + } - if (m_result.Cell != Cell) - { - chunks = chunks.Select(c => c.ImmutableFilterByCell(Cell)); - } + foreach (var chunk in chunks) yield return chunk; + } - foreach (var chunk in chunks) yield return chunk; - } + /// + /// + public IEnumerable GetPoints(int fromRelativeDepth, Box3i outer) + => GetPoints(fromRelativeDepth, outer, false); - /// - /// - public IEnumerable GetPoints(int fromRelativeDepth, Box3i outer) - => GetPoints(fromRelativeDepth, outer, false); + /// + /// + public IEnumerable GetPoints(int fromRelativeDepth, Box3i outer, bool excludeInnerCell) + => GetPoints(fromRelativeDepth, outer, new Box3i(V3i.OOO, V3i.OOO), excludeInnerCell); - /// - /// - public IEnumerable GetPoints(int fromRelativeDepth, Box3i outer, bool excludeInnerCell) - => GetPoints(fromRelativeDepth, outer, new Box3i(V3i.OOO, V3i.OOO), excludeInnerCell); + /// + /// Inner cells are excluded. + /// + public IEnumerable GetPoints(int fromRelativeDepth, Box3i outer, Box3i inner) + => GetPoints(fromRelativeDepth, outer, inner, true); - /// - /// Inner cells are excluded. - /// - public IEnumerable GetPoints(int fromRelativeDepth, Box3i outer, Box3i inner) - => GetPoints(fromRelativeDepth, outer, inner, true); + /// + /// + public IEnumerable GetPoints(int fromRelativeDepth, Box3i outer, Box3i inner, bool excludeInnerCells) + { + if (fromRelativeDepth < 0) throw new ArgumentException( + $"Parameter 'fromRelativeDepth' must not be negative (but is {fromRelativeDepth}). " + + "Invariant 7c4a9458-e65b-413f-bd3c-fc8b93b568b8.", + nameof(fromRelativeDepth) + ); + + if (!outer.Contains(inner)) throw new ArgumentException( + $"Outer box ({outer}) must contain inner box ({inner}). " + + "Invariant 98197924-0aea-454e-be6e-8c73e6c9274e.", + nameof(fromRelativeDepth) + ); - /// - /// - public IEnumerable GetPoints(int fromRelativeDepth, Box3i outer, Box3i inner, bool excludeInnerCells) + if (m_result == null) { - if (fromRelativeDepth < 0) throw new ArgumentException( - $"Parameter 'fromRelativeDepth' must not be negative (but is {fromRelativeDepth}). " - + "Invariant 7c4a9458-e65b-413f-bd3c-fc8b93b568b8.", - nameof(fromRelativeDepth) - ); - - if (!outer.Contains(inner)) throw new ArgumentException( - $"Outer box ({outer}) must contain inner box ({inner}). " - + "Invariant 98197924-0aea-454e-be6e-8c73e6c9274e.", - nameof(fromRelativeDepth) - ); - - if (m_result == null) - { - yield break; - } - else + yield break; + } + else + { + for (var x = outer.Min.X; x <= outer.Max.X; x++) { - for (var x = outer.Min.X; x <= outer.Max.X; x++) + for (var y = outer.Min.Y; y <= outer.Max.Y; y++) { - for (var y = outer.Min.Y; y <= outer.Max.Y; y++) + for (var z = outer.Min.Z; z <= outer.Max.Z; z++) { - for (var z = outer.Min.Z; z <= outer.Max.Z; z++) - { - if (excludeInnerCells && inner.Contains(new V3i(x, y, z))) continue; - var c = new Cell(Cell.X + x, Cell.Y + y, Cell.Z + z, Cell.Exponent); - var r = Root.QueryCell(c); - var chunks = r.GetPoints(fromRelativeDepth); - foreach (var chunk in chunks) yield return chunk; - } + if (excludeInnerCells && inner.Contains(new V3i(x, y, z))) continue; + var c = new Cell(Cell.X + x, Cell.Y + y, Cell.Z + z, Cell.Exponent); + var r = Root.QueryCell(c); + var chunks = r.GetPoints(fromRelativeDepth); + foreach (var chunk in chunks) yield return chunk; } } } } - - /// - /// Represents a cell 'resultCell' inside an octree ('root'), - /// where 'resultNode' is root's smallest subnode (incl. root) containing 'resultCell'. - /// - internal CellQueryResult(IPointCloudNode root, Cell resultCell, IPointCloudNode resultNode) - { - Root = root ?? throw new ArgumentNullException(nameof(root)); - Cell = resultCell; - m_result = resultNode; - - if (!root.Cell.Contains(resultNode.Cell)) throw new Exception( - $"Root node {root.Cell} must contain resultNode {resultNode.Cell}. Invariant fb8dc278-fa35-4022-8aa8-281855dd41af." - ); - } - - /// - /// Result node corresponding to result cell (same cell, or parent if octree is not deep enough). - /// - private readonly IPointCloudNode m_result; } /// - /// Returns points in given cell, - /// or null if octree does not cover given cell. - /// Result chunk contains 0 points, if cell is covered by octree, but no points are inside given cell. + /// Represents a cell 'resultCell' inside an octree ('root'), + /// where 'resultNode' is root's smallest subnode (incl. root) containing 'resultCell'. /// - public static CellQueryResult? QueryCell(this PointSet pointset, Cell cell) - => pointset.Root.Value != null ? QueryCell(pointset.Root.Value, cell) : null; + internal CellQueryResult(IPointCloudNode root, Cell resultCell, IPointCloudNode resultNode) + { + Root = root ?? throw new ArgumentNullException(nameof(root)); + Cell = resultCell; + m_result = resultNode; + + if (!root.Cell.Contains(resultNode.Cell)) throw new Exception( + $"Root node {root.Cell} must contain resultNode {resultNode.Cell}. Invariant fb8dc278-fa35-4022-8aa8-281855dd41af." + ); + } /// - /// Returns points in given cell, - /// or null if octree does not cover given cell. - /// Result chunk contains 0 points, if cell is covered by octree, but no points are inside given cell. + /// Result node corresponding to result cell (same cell, or parent if octree is not deep enough). /// - public static CellQueryResult QueryCell(this IPointCloudNode root, Cell cell) + private readonly IPointCloudNode m_result; + } + + /// + /// Returns points in given cell, + /// or null if octree does not cover given cell. + /// Result chunk contains 0 points, if cell is covered by octree, but no points are inside given cell. + /// + public static CellQueryResult? QueryCell(this PointSet pointset, Cell cell) + => pointset.Root.Value != null ? QueryCell(pointset.Root.Value, cell) : null; + + /// + /// Returns points in given cell, + /// or null if octree does not cover given cell. + /// Result chunk contains 0 points, if cell is covered by octree, but no points are inside given cell. + /// + public static CellQueryResult QueryCell(this IPointCloudNode root, Cell cell) + { + if (root == null) { - if (root == null) - { - throw new ArgumentNullException(nameof(root)); - } + throw new ArgumentNullException(nameof(root)); + } - if (!root.Cell.Contains(cell)) - { - return new CellQueryResult(root, cell, root); - } + if (!root.Cell.Contains(cell)) + { + return new CellQueryResult(root, cell, root); + } - return QueryCellRecursive(root); + return QueryCellRecursive(root); - CellQueryResult QueryCellRecursive(IPointCloudNode n) + CellQueryResult QueryCellRecursive(IPointCloudNode n) + { + if (n.Cell == cell || n.IsLeaf) { - if (n.Cell == cell || n.IsLeaf) - { - // found! - return new CellQueryResult(root, cell, n); - } - else + // found! + return new CellQueryResult(root, cell, n); + } + else + { + // continue search in subnode ... + var octant = n.Cell.GetOctant(cell); + if (octant.HasValue) { - // continue search in subnode ... - var octant = n.Cell.GetOctant(cell); - if (octant.HasValue) + var subNodeRef = n.Subnodes![octant.Value]; + if (subNodeRef != null) { - var subNodeRef = n.Subnodes![octant.Value]; - if (subNodeRef != null) - { - return QueryCellRecursive(subNodeRef.Value); - } - else - { - // we can't go deeper - return new CellQueryResult(root, cell, n); - } + return QueryCellRecursive(subNodeRef.Value); } else { - return new CellQueryResult(root, cell, root); + // we can't go deeper + return new CellQueryResult(root, cell, n); } } + else + { + return new CellQueryResult(root, cell, root); + } } } + } - /// - /// Enumerates all points in chunks of a given cell size (given by cellExponent). - /// Cell size is 2^cellExponent, e.g. -2 gives 0.25, -1 gives 0.50, 0 gives 1.00, 1 gives 2.00, and so on. - /// - public static IEnumerable EnumerateCells(this PointSet pointset, int cellExponent) - => EnumerateCells(pointset.Root.Value, cellExponent); + /// + /// Enumerates all points in chunks of a given cell size (given by cellExponent). + /// Cell size is 2^cellExponent, e.g. -2 gives 0.25, -1 gives 0.50, 0 gives 1.00, 1 gives 2.00, and so on. + /// + public static IEnumerable EnumerateCells(this PointSet pointset, int cellExponent) + => EnumerateCells(pointset.Root.Value, cellExponent); - /// - /// Enumerates all points in chunks of a given cell size (given by cellExponent). - /// Cell size is 2^cellExponent, e.g. -2 gives 0.25, -1 gives 0.50, 0 gives 1.00, 1 gives 2.00, and so on. - /// - public static IEnumerable EnumerateCells(this IPointCloudNode root, int cellExponent) - => EnumerateCells(root, cellExponent, V3i.III); + /// + /// Enumerates all points in chunks of a given cell size (given by cellExponent). + /// Cell size is 2^cellExponent, e.g. -2 gives 0.25, -1 gives 0.50, 0 gives 1.00, 1 gives 2.00, and so on. + /// + public static IEnumerable EnumerateCells(this IPointCloudNode root, int cellExponent) + => EnumerateCells(root, cellExponent, V3i.III); - /// - /// Enumerates all points in chunks of a given cell size (given by cellExponent). - /// Cell size is 2^cellExponent, e.g. -2 gives 0.25, -1 gives 0.50, 0 gives 1.00, 1 gives 2.00, and so on. - /// - public static IEnumerable EnumerateCells(this PointSet pointset, int cellExponent, V3i stride) - => EnumerateCells(pointset.Root.Value, cellExponent, stride); + /// + /// Enumerates all points in chunks of a given cell size (given by cellExponent). + /// Cell size is 2^cellExponent, e.g. -2 gives 0.25, -1 gives 0.50, 0 gives 1.00, 1 gives 2.00, and so on. + /// + public static IEnumerable EnumerateCells(this PointSet pointset, int cellExponent, V3i stride) + => EnumerateCells(pointset.Root.Value, cellExponent, stride); - /// - /// Enumerates all points in chunks of a given cell size (given by cellExponent). - /// Cell size is 2^cellExponent, e.g. -2 gives 0.25, -1 gives 0.50, 0 gives 1.00, 1 gives 2.00, and so on. - /// Stride is step size (default is V3i.III), which must be greater 0 for each coordinate axis. - /// - public static IEnumerable EnumerateCells(this IPointCloudNode root, int cellExponent, V3i stride) + /// + /// Enumerates all points in chunks of a given cell size (given by cellExponent). + /// Cell size is 2^cellExponent, e.g. -2 gives 0.25, -1 gives 0.50, 0 gives 1.00, 1 gives 2.00, and so on. + /// Stride is step size (default is V3i.III), which must be greater 0 for each coordinate axis. + /// + public static IEnumerable EnumerateCells(this IPointCloudNode root, int cellExponent, V3i stride) + { + if (root == null) { - if (root == null) - { - throw new ArgumentNullException(nameof(root)); - } + throw new ArgumentNullException(nameof(root)); + } - if (stride.X < 1 || stride.Y < 1 || stride.Z < 1) throw new InvalidOperationException( - $"Stride must be positive, but is {stride}. Invariant be88ccad-798f-4f7d-bcea-6d3eb96c4cb2." - ); + if (stride.X < 1 || stride.Y < 1 || stride.Z < 1) throw new InvalidOperationException( + $"Stride must be positive, but is {stride}. Invariant be88ccad-798f-4f7d-bcea-6d3eb96c4cb2." + ); - if (root.Cell.Exponent < cellExponent) - { - var c = root.Cell; - do { c = c.Parent; } while (c.Exponent < cellExponent); - return new[] { new CellQueryResult(root, c, root) }; - } + if (root.Cell.Exponent < cellExponent) + { + var c = root.Cell; + do { c = c.Parent; } while (c.Exponent < cellExponent); + return [new CellQueryResult(root, c, root)]; + } - return EnumerateCellsOfSizeRecursive(root); + return EnumerateCellsOfSizeRecursive(root); - bool IsOnStride(Cell c) => c.X % stride.X == 0 && c.Y % stride.Y == 0 && c.Z % stride.Z == 0; + bool IsOnStride(Cell c) => c.X % stride.X == 0 && c.Y % stride.Y == 0 && c.Z % stride.Z == 0; - IEnumerable EnumerateCellsOfSizeRecursive(IPointCloudNode n) + IEnumerable EnumerateCellsOfSizeRecursive(IPointCloudNode n) + { + if (n.Cell.Exponent == cellExponent) { - if (n.Cell.Exponent == cellExponent) + // done (reached requested cell size) + if (IsOnStride(n.Cell)) { - // done (reached requested cell size) - if (IsOnStride(n.Cell)) - { - yield return new CellQueryResult(root, n.Cell, n); - } - else - { - yield break; - } + yield return new CellQueryResult(root, n.Cell, n); } - else if (n.IsLeaf()) + else { - // reached leaf which is still too big => split - var xs = Split(n.Cell, n.ToChunk()); - foreach (var (c, _) in xs) - { - if (IsOnStride(c)) - { - yield return new CellQueryResult(root, c, n); - } - } + yield break; } - else + } + else if (n.IsLeaf()) + { + // reached leaf which is still too big => split + var xs = Split(n.Cell, n.ToChunk()); + foreach (var (c, _) in xs) { - for (var i = 0; i < 8; i++) + if (IsOnStride(c)) { - var subnode = n.Subnodes![i]; - if (subnode != null) - { - var xs = EnumerateCellsOfSizeRecursive(subnode.Value); - foreach (var x in xs) yield return x; - } + yield return new CellQueryResult(root, c, n); } } } - - IEnumerable<(Cell cell, Chunk chunk)> Split(Cell c, Chunk chunk) + else { - if (c.Exponent < cellExponent) - throw new InvalidOperationException("Invariant c8117f73-be8d-40d9-8559-ed35bbf5df71."); - - if (c.Exponent == cellExponent) + for (var i = 0; i < 8; i++) { - // reached requested size - yield return (c, chunk); - } - else - { - // split - for (var i = 0; i < 8; i++) + var subnode = n.Subnodes![i]; + if (subnode != null) { - var octant = c.GetOctant(i); - var part = chunk.ImmutableFilterByPosition(octant.BoundingBox.Contains); - if (part.IsEmpty) continue; - - var xs = Split(octant, part); + var xs = EnumerateCellsOfSizeRecursive(subnode.Value); foreach (var x in xs) yield return x; } } } } - #endregion - - #region query cell columns (2d) - - internal class CellQueryResult2dCache + IEnumerable<(Cell cell, Chunk chunk)> Split(Cell c, Chunk chunk) { - public Box2i Size { get; private set; } - public Dictionary> Cache { get; } + if (c.Exponent < cellExponent) + throw new InvalidOperationException("Invariant c8117f73-be8d-40d9-8559-ed35bbf5df71."); - public void UpdateSize(Box2i kernelSize) + if (c.Exponent == cellExponent) { - Size = Size.ExtendedBy(kernelSize); + // reached requested size + yield return (c, chunk); } - - public CellQueryResult2dCache() + else { - Size = Box2i.Invalid; - Cache = new Dictionary>(); + // split + for (var i = 0; i < 8; i++) + { + var octant = c.GetOctant(i); + var part = chunk.ImmutableFilterByPosition(octant.BoundingBox.Contains); + if (part.IsEmpty) continue; + + var xs = Split(octant, part); + foreach (var x in xs) yield return x; + } } } + } + + #endregion + + #region query cell columns (2d) + + internal class CellQueryResult2dCache + { + public Box2i Size { get; private set; } + public Dictionary> Cache { get; } + public void UpdateSize(Box2i kernelSize) + { + Size = Size.ExtendedBy(kernelSize); + } + + public CellQueryResult2dCache() + { + Size = Box2i.Invalid; + Cache = []; + } + } + + /// + /// + public class CellQueryResult2d + { /// + /// Query root node. /// - public class CellQueryResult2d - { - /// - /// Query root node. - /// - private readonly IPointCloudNode Root; + private readonly IPointCloudNode Root; - /// - /// Result (central) column. - /// - public readonly Cell2d Cell; + /// + /// Result (central) column. + /// + public readonly Cell2d Cell; - /// - /// Result (central) column. - /// - public readonly ColZ ColZ; + /// + /// Result (central) column. + /// + public readonly ColZ ColZ; - /// - /// Collects points inside column from LoD at given relative depth, - /// where 0 means points in cell column itself, 1 means points from subcells, aso. - /// - public ColumnPointsXY CollectPoints(int fromRelativeDepth) - { - if (fromRelativeDepth < 0) throw new ArgumentException( - $"Parameter 'fromRelativeDepth' must not be negative (but is {fromRelativeDepth}). " - + "Invariant e2548002-ecb4-421f-959f-daeb3293db60.", - nameof(fromRelativeDepth) - ); + /// + /// Collects points inside column from LoD at given relative depth, + /// where 0 means points in cell column itself, 1 means points from subcells, aso. + /// + public ColumnPointsXY CollectPoints(int fromRelativeDepth) + { + if (fromRelativeDepth < 0) throw new ArgumentException( + $"Parameter 'fromRelativeDepth' must not be negative (but is {fromRelativeDepth}). " + + "Invariant e2548002-ecb4-421f-959f-daeb3293db60.", + nameof(fromRelativeDepth) + ); - return new ColumnPointsXY(this.Cell, Chunk.ImmutableMerge(ColZ.GetPoints(fromRelativeDepth))); - } + return new ColumnPointsXY(this.Cell, Chunk.ImmutableMerge(ColZ.GetPoints(fromRelativeDepth))); + } - /// - /// Collects points from columns relative to center column. - /// E.g. if outer is Box2i(-2,-2,+2,+2) then 5x5 results (at most) are returned. - /// Results with no points are not returned. - /// - public IEnumerable CollectPoints(int fromRelativeDepth, Box2i outer) - => CollectPoints(fromRelativeDepth, outer, false); + /// + /// Collects points from columns relative to center column. + /// E.g. if outer is Box2i(-2,-2,+2,+2) then 5x5 results (at most) are returned. + /// Results with no points are not returned. + /// + public IEnumerable CollectPoints(int fromRelativeDepth, Box2i outer) + => CollectPoints(fromRelativeDepth, outer, false); - /// - /// Collects points from columns relative to center column. The center column (inner cell) is excluded. - /// E.g. if outer is Box2i(-2,-2,+2,+2) then 5x5-1 results (at most) are returned. - /// Results with no points are not returned. - /// - public IEnumerable CollectPoints(int fromRelativeDepth, Box2i outer, bool excludeInnerCell) - => CollectPoints(fromRelativeDepth, outer, new Box2i(V2i.OO, V2i.OO), excludeInnerCell); + /// + /// Collects points from columns relative to center column. The center column (inner cell) is excluded. + /// E.g. if outer is Box2i(-2,-2,+2,+2) then 5x5-1 results (at most) are returned. + /// Results with no points are not returned. + /// + public IEnumerable CollectPoints(int fromRelativeDepth, Box2i outer, bool excludeInnerCell) + => CollectPoints(fromRelativeDepth, outer, new Box2i(V2i.OO, V2i.OO), excludeInnerCell); - /// - /// Collects points from columns relative to center column. - /// Inner cells are excluded. - /// E.g. if outer is Box2i(-2,-2,+2,+2) and inner is Box2i(-1,-1,+1,+1) then 5x5-3x3=16 results (at most) are returned. - /// Results with no points are not returned. - /// - public IEnumerable CollectPoints(int fromRelativeDepth, Box2i outer, Box2i inner) - => CollectPoints(fromRelativeDepth, outer, inner, true); + /// + /// Collects points from columns relative to center column. + /// Inner cells are excluded. + /// E.g. if outer is Box2i(-2,-2,+2,+2) and inner is Box2i(-1,-1,+1,+1) then 5x5-3x3=16 results (at most) are returned. + /// Results with no points are not returned. + /// + public IEnumerable CollectPoints(int fromRelativeDepth, Box2i outer, Box2i inner) + => CollectPoints(fromRelativeDepth, outer, inner, true); - /// - /// Collects points from columns relative to center column. - /// Inner cells are excluded if excludeInnerCells is true. - /// E.g. if outer is Box2i(-2,-2,+2,+2) and inner is Box2i(-1,-1,+1,+1) then 5x5-3x3=16 results (at most) are returned. - /// Results with no points are not returned. - /// - public IEnumerable CollectPoints(int fromRelativeDepth, Box2i outer, Box2i inner, bool excludeInnerCells) + /// + /// Collects points from columns relative to center column. + /// Inner cells are excluded if excludeInnerCells is true. + /// E.g. if outer is Box2i(-2,-2,+2,+2) and inner is Box2i(-1,-1,+1,+1) then 5x5-3x3=16 results (at most) are returned. + /// Results with no points are not returned. + /// + public IEnumerable CollectPoints(int fromRelativeDepth, Box2i outer, Box2i inner, bool excludeInnerCells) + { + if (fromRelativeDepth < 0) throw new ArgumentException( + $"Parameter 'fromRelativeDepth' must not be negative (but is {fromRelativeDepth}). " + + "Invariant 217d4237-cb64-4a15-ab1f-62566a0c65a9.", + nameof(fromRelativeDepth) + ); + + if (!outer.Contains(inner)) throw new ArgumentException( + $"Outer box ({outer}) must contain inner box ({inner}). " + + "Invariant 31e0421e-339f-4354-8193-e508214bc364.", + nameof(fromRelativeDepth) + ); + + Dictionary cache; + lock (Cache) { - if (fromRelativeDepth < 0) throw new ArgumentException( - $"Parameter 'fromRelativeDepth' must not be negative (but is {fromRelativeDepth}). " - + "Invariant 217d4237-cb64-4a15-ab1f-62566a0c65a9.", - nameof(fromRelativeDepth) - ); - - if (!outer.Contains(inner)) throw new ArgumentException( - $"Outer box ({outer}) must contain inner box ({inner}). " - + "Invariant 31e0421e-339f-4354-8193-e508214bc364.", - nameof(fromRelativeDepth) - ); - - Dictionary cache; - lock (Cache) - { - Cache.UpdateSize(outer); - if (!Cache.Cache.TryGetValue(fromRelativeDepth, out cache)) - cache = Cache.Cache[fromRelativeDepth] = new Dictionary(); - } + Cache.UpdateSize(outer); + if (!Cache.Cache.TryGetValue(fromRelativeDepth, out cache)) + cache = Cache.Cache[fromRelativeDepth] = []; + } - //var cacheHits = 0; - //var cacheMisses = 0; + //var cacheHits = 0; + //var cacheMisses = 0; - inner += new V2i(Cell.X, Cell.Y); - outer += new V2i(Cell.X, Cell.Y); - for (var y = outer.Min.Y; y <= outer.Max.Y; y++) + inner += new V2i(Cell.X, Cell.Y); + outer += new V2i(Cell.X, Cell.Y); + for (var y = outer.Min.Y; y <= outer.Max.Y; y++) + { + for (var x = outer.Min.X; x <= outer.Max.X; x++) { - for (var x = outer.Min.X; x <= outer.Max.X; x++) - { - if (excludeInnerCells && inner.Contains(new V2i(x, y))) continue; - var c = new Cell2d(x, y, Cell.Exponent); + if (excludeInnerCells && inner.Contains(new V2i(x, y))) continue; + var c = new Cell2d(x, y, Cell.Exponent); - if (!cache.TryGetValue(c, out var chunks)) - { - chunks = Chunk.ImmutableMerge(Root.CollectColumnXY(c, fromRelativeDepth).ToArray()); - lock (Cache) cache[c] = chunks; - //cacheMisses++; - } - else - { - //cacheHits++; - } + if (!cache.TryGetValue(c, out var chunks)) + { + chunks = Chunk.ImmutableMerge(Root.CollectColumnXY(c, fromRelativeDepth).ToArray()); + lock (Cache) cache[c] = chunks; + //cacheMisses++; + } + else + { + //cacheHits++; + } - if (chunks.Count > 0) - { - yield return new ColumnPointsXY(c, chunks); - } + if (chunks.Count > 0) + { + yield return new ColumnPointsXY(c, chunks); } } - lock (Cache) - { - var foo = Cache.Size + new V2i(Cell.X, Cell.Y); - var ks = cache.Keys.Where(k => !foo.Contains(new V2i(k.X, k.Y))).ToArray(); - foreach (var k in ks) cache.Remove(k); - } - //Report.Warn($"hits = {cacheHits,2}, misses = {cacheMisses,2}"); } + lock (Cache) + { + var foo = Cache.Size + new V2i(Cell.X, Cell.Y); + var ks = cache.Keys.Where(k => !foo.Contains(new V2i(k.X, k.Y))).ToArray(); + foreach (var k in ks) cache.Remove(k); + } + //Report.Warn($"hits = {cacheHits,2}, misses = {cacheMisses,2}"); + } + /// + /// Points in a column with given footprint. + /// + public class ColumnPointsXY + { /// - /// Points in a column with given footprint. + /// Footprint of column. /// - public class ColumnPointsXY - { - /// - /// Footprint of column. - /// - public Cell2d Footprint { get; } - /// - /// All points in the column with given footprint. - /// - public Chunk Points { get; } - - internal ColumnPointsXY(Cell2d footprint, Chunk points) - { - Footprint = footprint; - Points = points; - } - } - + public Cell2d Footprint { get; } /// - /// Represents a cell 'resultCell' inside an octree ('root'). + /// All points in the column with given footprint. /// - internal CellQueryResult2d(IPointCloudNode root, Cell2d resultCell, ColZ colz, CellQueryResult2dCache cache) + public Chunk Points { get; } + + internal ColumnPointsXY(Cell2d footprint, Chunk points) { - Root = root ?? throw new ArgumentNullException(nameof(root)); - Cell = resultCell; - ColZ = colz; - Cache = cache ?? throw new ArgumentNullException(nameof(cache)); + Footprint = footprint; + Points = points; } + } - private CellQueryResult2dCache Cache { get; } - - - - /// - /// Deprecated. Use CollectPoints instead. - /// This method will be removed in future version. - /// - [Obsolete] - public IEnumerable GetPoints(int fromRelativeDepth) => new[] { CollectPoints(fromRelativeDepth).Points }; - - /// - /// Deprecated. Use CollectPoints instead. - /// This method will be removed in future version. - /// - [Obsolete] - public IEnumerable GetPoints(int fromRelativeDepth, Box2i outer) => CollectPoints(fromRelativeDepth, outer).Select(x => x.Points); + /// + /// Represents a cell 'resultCell' inside an octree ('root'). + /// + internal CellQueryResult2d(IPointCloudNode root, Cell2d resultCell, ColZ colz, CellQueryResult2dCache cache) + { + Root = root ?? throw new ArgumentNullException(nameof(root)); + Cell = resultCell; + ColZ = colz; + Cache = cache ?? throw new ArgumentNullException(nameof(cache)); + } - /// - /// Deprecated. Use CollectPoints instead. - /// This method will be removed in future version. - /// - [Obsolete] - public IEnumerable GetPoints(int fromRelativeDepth, Box2i outer, bool excludeInnerCell) => CollectPoints(fromRelativeDepth, outer, excludeInnerCell).Select(x => x.Points); + private CellQueryResult2dCache Cache { get; } - /// - /// Deprecated. Use CollectPoints instead. - /// This method will be removed in future version. - /// - [Obsolete] - public IEnumerable GetPoints(int fromRelativeDepth, Box2i outer, Box2i inner) => CollectPoints(fromRelativeDepth, outer, inner).Select(x => x.Points); - /// - /// Deprecated. Use CollectPoints instead. - /// This method will be removed in future version. - /// - [Obsolete] - public IEnumerable GetPoints(int fromRelativeDepth, Box2i outer, Box2i inner, bool excludeInnerCells) => CollectPoints(fromRelativeDepth, outer, inner, excludeInnerCells).Select(x => x.Points); - } + /// + /// Deprecated. Use CollectPoints instead. + /// This method will be removed in future version. + /// + [Obsolete] + public IEnumerable GetPoints(int fromRelativeDepth) => [CollectPoints(fromRelativeDepth).Points]; /// - /// Enumerates all columns of a given cell size (given by cellExponent). - /// Cell size is 2^cellExponent, e.g. -2 gives 0.25, -1 gives 0.50, 0 gives 1.00, 1 gives 2.00, and so on. - /// Stride is step size (default is V3i.III), which must be greater 0 for each coordinate axis. + /// Deprecated. Use CollectPoints instead. + /// This method will be removed in future version. /// - public static IEnumerable EnumerateCellColumns(this PointSet pointset, int cellExponent) - => EnumerateCellColumns(pointset.Root.Value, cellExponent); + [Obsolete] + public IEnumerable GetPoints(int fromRelativeDepth, Box2i outer) => CollectPoints(fromRelativeDepth, outer).Select(x => x.Points); /// - /// Enumerates all columns of a given cell size (given by cellExponent). - /// Cell size is 2^cellExponent, e.g. -2 gives 0.25, -1 gives 0.50, 0 gives 1.00, 1 gives 2.00, and so on. - /// Stride is step size (default is V3i.III), which must be greater 0 for each coordinate axis. + /// Deprecated. Use CollectPoints instead. + /// This method will be removed in future version. /// - public static IEnumerable EnumerateCellColumns(this IPointCloudNode root, int cellExponent) - => EnumerateCellColumns(root, cellExponent, V2i.II); + [Obsolete] + public IEnumerable GetPoints(int fromRelativeDepth, Box2i outer, bool excludeInnerCell) => CollectPoints(fromRelativeDepth, outer, excludeInnerCell).Select(x => x.Points); /// - /// Enumerates all columns of a given cell size (given by cellExponent). - /// Cell size is 2^cellExponent, e.g. -2 gives 0.25, -1 gives 0.50, 0 gives 1.00, 1 gives 2.00, and so on. - /// Stride is step size (default is V3i.III), which must be greater 0 for each coordinate axis. + /// Deprecated. Use CollectPoints instead. + /// This method will be removed in future version. /// - public static IEnumerable EnumerateCellColumns(this PointSet pointset, int cellExponent, V2i stride) - => EnumerateCellColumns(pointset.Root.Value, cellExponent, stride); + [Obsolete] + public IEnumerable GetPoints(int fromRelativeDepth, Box2i outer, Box2i inner) => CollectPoints(fromRelativeDepth, outer, inner).Select(x => x.Points); /// - /// Enumerates all columns of a given cell size (given by cellExponent). - /// Cell size is 2^cellExponent, e.g. -2 gives 0.25, -1 gives 0.50, 0 gives 1.00, 1 gives 2.00, and so on. - /// Stride is step size (default is V3i.III), which must be greater 0 for each coordinate axis. + /// Deprecated. Use CollectPoints instead. + /// This method will be removed in future version. /// - public static IEnumerable EnumerateCellColumns(this IPointCloudNode root, int cellExponent, V2i stride) + [Obsolete] + public IEnumerable GetPoints(int fromRelativeDepth, Box2i outer, Box2i inner, bool excludeInnerCells) => CollectPoints(fromRelativeDepth, outer, inner, excludeInnerCells).Select(x => x.Points); + + } + + /// + /// Enumerates all columns of a given cell size (given by cellExponent). + /// Cell size is 2^cellExponent, e.g. -2 gives 0.25, -1 gives 0.50, 0 gives 1.00, 1 gives 2.00, and so on. + /// Stride is step size (default is V3i.III), which must be greater 0 for each coordinate axis. + /// + public static IEnumerable EnumerateCellColumns(this PointSet pointset, int cellExponent) + => EnumerateCellColumns(pointset.Root.Value, cellExponent); + + /// + /// Enumerates all columns of a given cell size (given by cellExponent). + /// Cell size is 2^cellExponent, e.g. -2 gives 0.25, -1 gives 0.50, 0 gives 1.00, 1 gives 2.00, and so on. + /// Stride is step size (default is V3i.III), which must be greater 0 for each coordinate axis. + /// + public static IEnumerable EnumerateCellColumns(this IPointCloudNode root, int cellExponent) + => EnumerateCellColumns(root, cellExponent, V2i.II); + + /// + /// Enumerates all columns of a given cell size (given by cellExponent). + /// Cell size is 2^cellExponent, e.g. -2 gives 0.25, -1 gives 0.50, 0 gives 1.00, 1 gives 2.00, and so on. + /// Stride is step size (default is V3i.III), which must be greater 0 for each coordinate axis. + /// + public static IEnumerable EnumerateCellColumns(this PointSet pointset, int cellExponent, V2i stride) + => EnumerateCellColumns(pointset.Root.Value, cellExponent, stride); + + /// + /// Enumerates all columns of a given cell size (given by cellExponent). + /// Cell size is 2^cellExponent, e.g. -2 gives 0.25, -1 gives 0.50, 0 gives 1.00, 1 gives 2.00, and so on. + /// Stride is step size (default is V3i.III), which must be greater 0 for each coordinate axis. + /// + public static IEnumerable EnumerateCellColumns(this IPointCloudNode root, int cellExponent, V2i stride) + { + if (root == null) { - if (root == null) - { - throw new ArgumentNullException(nameof(root)); - } + throw new ArgumentNullException(nameof(root)); + } - if (stride.X < 1 || stride.Y < 1) throw new InvalidOperationException( - $"Stride must be positive, but is {stride}. Invariant 6b7a86a4-6bde-41f1-9af8-e7dc75177e68." - ); + if (stride.X < 1 || stride.Y < 1) throw new InvalidOperationException( + $"Stride must be positive, but is {stride}. Invariant 6b7a86a4-6bde-41f1-9af8-e7dc75177e68." + ); - var cache = new CellQueryResult2dCache(); + var cache = new CellQueryResult2dCache(); - // new-style - var dx = Fun.PowerOfTwo(cellExponent) * (ulong)(stride.X - 1 / 2); // FIXME: missing parantheses? - var dy = Fun.PowerOfTwo(cellExponent) * (ulong)(stride.Y - 1 / 2); - var bbCell = new Cell2d(root.Cell.X, root.Cell.X, root.Cell.Exponent).BoundingBox; - var bb = new Box2d(bbCell.Min - new V2d(dx, dy), bbCell.Max + new V2d(dx, dy)); - var enlargedFootprint = new Cell2d(bb); + // new-style + var dx = Fun.PowerOfTwo(cellExponent) * (ulong)(stride.X - 1 / 2); // FIXME: missing parantheses? + var dy = Fun.PowerOfTwo(cellExponent) * (ulong)(stride.Y - 1 / 2); + var bbCell = new Cell2d(root.Cell.X, root.Cell.X, root.Cell.Exponent).BoundingBox; + var bb = new Box2d(bbCell.Min - new V2d(dx, dy), bbCell.Max + new V2d(dx, dy)); + var enlargedFootprint = new Cell2d(bb); - var cs = new ColZ(root, enlargedFootprint).EnumerateColumns(cellExponent, stride); - foreach (var c in cs) - { - yield return new CellQueryResult2d(root, c.Footprint, c, cache); - } + var cs = new ColZ(root, enlargedFootprint).EnumerateColumns(cellExponent, stride); + foreach (var c in cs) + { + yield return new CellQueryResult2d(root, c.Footprint, c, cache); } + } - #endregion + #endregion - public class ColZ + public class ColZ + { + public Cell2d Footprint { get; } + public IPointCloudNode[] Nodes { get; } + public Chunk Rest { get; } + public ColZ(IPointCloudNode n) { - public Cell2d Footprint { get; } - public IPointCloudNode[] Nodes { get; } - public Chunk Rest { get; } - public ColZ(IPointCloudNode n) - { - Footprint = new Cell2d(n.Cell.X, n.Cell.Y, n.Cell.Exponent); - Nodes = new [] { n ?? throw new ArgumentNullException(nameof(n)) }; - Rest = Chunk.Empty; - } - public ColZ(IPointCloudNode n, Cell2d footprint) - { - Footprint = footprint; - Nodes = new[] { n ?? throw new ArgumentNullException(nameof(n)) }; - Rest = Chunk.Empty; - } + Footprint = new Cell2d(n.Cell.X, n.Cell.Y, n.Cell.Exponent); + Nodes = [n ?? throw new ArgumentNullException(nameof(n))]; + Rest = Chunk.Empty; + } + public ColZ(IPointCloudNode n, Cell2d footprint) + { + Footprint = footprint; + Nodes = [n ?? throw new ArgumentNullException(nameof(n))]; + Rest = Chunk.Empty; + } - private ColZ(Cell2d footprint, IPointCloudNode[] nodes, Chunk rest) - { - Footprint = footprint; - Nodes = nodes ?? throw new ArgumentNullException(nameof(nodes)); - Rest = rest; + private ColZ(Cell2d footprint, IPointCloudNode[] nodes, Chunk rest) + { + Footprint = footprint; + Nodes = nodes ?? throw new ArgumentNullException(nameof(nodes)); + Rest = rest; #if DEBUG - static Cell2d GetFootprintZ(Cell c) => new(c.X, c.Y, c.Exponent); - if (!nodes.All(n => footprint.Contains(GetFootprintZ(n.Cell)))) throw new InvalidOperationException(); - var bb = footprint.BoundingBox; - if (rest.HasPositions && !rest.Positions.All(p => bb.Contains(p.XY))) throw new InvalidOperationException(); + static Cell2d GetFootprintZ(Cell c) => new(c.X, c.Y, c.Exponent); + if (!nodes.All(n => footprint.Contains(GetFootprintZ(n.Cell)))) throw new InvalidOperationException(); + var bb = footprint.BoundingBox; + if (rest.HasPositions && !rest.Positions.All(p => bb.Contains(p.XY))) throw new InvalidOperationException(); #endif - } + } - public bool IsEmpty => Nodes.Length == 0 && Rest.IsEmpty; + public bool IsEmpty => Nodes.Length == 0 && Rest.IsEmpty; - public IEnumerable EnumerateColumns(int cellExponent) - => EnumerateColumns(cellExponent, V2i.II); + public IEnumerable EnumerateColumns(int cellExponent) + => EnumerateColumns(cellExponent, V2i.II); - public IEnumerable EnumerateColumns(int cellExponent, V2i stride) - { - if (Footprint.Exponent < cellExponent) throw new InvalidOperationException( - $"ColZ is already smaller ({Footprint.Exponent}) then requested cellExponent ({cellExponent}). Invariant 00a70058-0cd8-42fa-ae11-b28de9986984." - ); + public IEnumerable EnumerateColumns(int cellExponent, V2i stride) + { + if (Footprint.Exponent < cellExponent) throw new InvalidOperationException( + $"ColZ is already smaller ({Footprint.Exponent}) then requested cellExponent ({cellExponent}). Invariant 00a70058-0cd8-42fa-ae11-b28de9986984." + ); - if (Footprint.Exponent == cellExponent) + if (Footprint.Exponent == cellExponent) + { + if (Footprint.X % stride.X == 0 && Footprint.Y % stride.Y == 0) { - if (Footprint.X % stride.X == 0 && Footprint.Y % stride.Y == 0) - { - yield return this; - } + yield return this; } - else + } + else + { + foreach (var col in Split()) { - foreach (var col in Split()) + if (col == null) continue; + foreach (var x in col.EnumerateColumns(cellExponent, stride)) { - if (col == null) continue; - foreach (var x in col.EnumerateColumns(cellExponent, stride)) - { - if (x.Footprint.X % stride.X != 0 || x.Footprint.Y % stride.Y != 0) continue; - if (x.CountTotal == 0) continue; - yield return x; - } + if (x.Footprint.X % stride.X != 0 || x.Footprint.Y % stride.Y != 0) continue; + if (x.CountTotal == 0) continue; + yield return x; } } } + } - /// - /// Number of points in column at current level. - /// - public long Count => Nodes.Sum(n => n.PointCountCell) + Rest.Count; + /// + /// Number of points in column at current level. + /// + public long Count => Nodes.Sum(n => n.PointCountCell) + Rest.Count; - /// - /// Number of points in column at most detailed level. - /// - public long CountTotal => Nodes.Sum(n => n.PointCountTree) + Rest.Count; + /// + /// Number of points in column at most detailed level. + /// + public long CountTotal => Nodes.Sum(n => n.PointCountTree) + Rest.Count; - /// - /// Returns points inside cell column from LoD at given relative depth, - /// where 0 means points in cell column itself, 1 means points from subcells, aso. - /// - public IEnumerable GetPoints(int fromRelativeDepth) - { - if (fromRelativeDepth < 0) throw new ArgumentException( - $"Parameter 'fromRelativeDepth' must not be negative (but is {fromRelativeDepth}). " - + "Invariant 99e46eef-0c0f-4279-8c98-8f01e29788b3.", - nameof(fromRelativeDepth) - ); + /// + /// Returns points inside cell column from LoD at given relative depth, + /// where 0 means points in cell column itself, 1 means points from subcells, aso. + /// + public IEnumerable GetPoints(int fromRelativeDepth) + { + if (fromRelativeDepth < 0) throw new ArgumentException( + $"Parameter 'fromRelativeDepth' must not be negative (but is {fromRelativeDepth}). " + + "Invariant 99e46eef-0c0f-4279-8c98-8f01e29788b3.", + nameof(fromRelativeDepth) + ); - foreach (var n in Nodes) + foreach (var n in Nodes) + { + foreach (var x in n.ToChunk(fromRelativeDepth)) { - foreach (var x in n.ToChunk(fromRelativeDepth)) + if (x.Count > 0) { - if (x.Count > 0) - { - yield return x; - } + yield return x; } } + } - if (Rest.Count > 0) - { - yield return Rest; - } + if (Rest.Count > 0) + { + yield return Rest; } + } - private ColZ?[] Split() + private ColZ?[] Split() + { + // inner ... + var nss = new List[4].SetByIndex(_ => []); + foreach (var n in Nodes.Where(n => !n.IsLeaf)) { - // inner ... - var nss = new List[4].SetByIndex(_ => new List()); - foreach (var n in Nodes.Where(n => !n.IsLeaf)) + for (var i = 0; i < 8; i++) { - for (var i = 0; i < 8; i++) - { - var x = n?.Subnodes![i]?.Value; - if (x != null) nss[i & 0b011].Add(x); + var x = n?.Subnodes![i]?.Value; + if (x != null) nss[i & 0b011].Add(x); - } } + } - // leafs ... - var c = Footprint.GetCenter(); - var rs = Rest - .ImmutableMergeWith(Nodes.Where(n => n.IsLeaf).Select(n => n.ToChunk())) - .GroupBy((chunk, i) => oct(c, chunk.Positions[i].XY)) - ; - - // create sub-columns ... - return new ColZ[4].SetByIndex(i => - (nss[i].Count > 0 || rs.ContainsKey(i)) - ? new ColZ( - Footprint.GetQuadrant(i), - nss[i].Count > 0 ? nss[i].ToArray() : Array.Empty(), - rs.GetOrDefault(i, Chunk.Empty) - ) - : null - ); + // leafs ... + var c = Footprint.GetCenter(); + var rs = Rest + .ImmutableMergeWith(Nodes.Where(n => n.IsLeaf).Select(n => n.ToChunk())) + .GroupBy((chunk, i) => oct(c, chunk.Positions[i].XY)) + ; + + // create sub-columns ... + return new ColZ[4].SetByIndex(i => + (nss[i].Count > 0 || rs.ContainsKey(i)) + ? new ColZ( + Footprint.GetQuadrant(i), + nss[i].Count > 0 ? [.. nss[i]] : [], + rs.GetOrDefault(i, Chunk.Empty) + ) + : null + ); - static int oct(V2d center, V2d p) - { - var i = p.X > center.X ? 1 : 0; - return p.Y > center.Y ? i | 2 : i; - } + static int oct(V2d center, V2d p) + { + var i = p.X > center.X ? 1 : 0; + return p.Y > center.Y ? i | 2 : i; } } } diff --git a/src/Aardvark.Geometry.PointSet/Queries/QueriesDirectedRay3d.cs b/src/Aardvark.Geometry.PointSet/Queries/QueriesDirectedRay3d.cs index 27b84032..4e212fc1 100644 --- a/src/Aardvark.Geometry.PointSet/Queries/QueriesDirectedRay3d.cs +++ b/src/Aardvark.Geometry.PointSet/Queries/QueriesDirectedRay3d.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -11,11 +11,10 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -using System; -using System.Collections.Generic; -using System.Linq; using Aardvark.Base; using Aardvark.Data.Points; +using System.Collections.Generic; +using System.Linq; namespace Aardvark.Geometry.Points; @@ -36,7 +35,7 @@ public static IEnumerable QueryPointsNearRay( int minCellExponent = int.MinValue ) { - if(ps.Root == null) return Enumerable.Empty(); + if (ps.Root == null) return []; return ps.Root.Value.QueryPointsNearRay(ray,maxDistanceToRay,tMin,tMax,minCellExponent); } diff --git a/src/Aardvark.Geometry.PointSet/Queries/QueriesGeneric.cs b/src/Aardvark.Geometry.PointSet/Queries/QueriesGeneric.cs index 8b6b2066..8268464b 100644 --- a/src/Aardvark.Geometry.PointSet/Queries/QueriesGeneric.cs +++ b/src/Aardvark.Geometry.PointSet/Queries/QueriesGeneric.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -16,250 +16,249 @@ You should have received a copy of the GNU Affero General Public License using System; using System.Collections.Generic; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static partial class Queries { + #region Query points + /// /// - public static partial class Queries - { - #region Query points + public static IEnumerable QueryPoints(this PointSet node, + Func isNodeFullyInside, + Func isNodeFullyOutside, + Func isPositionInside, + int minCellExponent = int.MinValue + ) + => QueryPoints(node.Root.Value, isNodeFullyInside, isNodeFullyOutside, isPositionInside, minCellExponent); - /// - /// - public static IEnumerable QueryPoints(this PointSet node, - Func isNodeFullyInside, - Func isNodeFullyOutside, - Func isPositionInside, - int minCellExponent = int.MinValue - ) - => QueryPoints(node.Root.Value, isNodeFullyInside, isNodeFullyOutside, isPositionInside, minCellExponent); + /// + /// + public static IEnumerable QueryPoints(this IPointCloudNode node, + Func isNodeFullyInside, + Func isNodeFullyOutside, + Func isPositionInside, + int minCellExponent = int.MinValue + ) + { + if (node.Cell.Exponent < minCellExponent) yield break; - /// - /// - public static IEnumerable QueryPoints(this IPointCloudNode node, - Func isNodeFullyInside, - Func isNodeFullyOutside, - Func isPositionInside, - int minCellExponent = int.MinValue - ) + if (isNodeFullyOutside(node)) yield break; + + if (node.IsLeaf() || node.Cell.Exponent == minCellExponent) { - if (node.Cell.Exponent < minCellExponent) yield break; - - if (isNodeFullyOutside(node)) yield break; - - if (node.IsLeaf() || node.Cell.Exponent == minCellExponent) + if (isNodeFullyInside(node)) { - if (isNodeFullyInside(node)) - { - yield return node.ToChunk(); - } - else // partially inside - { - var ps = node.PositionsAbsolute; - var csRaw = node.HasColors ? node.Colors.Value : null; - var nsRaw = node.HasNormals ? node.Normals.Value : null; - var jsRaw = node.HasIntensities ? node.Intensities.Value : null; - var ksRaw = node.HasClassifications ? node.Classifications!.Value : null; - var qsRaw = node.PartIndices; + yield return node.ToChunk(); + } + else // partially inside + { + var ps = node.PositionsAbsolute; + var csRaw = node.HasColors ? node.Colors.Value : null; + var nsRaw = node.HasNormals ? node.Normals.Value : null; + var jsRaw = node.HasIntensities ? node.Intensities.Value : null; + var ksRaw = node.HasClassifications ? node.Classifications!.Value : null; + var qsRaw = node.PartIndices; - var ia = new List(); - for (var i = 0; i < ps.Length; i++) if (isPositionInside(ps[i])) ia.Add(i); + var ia = new List(); + for (var i = 0; i < ps.Length; i++) if (isPositionInside(ps[i])) ia.Add(i); - if (ia.Count > 0) yield return new Chunk( - ps.Subset(ia), csRaw?.Subset(ia), nsRaw?.Subset(ia), jsRaw?.Subset(ia), ksRaw?.Subset(ia), PartIndexUtils.Subset(qsRaw, ia), partIndexRange: null, bbox: null - ); - } + if (ia.Count > 0) yield return new Chunk( + ps.Subset(ia), csRaw?.Subset(ia), nsRaw?.Subset(ia), jsRaw?.Subset(ia), ksRaw?.Subset(ia), PartIndexUtils.Subset(qsRaw, ia), partIndexRange: null, bbox: null + ); } - else + } + else + { + for (var i = 0; i < 8; i++) { - for (var i = 0; i < 8; i++) - { - var n = node.Subnodes![i]; - if (n == null) continue; - var xs = QueryPoints(n.Value, isNodeFullyInside, isNodeFullyOutside, isPositionInside, minCellExponent); - foreach (var x in xs) yield return x; - } + var n = node.Subnodes![i]; + if (n == null) continue; + var xs = QueryPoints(n.Value, isNodeFullyInside, isNodeFullyOutside, isPositionInside, minCellExponent); + foreach (var x in xs) yield return x; } } + } - /// - /// Enumerates cells/front at given cell exponent (or higher if given depth is not reached). - /// E.g. with minCellExponent = 0 all cells of size 1 (or larger) are numerated. - /// - public static IEnumerable QueryPoints(this IPointCloudNode node, - int minCellExponent = int.MinValue - ) => QueryPoints( - node, - _ => true, - _ => throw new InvalidOperationException("Invariant 482cbeed-88f2-46af-9cc0-6b0f6f1fc61a."), - _ => throw new InvalidOperationException("Invariant 31b005a8-f65d-406f-b2a1-96f133d357d3."), - minCellExponent - ); + /// + /// Enumerates cells/front at given cell exponent (or higher if given depth is not reached). + /// E.g. with minCellExponent = 0 all cells of size 1 (or larger) are numerated. + /// + public static IEnumerable QueryPoints(this IPointCloudNode node, + int minCellExponent = int.MinValue + ) => QueryPoints( + node, + _ => true, + _ => throw new InvalidOperationException("Invariant 482cbeed-88f2-46af-9cc0-6b0f6f1fc61a."), + _ => throw new InvalidOperationException("Invariant 31b005a8-f65d-406f-b2a1-96f133d357d3."), + minCellExponent + ); - #endregion + #endregion - #region Count exact + #region Count exact - /// - /// Exact count. - /// - public static long CountPoints(this PointSet node, - Func isNodeFullyInside, - Func isNodeFullyOutside, - Func isPositionInside, - int minCellExponent = int.MinValue - ) - => CountPoints(node.Root.Value, isNodeFullyInside, isNodeFullyOutside, isPositionInside, minCellExponent); + /// + /// Exact count. + /// + public static long CountPoints(this PointSet node, + Func isNodeFullyInside, + Func isNodeFullyOutside, + Func isPositionInside, + int minCellExponent = int.MinValue + ) + => CountPoints(node.Root.Value, isNodeFullyInside, isNodeFullyOutside, isPositionInside, minCellExponent); - /// - /// Exact count. - /// - public static long CountPoints(this IPointCloudNode node, - Func isNodeFullyInside, - Func isNodeFullyOutside, - Func isPositionInside, - int minCellExponent = int.MinValue - ) - { - if (node.Cell.Exponent < minCellExponent) return 0L; + /// + /// Exact count. + /// + public static long CountPoints(this IPointCloudNode node, + Func isNodeFullyInside, + Func isNodeFullyOutside, + Func isPositionInside, + int minCellExponent = int.MinValue + ) + { + if (node.Cell.Exponent < minCellExponent) return 0L; - if (isNodeFullyOutside(node)) return 0L; + if (isNodeFullyOutside(node)) return 0L; - if (node.IsLeaf() || node.Cell.Exponent == minCellExponent) + if (node.IsLeaf() || node.Cell.Exponent == minCellExponent) + { + if (isNodeFullyInside(node)) { - if (isNodeFullyInside(node)) - { - return node.Positions.Value.Length; - } - else // partially inside - { - var count = 0L; - var psRaw = node.PositionsAbsolute; - for (var i = 0; i < psRaw.Length; i++) - { - var p = psRaw[i]; - if (isPositionInside(p)) count++; - } - return count; - } + return node.Positions.Value.Length; } - else + else // partially inside { - var sum = 0L; - for (var i = 0; i < 8; i++) + var count = 0L; + var psRaw = node.PositionsAbsolute; + for (var i = 0; i < psRaw.Length; i++) { - var n = node.Subnodes![i]; - if (n == null) continue; - sum += CountPoints(n.Value, isNodeFullyInside, isNodeFullyOutside, isPositionInside, minCellExponent); + var p = psRaw[i]; + if (isPositionInside(p)) count++; } - return sum; + return count; } } + else + { + var sum = 0L; + for (var i = 0; i < 8; i++) + { + var n = node.Subnodes![i]; + if (n == null) continue; + sum += CountPoints(n.Value, isNodeFullyInside, isNodeFullyOutside, isPositionInside, minCellExponent); + } + return sum; + } + } - #endregion + #endregion - #region Count approximately + #region Count approximately - /// - /// Approximate count (cell granularity). - /// Result is always equal or greater than exact number. - /// - public static long CountPointsApproximately(this PointSet node, - Func isNodeFullyInside, - Func isNodeFullyOutside, - int minCellExponent = int.MinValue - ) - => CountPointsApproximately(node.Root.Value, isNodeFullyInside, isNodeFullyOutside, minCellExponent); + /// + /// Approximate count (cell granularity). + /// Result is always equal or greater than exact number. + /// + public static long CountPointsApproximately(this PointSet node, + Func isNodeFullyInside, + Func isNodeFullyOutside, + int minCellExponent = int.MinValue + ) + => CountPointsApproximately(node.Root.Value, isNodeFullyInside, isNodeFullyOutside, minCellExponent); - /// - /// Approximate count (cell granularity). - /// Result is always equal or greater than exact number. - /// - public static long CountPointsApproximately(this IPointCloudNode node, - Func isNodeFullyInside, - Func isNodeFullyOutside, - int minCellExponent = int.MinValue - ) - { - if (node.Cell.Exponent < minCellExponent) return 0L; + /// + /// Approximate count (cell granularity). + /// Result is always equal or greater than exact number. + /// + public static long CountPointsApproximately(this IPointCloudNode node, + Func isNodeFullyInside, + Func isNodeFullyOutside, + int minCellExponent = int.MinValue + ) + { + if (node.Cell.Exponent < minCellExponent) return 0L; - if (isNodeFullyOutside(node)) return 0L; + if (isNodeFullyOutside(node)) return 0L; - if (node.IsLeaf() || node.Cell.Exponent == minCellExponent) - { - return node.Positions.Value.Length; - } - else + if (node.IsLeaf() || node.Cell.Exponent == minCellExponent) + { + return node.Positions.Value.Length; + } + else + { + var sum = 0L; + for (var i = 0; i < 8; i++) { - var sum = 0L; - for (var i = 0; i < 8; i++) - { - var n = node.Subnodes![i]; - if (n == null) continue; - sum += CountPointsApproximately(n.Value, isNodeFullyInside, isNodeFullyOutside, minCellExponent); - } - return sum; + var n = node.Subnodes![i]; + if (n == null) continue; + sum += CountPointsApproximately(n.Value, isNodeFullyInside, isNodeFullyOutside, minCellExponent); } + return sum; } + } - #endregion + #endregion - #region QueryContainsPoints + #region QueryContainsPoints - /// - /// Exact count. - /// - public static bool QueryContainsPoints(this PointSet node, - Func isNodeFullyInside, - Func isNodeFullyOutside, - Func isPositionInside, - int minCellExponent = int.MinValue - ) - => QueryContainsPoints(node.Root.Value, isNodeFullyInside, isNodeFullyOutside, isPositionInside, minCellExponent); + /// + /// Exact count. + /// + public static bool QueryContainsPoints(this PointSet node, + Func isNodeFullyInside, + Func isNodeFullyOutside, + Func isPositionInside, + int minCellExponent = int.MinValue + ) + => QueryContainsPoints(node.Root.Value, isNodeFullyInside, isNodeFullyOutside, isPositionInside, minCellExponent); - /// - /// Exact count. - /// - public static bool QueryContainsPoints(this IPointCloudNode node, - Func isNodeFullyInside, - Func isNodeFullyOutside, - Func isPositionInside, - int minCellExponent = int.MinValue - ) - { - if (node.Cell.Exponent < minCellExponent) return false; + /// + /// Exact count. + /// + public static bool QueryContainsPoints(this IPointCloudNode node, + Func isNodeFullyInside, + Func isNodeFullyOutside, + Func isPositionInside, + int minCellExponent = int.MinValue + ) + { + if (node.Cell.Exponent < minCellExponent) return false; - if (isNodeFullyOutside(node)) return false; + if (isNodeFullyOutside(node)) return false; - if (node.IsLeaf() || node.Cell.Exponent == minCellExponent) + if (node.IsLeaf() || node.Cell.Exponent == minCellExponent) + { + if (isNodeFullyInside(node)) { - if (isNodeFullyInside(node)) - { - return true; - } - else // partially inside - { - var psRaw = node.PositionsAbsolute; - for (var i = 0; i < psRaw.Length; i++) - { - var p = psRaw[i]; - if (isPositionInside(p)) return true; - } - return false; - } + return true; } - else + else // partially inside { - for (var i = 0; i < 8; i++) + var psRaw = node.PositionsAbsolute; + for (var i = 0; i < psRaw.Length; i++) { - var n = node.Subnodes![i]; - if (n == null) continue; - if (QueryContainsPoints(n.Value, isNodeFullyInside, isNodeFullyOutside, isPositionInside, minCellExponent)) return true; + var p = psRaw[i]; + if (isPositionInside(p)) return true; } return false; } } - - #endregion + else + { + for (var i = 0; i < 8; i++) + { + var n = node.Subnodes![i]; + if (n == null) continue; + if (QueryContainsPoints(n.Value, isNodeFullyInside, isNodeFullyOutside, isPositionInside, minCellExponent)) return true; + } + return false; + } } + + #endregion } diff --git a/src/Aardvark.Geometry.PointSet/Queries/QueriesGrid.cs b/src/Aardvark.Geometry.PointSet/Queries/QueriesGrid.cs index eda60a58..255545d9 100644 --- a/src/Aardvark.Geometry.PointSet/Queries/QueriesGrid.cs +++ b/src/Aardvark.Geometry.PointSet/Queries/QueriesGrid.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -17,409 +17,403 @@ You should have received a copy of the GNU Affero General Public License using System.Collections.Generic; using System.Linq; -namespace Aardvark.Geometry.Points +#pragma warning disable CS9113 // Parameter is unread. + +namespace Aardvark.Geometry.Points; + +/// +/// +public static partial class Queries { + #region grid query (cell stride) + /// + /// Enumerate over point cloud in a grid of cells of size gridCellExponent. /// - public static partial class Queries - { - #region grid query (cell stride) + public static IEnumerable EnumerateGridCellsXY( + this PointSet self, int gridCellExponent + ) + => new GridQueryXY(self.Root.Value).EnumerateGridCellsXY(gridCellExponent); - /// - /// Enumerate over point cloud in a grid of cells of size gridCellExponent. - /// - public static IEnumerable EnumerateGridCellsXY( - this PointSet self, int gridCellExponent - ) - => new GridQueryXY(self.Root.Value).EnumerateGridCellsXY(gridCellExponent); + /// + /// Enumerate over point cloud in a grid of cells of size gridCellExponent. + /// Empty grid cells are skipped. + /// + public static IEnumerable EnumerateGridCellsXY( + this IPointCloudNode self, int gridCellExponent + ) + => new GridQueryXY(self).EnumerateGridCellsXY(gridCellExponent); - /// - /// Enumerate over point cloud in a grid of cells of size gridCellExponent. - /// Empty grid cells are skipped. - /// - public static IEnumerable EnumerateGridCellsXY( - this IPointCloudNode self, int gridCellExponent - ) - => new GridQueryXY(self).EnumerateGridCellsXY(gridCellExponent); + public interface IGridQueryXY + { + public long Count { get; } + public Cell2d Footprint { get; } + public IEnumerable CollectPoints(int minCellExponent = int.MinValue); + public IEnumerable EnumerateGridCellsXY(int subgridCellExponent); + } + + public class GridQueryXY : IGridQueryXY + { + private IPointCloudNode[] Roots { get; } + private Chunk Rest { get; } + public Cell2d Footprint { get; } + //public int GridCellExponent { get; } + public long Count { get; } - public interface IGridQueryXY + public GridQueryXY(IPointCloudNode root) { - public long Count { get; } - public Cell2d Footprint { get; } - public IEnumerable CollectPoints(int minCellExponent = int.MinValue); - public IEnumerable EnumerateGridCellsXY(int subgridCellExponent); + Footprint = new Cell2d(root.Cell.X, root.Cell.Y, root.Cell.Exponent); + Roots = [root]; + Rest = Chunk.Empty; + Count = root.PointCountTree; + //GridCellExponent = gridCellExponent; } - public class GridQueryXY : IGridQueryXY + private GridQueryXY(Cell2d footprint, IPointCloudNode[]? roots, Chunk rest) { - private IPointCloudNode[] Roots { get; } - private Chunk Rest { get; } - public Cell2d Footprint { get; } - //public int GridCellExponent { get; } - public long Count { get; } + if ((roots == null || roots.Length == 0) && (rest == null || rest.Count == 0)) + throw new InvalidOperationException("Invariant 0ee8c852-9580-44fb-9c19-a9f2f2dd7c93."); + + Footprint = footprint; + Roots = roots ?? []; + Rest = rest ?? Chunk.Empty; + //GridCellExponent = gridCellExponent; + Count = Roots.Sum(r => r.PointCountTree) + Rest.Count; + } - public GridQueryXY(IPointCloudNode root) + /// + /// Get all points in this grid cell. + /// + public IEnumerable CollectPoints(int minCellExponent = int.MinValue) + { + if (Rest.Count > 0) { - Footprint = new Cell2d(root.Cell.X, root.Cell.Y, root.Cell.Exponent); - Roots = new[] { root }; - Rest = Chunk.Empty; - Count = root.PointCountTree; - //GridCellExponent = gridCellExponent; + yield return Rest; } - private GridQueryXY(Cell2d footprint, IPointCloudNode[]? roots, Chunk rest) + foreach (var root in Roots) { - if ((roots == null || roots.Length == 0) && (rest == null || rest.Count == 0)) - throw new InvalidOperationException("Invariant 0ee8c852-9580-44fb-9c19-a9f2f2dd7c93."); - - Footprint = footprint; - Roots = roots ?? Array.Empty(); - Rest = rest ?? Chunk.Empty; - //GridCellExponent = gridCellExponent; - Count = Roots.Sum(r => r.PointCountTree) + Rest.Count; + foreach (var chunk in root.QueryAllPoints(minCellExponent)) + { + yield return chunk; + } } + } - /// - /// Get all points in this grid cell. - /// - public IEnumerable CollectPoints(int minCellExponent = int.MinValue) + /// + /// Enumerate over this grid cell in a grid of subcells of size subgridCellExponent. + /// + public IEnumerable EnumerateGridCellsXY(int subgridCellExponent) + { + if (Footprint.Exponent <= subgridCellExponent) { - if (Rest.Count > 0) - { - yield return Rest; - } + yield return this; + } + else + { + var hasRest = Rest.Count > 0; + var hasRoots = Roots.Length > 0; - foreach (var root in Roots) + if (!hasRoots && Rest.Count == 1) { - foreach (var chunk in root.QueryAllPoints(minCellExponent)) + + var fp = Footprint; + while (fp.Exponent > subgridCellExponent) { - yield return chunk; + var c = fp.GetCenter(); + var p = Rest.Positions[0].XY; + var i = (p.X < c.X) ? (p.Y < c.Y ? 0 : 2) : (p.Y < c.Y ? 1 : 3); + fp = fp.GetQuadrant(i); } - } - } - - /// - /// Enumerate over this grid cell in a grid of subcells of size subgridCellExponent. - /// - public IEnumerable EnumerateGridCellsXY(int subgridCellExponent) - { - if (Footprint.Exponent <= subgridCellExponent) - { - yield return this; + yield return new GridQueryXY(fp, Roots, Rest); } else { - var hasRest = Rest.Count > 0; - var hasRoots = Roots.Length > 0; - - if (!hasRoots && Rest.Count == 1) + var c = Footprint.GetCenter(); + var qs = Footprint.Children; + var qbbs = qs.Map(q => q.BoundingBox); + + // split rest ... + var newRests = hasRest + ? qbbs.Map(Rest.ImmutableFilterByBoxXY) + : new Chunk[4].Set(Chunk.Empty) + ; + + // split roots ... + List[]? newRoots = null; + void addRoot(int i, IPointCloudNode? n) { - - var fp = Footprint; - while (fp.Exponent > subgridCellExponent) - { - var c = fp.GetCenter(); - var p = Rest.Positions[0].XY; - var i = (p.X < c.X) ? (p.Y < c.Y ? 0 : 2) : (p.Y < c.Y ? 1 : 3); - fp = fp.GetQuadrant(i); - } - yield return new GridQueryXY(fp, Roots, Rest); + if (n == null) return; + newRoots ??= new List[4]; + if (newRoots[i] == null) newRoots[i] = [n]; + else newRoots[i].Add(n); } - else + foreach (var r in Roots) { - var c = Footprint.GetCenter(); - var qs = Footprint.Children; - var qbbs = qs.Map(q => q.BoundingBox); - - // split rest ... - var newRests = hasRest - ? qbbs.Map(Rest.ImmutableFilterByBoxXY) - : new Chunk[4].Set(Chunk.Empty) - ; - - // split roots ... - List[]? newRoots = null; - void addRoot(int i, IPointCloudNode? n) + if (r.IsLeaf) { - if (n == null) return; - newRoots ??= new List[4]; - if (newRoots[i] == null) newRoots[i] = new List { n }; - else newRoots[i].Add(n); + var leafChunk = r.ToChunk(); + qbbs.Map((bb, i) => + newRests[i] = newRests[i].ImmutableMergeWith(leafChunk.ImmutableFilterByBoxXY(bb)) + ); } - foreach (var r in Roots) + else { - if (r.IsLeaf) - { - var leafChunk = r.ToChunk(); - qbbs.Map((bb, i) => - newRests[i] = newRests[i].ImmutableMergeWith(leafChunk.ImmutableFilterByBoxXY(bb)) - ); - } - else - { - var ns = r.Subnodes!; - for (var i = 0; i < 8; i++) addRoot(i & 0b11, ns[i]?.Value); - } + var ns = r.Subnodes!; + for (var i = 0; i < 8; i++) addRoot(i & 0b11, ns[i]?.Value); } + } - // foreach quadrant: yield grid cells (recursively) - for (var i = 0; i < 4; i++) - { - var a = newRoots?[i]?.ToArray(); - var b = newRests[i]; - if (a == null && b.Count == 0) continue; - var qgrid = new GridQueryXY(qs[i], a, b); - foreach (var x in qgrid.EnumerateGridCellsXY(subgridCellExponent)) yield return x; - } + // foreach quadrant: yield grid cells (recursively) + for (var i = 0; i < 4; i++) + { + var a = newRoots?[i]?.ToArray(); + var b = newRests[i]; + if (a == null && b.Count == 0) continue; + var qgrid = new GridQueryXY(qs[i], a, b); + foreach (var x in qgrid.EnumerateGridCellsXY(subgridCellExponent)) yield return x; } } } } + } - //public class GridQueryResult - //{ - // /// Grid cell bounding box. - // public Cell2d Footprint { get; } - - // /// Total number of points in grid cell. - // public long Count { get; } - - // /// All points in cell. - // public Chunk[] Points { get; } - - // public GridQueryResult(Cell2d footprint, IEnumerable points) - // { - // Footprint = footprint; - // Points = points; - // } - //} - - ///// - ///// - //public static IEnumerable QueryGridXY( - // this PointSet self, int stride, int minCellExponent = int.MinValue - // ) - // => QueryGridXY(self.Root.Value, stride, minCellExponent); - - ///// - ///// - //public static IEnumerable QueryGridXY( - // this IPointCloudNode self, int stride, int minCellExponent = int.MinValue - // ) - //{ - // var bbw = self.BoundingBoxExactGlobal; // bounding box (world space) - // var bbt = new Box2l( // bounding box (tile space) - // new V2l((long)Math.Floor(bbw.Min.X / stride.X), (long)Math.Floor(bbw.Min.Y / stride.Y)), - // new V2l((long)Math.Floor(bbw.Max.X / stride.X) + 1L, (long)Math.Floor(bbw.Max.Y / stride.Y) + 1L) - // ); - - // return QueryRecGridXY(bbt, stride, minCellExponent, new List { self }); - //} - - //private static IEnumerable QueryRecGridXY(Box2l bb, int stride, int minCellExponent, List roots) - //{ - // var area = bb.Area; - // if (area == 0 || roots.Count == 0) yield break; - - // var q = new Box2d(bb.Min.X * stride.X, bb.Min.Y * stride.Y, bb.Max.X * stride.X, bb.Max.Y * stride.Y); - - // if (area == 1) - // { - // yield return new GridQueryBox2dResult(q, roots.SelectMany(root => root.QueryPointsInsideBoxXY(q))); - // } - // else - // { - // var newRoots = new List(); - // foreach (var r in roots) - // { - // if (r.IsLeaf) newRoots.Add(r); - // else - // { - // var _bb = r.BoundingBoxExactGlobal.XY; - // if (!q.Intersects(_bb)) { } - // else if (q.Contains(_bb)) newRoots.Add(r); - // else - // { - // var sub = r.Subnodes; - // void add(int i) { if (sub[i] != null) { newRoots.Add(sub[i].Value); } } - // var c = r.Center.XY; - // if (q.Max.X < c.X) - // { - // // left cells - // if (q.Max.Y < c.Y) { add(0); add(4); } // left/bottom - // else if (q.Min.Y >= c.Y) { add(2); add(6); } // left/top - // else { add(0); add(4); add(2); add(6); } - // } - // else if (q.Min.X >= c.X) - // { - // // right cells - // if (q.Max.Y < c.Y) { add(1); add(5); } // right/bottom - // else if (q.Min.Y >= c.Y) { add(3); add(7); } // right/top - // else { add(1); add(5); add(3); add(7); } - // } - // else - // { - // // left/right cells - // if (q.Max.Y < c.Y) { add(0); add(1); add(4); add(5); } // bottom - // else if (q.Min.Y >= c.Y) { add(2); add(3); add(6); add(7); } // top - // else { newRoots.Add(r); } - // } - // } - // } - // } - - // var sbbs = bb.SplitAtCenter(); - // foreach (var sbb in sbbs) - // { - // if (sbb.Min.X == sbb.Max.X || sbb.Min.Y == sbb.Max.Y) continue; - // var xs = QueryRecGridXY(sbb, stride, minCellExponent, newRoots); - // foreach (var x in xs) yield return x; - // } - // } - //} - - #endregion - - #region grid query (arbitrary stride) - - public class GridQueryBox2dResult - { - /// Grid cell bounding box. - public Box2d Footprint { get; } - - /// - public IEnumerable Points { get; } + //public class GridQueryResult + //{ + // /// Grid cell bounding box. + // public Cell2d Footprint { get; } + + // /// Total number of points in grid cell. + // public long Count { get; } + + // /// All points in cell. + // public Chunk[] Points { get; } + + // public GridQueryResult(Cell2d footprint, IEnumerable points) + // { + // Footprint = footprint; + // Points = points; + // } + //} + + ///// + ///// + //public static IEnumerable QueryGridXY( + // this PointSet self, int stride, int minCellExponent = int.MinValue + // ) + // => QueryGridXY(self.Root.Value, stride, minCellExponent); + + ///// + ///// + //public static IEnumerable QueryGridXY( + // this IPointCloudNode self, int stride, int minCellExponent = int.MinValue + // ) + //{ + // var bbw = self.BoundingBoxExactGlobal; // bounding box (world space) + // var bbt = new Box2l( // bounding box (tile space) + // new V2l((long)Math.Floor(bbw.Min.X / stride.X), (long)Math.Floor(bbw.Min.Y / stride.Y)), + // new V2l((long)Math.Floor(bbw.Max.X / stride.X) + 1L, (long)Math.Floor(bbw.Max.Y / stride.Y) + 1L) + // ); + + // return QueryRecGridXY(bbt, stride, minCellExponent, new List { self }); + //} + + //private static IEnumerable QueryRecGridXY(Box2l bb, int stride, int minCellExponent, List roots) + //{ + // var area = bb.Area; + // if (area == 0 || roots.Count == 0) yield break; + + // var q = new Box2d(bb.Min.X * stride.X, bb.Min.Y * stride.Y, bb.Max.X * stride.X, bb.Max.Y * stride.Y); + + // if (area == 1) + // { + // yield return new GridQueryBox2dResult(q, roots.SelectMany(root => root.QueryPointsInsideBoxXY(q))); + // } + // else + // { + // var newRoots = new List(); + // foreach (var r in roots) + // { + // if (r.IsLeaf) newRoots.Add(r); + // else + // { + // var _bb = r.BoundingBoxExactGlobal.XY; + // if (!q.Intersects(_bb)) { } + // else if (q.Contains(_bb)) newRoots.Add(r); + // else + // { + // var sub = r.Subnodes; + // void add(int i) { if (sub[i] != null) { newRoots.Add(sub[i].Value); } } + // var c = r.Center.XY; + // if (q.Max.X < c.X) + // { + // // left cells + // if (q.Max.Y < c.Y) { add(0); add(4); } // left/bottom + // else if (q.Min.Y >= c.Y) { add(2); add(6); } // left/top + // else { add(0); add(4); add(2); add(6); } + // } + // else if (q.Min.X >= c.X) + // { + // // right cells + // if (q.Max.Y < c.Y) { add(1); add(5); } // right/bottom + // else if (q.Min.Y >= c.Y) { add(3); add(7); } // right/top + // else { add(1); add(5); add(3); add(7); } + // } + // else + // { + // // left/right cells + // if (q.Max.Y < c.Y) { add(0); add(1); add(4); add(5); } // bottom + // else if (q.Min.Y >= c.Y) { add(2); add(3); add(6); add(7); } // top + // else { newRoots.Add(r); } + // } + // } + // } + // } + + // var sbbs = bb.SplitAtCenter(); + // foreach (var sbb in sbbs) + // { + // if (sbb.Min.X == sbb.Max.X || sbb.Min.Y == sbb.Max.Y) continue; + // var xs = QueryRecGridXY(sbb, stride, minCellExponent, newRoots); + // foreach (var x in xs) yield return x; + // } + // } + //} + + #endregion + + #region grid query (arbitrary stride) - public GridQueryBox2dResult(Box2d footprint, IEnumerable points) - { - Footprint = footprint; - Points = points; - } - } + /// + /// + /// Grid cell bounding box. + /// + public class GridQueryBox2dResult(Box2d Footprint, IEnumerable Points) + { + } - /// - /// - public static IEnumerable QueryGridXY( - this PointSet self, V2d stride, int minCellExponent = int.MinValue - ) - => QueryGridXY(self.Root.Value, stride, minCellExponent); + /// + /// + public static IEnumerable QueryGridXY( + this PointSet self, V2d stride, int minCellExponent = int.MinValue + ) + => QueryGridXY(self.Root.Value, stride, minCellExponent); - /// - /// - public static IEnumerable QueryGridXY( - this IPointCloudNode self, V2d stride, int maxInMemoryPointCount = 10 * 1024 * 1024, int minCellExponent = int.MinValue - ) - { - var bbw = self.BoundingBoxExactGlobal; // bounding box (world space) - var bbt = new Box2l( // bounding box (tile space) - new V2l((long)Math.Floor(bbw.Min.X / stride.X), (long)Math.Floor(bbw.Min.Y / stride.Y)), - new V2l((long)Math.Floor(bbw.Max.X / stride.X) + 1L, (long)Math.Floor(bbw.Max.Y / stride.Y) + 1L) - ) ; + /// + /// + public static IEnumerable QueryGridXY( + this IPointCloudNode self, V2d stride, int maxInMemoryPointCount = 10 * 1024 * 1024, int minCellExponent = int.MinValue + ) + { + var bbw = self.BoundingBoxExactGlobal; // bounding box (world space) + var bbt = new Box2l( // bounding box (tile space) + new V2l((long)Math.Floor(bbw.Min.X / stride.X), (long)Math.Floor(bbw.Min.Y / stride.Y)), + new V2l((long)Math.Floor(bbw.Max.X / stride.X) + 1L, (long)Math.Floor(bbw.Max.Y / stride.Y) + 1L) + ) ; - return QueryGridRecXY(bbt, stride, maxInMemoryPointCount, minCellExponent, new List { self }); - } + return QueryGridRecXY(bbt, stride, maxInMemoryPointCount, minCellExponent, [self]); + } - private static IEnumerable QueryGridRecInMemoryXY(Box2l bb, V2d stride, Chunk chunk) - { - var area = bb.Area; - if (area == 0 || chunk.Count == 0) yield break; + private static IEnumerable QueryGridRecInMemoryXY(Box2l bb, V2d stride, Chunk chunk) + { + var area = bb.Area; + if (area == 0 || chunk.Count == 0) yield break; - var q = new Box2d(bb.Min.X * stride.X, bb.Min.Y * stride.Y, bb.Max.X * stride.X, bb.Max.Y * stride.Y); + var q = new Box2d(bb.Min.X * stride.X, bb.Min.Y * stride.Y, bb.Max.X * stride.X, bb.Max.Y * stride.Y); - var newChunk = chunk.ImmutableFilterByBoxXY(q); - if (newChunk.Count == 0) yield break; + var newChunk = chunk.ImmutableFilterByBoxXY(q); + if (newChunk.Count == 0) yield break; - if (area == 1) - { - yield return new GridQueryBox2dResult(q, new[] { newChunk }); - } - else + if (area == 1) + { + yield return new GridQueryBox2dResult(q, [newChunk]); + } + else + { + var sbbs = bb.SplitAtCenter(); + foreach (var sbb in sbbs) { - var sbbs = bb.SplitAtCenter(); - foreach (var sbb in sbbs) - { - if (sbb.Min.X == sbb.Max.X || sbb.Min.Y == sbb.Max.Y) continue; - var xs = QueryGridRecInMemoryXY(sbb, stride, newChunk); - foreach (var x in xs) yield return x; - } + if (sbb.Min.X == sbb.Max.X || sbb.Min.Y == sbb.Max.Y) continue; + var xs = QueryGridRecInMemoryXY(sbb, stride, newChunk); + foreach (var x in xs) yield return x; } } - private static IEnumerable QueryGridRecXY(Box2l bb, V2d stride, int maxInMemoryPointCount, int minCellExponent, List roots) - { - var area = bb.Area; - if (area == 0 || roots.Count == 0) yield break; + } + private static IEnumerable QueryGridRecXY(Box2l bb, V2d stride, int maxInMemoryPointCount, int minCellExponent, List roots) + { + var area = bb.Area; + if (area == 0 || roots.Count == 0) yield break; - var q = new Box2d(bb.Min.X * stride.X, bb.Min.Y * stride.Y, bb.Max.X * stride.X, bb.Max.Y * stride.Y); + var q = new Box2d(bb.Min.X * stride.X, bb.Min.Y * stride.Y, bb.Max.X * stride.X, bb.Max.Y * stride.Y); - if (area == 1) - { - yield return new GridQueryBox2dResult(q, roots.SelectMany(root => root.QueryPointsInsideBoxXY(q))); - } - else + if (area == 1) + { + yield return new GridQueryBox2dResult(q, roots.SelectMany(root => root.QueryPointsInsideBoxXY(q))); + } + else + { + var newRoots = new List(); + foreach (var r in roots) { - var newRoots = new List(); - foreach (var r in roots) + if (r.IsLeaf) newRoots.Add(r); + else { - if (r.IsLeaf) newRoots.Add(r); + var _bb = r.BoundingBoxExactGlobal.XY; + if (!q.Intersects(_bb)) { } + else if (q.Contains(_bb)) newRoots.Add(r); else { - var _bb = r.BoundingBoxExactGlobal.XY; - if (!q.Intersects(_bb)) { } - else if (q.Contains(_bb)) newRoots.Add(r); + var sub = r.Subnodes!; + void add(int i) { if (sub[i] != null) { newRoots.Add(sub[i]!.Value); } } + var c = r.Center.XY; + if (q.Max.X < c.X) + { + // left cells + if (q.Max.Y < c.Y) { add(0); add(4); } // left/bottom + else if (q.Min.Y >= c.Y) { add(2); add(6); } // left/top + else { add(0); add(4); add(2); add(6); } + } + else if (q.Min.X >= c.X) + { + // right cells + if (q.Max.Y < c.Y) { add(1); add(5); } // right/bottom + else if (q.Min.Y >= c.Y) { add(3); add(7); } // right/top + else { add(1); add(5); add(3); add(7); } + } else { - var sub = r.Subnodes!; - void add(int i) { if (sub[i] != null) { newRoots.Add(sub[i]!.Value); } } - var c = r.Center.XY; - if (q.Max.X < c.X) - { - // left cells - if (q.Max.Y < c.Y) { add(0); add(4); } // left/bottom - else if (q.Min.Y >= c.Y) { add(2); add(6); } // left/top - else { add(0); add(4); add(2); add(6); } - } - else if (q.Min.X >= c.X) - { - // right cells - if (q.Max.Y < c.Y) { add(1); add(5); } // right/bottom - else if (q.Min.Y >= c.Y) { add(3); add(7); } // right/top - else { add(1); add(5); add(3); add(7); } - } - else - { - // left/right cells - if (q.Max.Y < c.Y) { add(0); add(1); add(4); add(5); } // bottom - else if (q.Min.Y >= c.Y) { add(2); add(3); add(6); add(7); } // top - else { newRoots.Add(r); } - } + // left/right cells + if (q.Max.Y < c.Y) { add(0); add(1); add(4); add(5); } // bottom + else if (q.Min.Y >= c.Y) { add(2); add(3); add(6); add(7); } // top + else { newRoots.Add(r); } } } } + } - var sbbs = bb.SplitAtCenter(); - var total = newRoots.Sum(r => r.PointCountTree); - if (total <= maxInMemoryPointCount) + var sbbs = bb.SplitAtCenter(); + var total = newRoots.Sum(r => r.PointCountTree); + if (total <= maxInMemoryPointCount) + { + var chunk = Chunk.ImmutableMerge(newRoots.SelectMany(r => r.QueryPointsInsideBoxXY(q))); + foreach (var sbb in sbbs) { - var chunk = Chunk.ImmutableMerge(newRoots.SelectMany(r => r.QueryPointsInsideBoxXY(q))); - foreach (var sbb in sbbs) - { - if (sbb.Min.X == sbb.Max.X || sbb.Min.Y == sbb.Max.Y) continue; - var xs = QueryGridRecInMemoryXY(sbb, stride, chunk); - foreach (var x in xs) yield return x; - } + if (sbb.Min.X == sbb.Max.X || sbb.Min.Y == sbb.Max.Y) continue; + var xs = QueryGridRecInMemoryXY(sbb, stride, chunk); + foreach (var x in xs) yield return x; } - else + } + else + { + foreach (var sbb in sbbs) { - foreach (var sbb in sbbs) - { - if (sbb.Min.X == sbb.Max.X || sbb.Min.Y == sbb.Max.Y) continue; - var xs = QueryGridRecXY(sbb, stride, maxInMemoryPointCount, minCellExponent, newRoots); - foreach (var x in xs) yield return x; - } + if (sbb.Min.X == sbb.Max.X || sbb.Min.Y == sbb.Max.Y) continue; + var xs = QueryGridRecXY(sbb, stride, maxInMemoryPointCount, minCellExponent, newRoots); + foreach (var x in xs) yield return x; } } } - - #endregion } + + #endregion } diff --git a/src/Aardvark.Geometry.PointSet/Queries/QueriesHull3d.cs b/src/Aardvark.Geometry.PointSet/Queries/QueriesHull3d.cs index c4ebc66e..80c8d9be 100644 --- a/src/Aardvark.Geometry.PointSet/Queries/QueriesHull3d.cs +++ b/src/Aardvark.Geometry.PointSet/Queries/QueriesHull3d.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -16,136 +16,135 @@ You should have received a copy of the GNU Affero General Public License using System.Collections.Generic; using System.Linq; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static partial class Queries { + #region Query points + + /// + /// All points inside convex hull (including boundary). + /// + public static IEnumerable QueryPointsInsideConvexHull( + this PointSet self, Hull3d query, int minCellExponent = int.MinValue + ) + => QueryPointsInsideConvexHull(self.Root.Value, query, minCellExponent); + + /// + /// All points inside convex hull (including boundary). + /// + public static IEnumerable QueryPointsInsideConvexHull( + this IPointCloudNode self, Hull3d query, int minCellExponent = int.MinValue + ) + => QueryPoints(self, + n => query.Contains(n.BoundingBoxExactGlobal), + n => !query.Intersects(n.BoundingBoxExactGlobal), + p => query.Contains(p), + minCellExponent); + + /// + /// All points outside convex hull (excluding boundary). + /// + public static IEnumerable QueryPointsOutsideConvexHull( + this PointSet self, Hull3d query, int minCellExponent = int.MinValue + ) + => QueryPointsOutsideConvexHull(self.Root.Value, query, minCellExponent); + + /// + /// All points outside convex hull (excluding boundary). + /// + public static IEnumerable QueryPointsOutsideConvexHull( + this IPointCloudNode self, Hull3d query, int minCellExponent = int.MinValue + ) + => QueryPointsInsideConvexHull(self, query.Reversed(), minCellExponent); + + #endregion + + #region Count exact + + /// + /// Counts points inside convex hull. + /// + internal static long CountPointsInsideConvexHull( + this PointSet self, Hull3d query, int minCellExponent = int.MinValue + ) + => CountPointsInsideConvexHull(self.Root.Value, query, minCellExponent); + + /// + /// Counts points inside convex hull. + /// + internal static long CountPointsInsideConvexHull( + this IPointCloudNode self, Hull3d query, int minCellExponent = int.MinValue + ) + => CountPoints(self, + n => query.Contains(n.BoundingBoxExactGlobal), + n => !query.Intersects(n.BoundingBoxExactGlobal), + p => query.Contains(p), + minCellExponent); + + /// + /// Counts points outside convex hull. + /// + internal static long CountPointsOutsideConvexHull( + this PointSet self, Hull3d query, int minCellExponent = int.MinValue + ) + => CountPointsOutsideConvexHull(self.Root.Value, query, minCellExponent); + + /// + /// Counts points outside convex hull. + /// + internal static long CountPointsOutsideConvexHull( + this IPointCloudNode self, Hull3d query, int minCellExponent = int.MinValue + ) + => CountPointsInsideConvexHull(self, query.Reversed(), minCellExponent); + + #endregion + + #region Count approximately + + /// + /// Counts points inside convex hull (approximately). + /// Result is always equal or greater than exact number. + /// + internal static long CountPointsApproximatelyInsideConvexHull( + this PointSet self, Hull3d query, int minCellExponent = int.MinValue + ) + => CountPointsApproximatelyInsideConvexHull(self.Root.Value, query, minCellExponent); + + /// + /// Counts points inside convex hull (approximately). + /// Result is always equal or greater than exact number. + /// + internal static long CountPointsApproximatelyInsideConvexHull( + this IPointCloudNode self, Hull3d query, int minCellExponent = int.MinValue + ) + => CountPointsApproximately(self, + n => query.Contains(n.BoundingBoxExactGlobal), + n => !query.Intersects(n.BoundingBoxExactGlobal), + minCellExponent); + + /// + /// Counts points outside convex hull (approximately). + /// Result is always equal or greater than exact number. + /// + internal static long CountPointsApproximatelyOutsideConvexHull( + this PointSet self, Hull3d query, int minCellExponent = int.MinValue + ) + => CountPointsApproximatelyOutsideConvexHull(self.Root.Value, query, minCellExponent); + /// + /// Counts points outside convex hull (approximately). + /// Result is always equal or greater than exact number. /// - public static partial class Queries - { - #region Query points - - /// - /// All points inside convex hull (including boundary). - /// - public static IEnumerable QueryPointsInsideConvexHull( - this PointSet self, Hull3d query, int minCellExponent = int.MinValue - ) - => QueryPointsInsideConvexHull(self.Root.Value, query, minCellExponent); - - /// - /// All points inside convex hull (including boundary). - /// - public static IEnumerable QueryPointsInsideConvexHull( - this IPointCloudNode self, Hull3d query, int minCellExponent = int.MinValue - ) - => QueryPoints(self, - n => query.Contains(n.BoundingBoxExactGlobal), - n => !query.Intersects(n.BoundingBoxExactGlobal), - p => query.Contains(p), - minCellExponent); - - /// - /// All points outside convex hull (excluding boundary). - /// - public static IEnumerable QueryPointsOutsideConvexHull( - this PointSet self, Hull3d query, int minCellExponent = int.MinValue - ) - => QueryPointsOutsideConvexHull(self.Root.Value, query, minCellExponent); - - /// - /// All points outside convex hull (excluding boundary). - /// - public static IEnumerable QueryPointsOutsideConvexHull( - this IPointCloudNode self, Hull3d query, int minCellExponent = int.MinValue - ) - => QueryPointsInsideConvexHull(self, query.Reversed(), minCellExponent); - - #endregion - - #region Count exact - - /// - /// Counts points inside convex hull. - /// - internal static long CountPointsInsideConvexHull( - this PointSet self, Hull3d query, int minCellExponent = int.MinValue - ) - => CountPointsInsideConvexHull(self.Root.Value, query, minCellExponent); - - /// - /// Counts points inside convex hull. - /// - internal static long CountPointsInsideConvexHull( - this IPointCloudNode self, Hull3d query, int minCellExponent = int.MinValue - ) - => CountPoints(self, - n => query.Contains(n.BoundingBoxExactGlobal), - n => !query.Intersects(n.BoundingBoxExactGlobal), - p => query.Contains(p), - minCellExponent); - - /// - /// Counts points outside convex hull. - /// - internal static long CountPointsOutsideConvexHull( - this PointSet self, Hull3d query, int minCellExponent = int.MinValue - ) - => CountPointsOutsideConvexHull(self.Root.Value, query, minCellExponent); - - /// - /// Counts points outside convex hull. - /// - internal static long CountPointsOutsideConvexHull( - this IPointCloudNode self, Hull3d query, int minCellExponent = int.MinValue - ) - => CountPointsInsideConvexHull(self, query.Reversed(), minCellExponent); - - #endregion - - #region Count approximately - - /// - /// Counts points inside convex hull (approximately). - /// Result is always equal or greater than exact number. - /// - internal static long CountPointsApproximatelyInsideConvexHull( - this PointSet self, Hull3d query, int minCellExponent = int.MinValue - ) - => CountPointsApproximatelyInsideConvexHull(self.Root.Value, query, minCellExponent); - - /// - /// Counts points inside convex hull (approximately). - /// Result is always equal or greater than exact number. - /// - internal static long CountPointsApproximatelyInsideConvexHull( - this IPointCloudNode self, Hull3d query, int minCellExponent = int.MinValue - ) - => CountPointsApproximately(self, - n => query.Contains(n.BoundingBoxExactGlobal), - n => !query.Intersects(n.BoundingBoxExactGlobal), - minCellExponent); - - /// - /// Counts points outside convex hull (approximately). - /// Result is always equal or greater than exact number. - /// - internal static long CountPointsApproximatelyOutsideConvexHull( - this PointSet self, Hull3d query, int minCellExponent = int.MinValue - ) - => CountPointsApproximatelyOutsideConvexHull(self.Root.Value, query, minCellExponent); - - /// - /// Counts points outside convex hull (approximately). - /// Result is always equal or greater than exact number. - /// - internal static long CountPointsApproximatelyOutsideConvexHull( - this IPointCloudNode self, Hull3d query, int minCellExponent = int.MinValue - ) - => CountPointsApproximately(self, - n => !query.Intersects(n.BoundingBoxExactGlobal), - n => query.Contains(n.BoundingBoxExactGlobal), - minCellExponent); - - #endregion - } + internal static long CountPointsApproximatelyOutsideConvexHull( + this IPointCloudNode self, Hull3d query, int minCellExponent = int.MinValue + ) + => CountPointsApproximately(self, + n => !query.Intersects(n.BoundingBoxExactGlobal), + n => query.Contains(n.BoundingBoxExactGlobal), + minCellExponent); + + #endregion } diff --git a/src/Aardvark.Geometry.PointSet/Queries/QueriesOctreeLevels.cs b/src/Aardvark.Geometry.PointSet/Queries/QueriesOctreeLevels.cs index 55c3c90d..ab6d320e 100644 --- a/src/Aardvark.Geometry.PointSet/Queries/QueriesOctreeLevels.cs +++ b/src/Aardvark.Geometry.PointSet/Queries/QueriesOctreeLevels.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -17,266 +17,265 @@ You should have received a copy of the GNU Affero General Public License using System.Collections.Generic; using System.Linq; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static partial class Queries { /// + /// Max tree depth. + /// + public static int CountOctreeLevels(this PointSet self) + => CountOctreeLevels(self.Root.Value); + + /// + /// Max tree depth. + /// + public static int CountOctreeLevels(this IPointCloudNode? root) + { + if (root == null) return 0; + if (root.Subnodes == null) return 1; + return root.Subnodes.Select(n => CountOctreeLevels(n?.Value)).Max() + 1; + } + + + + /// + /// Finds deepest octree level which still contains less than given number of points. + /// + public static int GetMaxOctreeLevelWithLessThanGivenPointCount( + this PointSet self, long maxPointCount + ) + => GetMaxOctreeLevelWithLessThanGivenPointCount(self.Root.Value, maxPointCount); + + /// + /// Finds deepest octree level which still contains less than given number of points. /// - public static partial class Queries + public static int GetMaxOctreeLevelWithLessThanGivenPointCount( + this IPointCloudNode node, long maxPointCount + ) { - /// - /// Max tree depth. - /// - public static int CountOctreeLevels(this PointSet self) - => CountOctreeLevels(self.Root.Value); - - /// - /// Max tree depth. - /// - public static int CountOctreeLevels(this IPointCloudNode? root) + var imax = node.CountOctreeLevels(); + for (var i = 0; i < imax; i++) { - if (root == null) return 0; - if (root.Subnodes == null) return 1; - return root.Subnodes.Select(n => CountOctreeLevels(n?.Value)).Max() + 1; + var count = node.CountPointsInOctreeLevel(i); + if (count >= maxPointCount) return i - 1; } - - - - /// - /// Finds deepest octree level which still contains less than given number of points. - /// - public static int GetMaxOctreeLevelWithLessThanGivenPointCount( - this PointSet self, long maxPointCount - ) - => GetMaxOctreeLevelWithLessThanGivenPointCount(self.Root.Value, maxPointCount); - - /// - /// Finds deepest octree level which still contains less than given number of points. - /// - public static int GetMaxOctreeLevelWithLessThanGivenPointCount( - this IPointCloudNode node, long maxPointCount - ) - { - var imax = node.CountOctreeLevels(); - for (var i = 0; i < imax; i++) - { - var count = node.CountPointsInOctreeLevel(i); - if (count >= maxPointCount) return i - 1; - } - return imax - 1; + return imax - 1; + } + + + + /// + /// Finds deepest octree level which still contains less than given number of points within given bounds. + /// + public static int GetMaxOctreeLevelWithLessThanGivenPointCount( + this PointSet self, long maxPointCount, Box3d bounds + ) + => GetMaxOctreeLevelWithLessThanGivenPointCount(self.Root.Value, maxPointCount, bounds); + + /// + /// Finds deepest octree level which still contains less than given number of points within given bounds. + /// + public static int GetMaxOctreeLevelWithLessThanGivenPointCount( + this IPointCloudNode node, long maxPointCount, Box3d bounds + ) + { + var imax = node.CountOctreeLevels(); + for (var i = 0; i < imax; i++) + { + var count = node.CountPointsInOctreeLevel(i, bounds); + if (count >= maxPointCount) return i - 1; } + return imax - 1; + } + + + /// + /// Gets total number of points in all cells at given octree level. + /// + public static long CountPointsInOctreeLevel( + this PointSet self, int level + ) + => CountPointsInOctreeLevel(self.Root.Value, level); - /// - /// Finds deepest octree level which still contains less than given number of points within given bounds. - /// - public static int GetMaxOctreeLevelWithLessThanGivenPointCount( - this PointSet self, long maxPointCount, Box3d bounds - ) - => GetMaxOctreeLevelWithLessThanGivenPointCount(self.Root.Value, maxPointCount, bounds); + /// + /// Gets total number of lod-points in all cells at given octree level. + /// + public static long CountPointsInOctreeLevel( + this IPointCloudNode node, int level + ) + { + if (level < 0) return 0; - /// - /// Finds deepest octree level which still contains less than given number of points within given bounds. - /// - public static int GetMaxOctreeLevelWithLessThanGivenPointCount( - this IPointCloudNode node, long maxPointCount, Box3d bounds - ) + if (level == 0 || node.IsLeaf()) + { + return node.Positions.Value.Count(); + } + else { - var imax = node.CountOctreeLevels(); - for (var i = 0; i < imax; i++) + var nextLevel = level - 1; + var sum = 0L; + for (var i = 0; i < 8; i++) { - var count = node.CountPointsInOctreeLevel(i, bounds); - if (count >= maxPointCount) return i - 1; + var n = node.Subnodes![i]; + if (n == null) continue; + sum += CountPointsInOctreeLevel(n.Value, nextLevel); } - - return imax - 1; + return sum; } + } - /// - /// Gets total number of points in all cells at given octree level. - /// - public static long CountPointsInOctreeLevel( - this PointSet self, int level - ) - => CountPointsInOctreeLevel(self.Root.Value, level); + /// + /// Gets approximate number of points at given octree level within given bounds. + /// For cells that only partially overlap the specified bounds all points are counted anyway. + /// For performance reasons, in order to avoid per-point bounds checks. + /// + public static long CountPointsInOctreeLevel( + this PointSet self, int level, Box3d bounds + ) + => CountPointsInOctreeLevel(self.Root.Value, level, bounds); - /// - /// Gets total number of lod-points in all cells at given octree level. - /// - public static long CountPointsInOctreeLevel( - this IPointCloudNode node, int level - ) - { - if (level < 0) return 0; + /// + /// Gets approximate number of points at given octree level within given bounds. + /// For cells that only partially overlap the specified bounds all points are counted anyway. + /// For performance reasons, in order to avoid per-point bounds checks. + /// + public static long CountPointsInOctreeLevel( + this IPointCloudNode node, int level, Box3d bounds + ) + { + if (level < 0) return 0; + if (!node.BoundingBoxExactGlobal.Intersects(bounds)) return 0; - if (level == 0 || node.IsLeaf()) - { - return node.Positions.Value.Count(); - } - else + if (level == 0 || node.IsLeaf()) + { + return node.Positions.Value.Length; + } + else + { + var sum = 0L; + if (node.Subnodes != null) { var nextLevel = level - 1; - var sum = 0L; for (var i = 0; i < 8; i++) { - var n = node.Subnodes![i]; + var n = node.Subnodes[i]; if (n == null) continue; - sum += CountPointsInOctreeLevel(n.Value, nextLevel); + sum += CountPointsInOctreeLevel(n.Value, nextLevel, bounds); } - return sum; } + return sum; } + } + + + /// + /// Returns points in given octree level, where level 0 is the root node. + /// + public static IEnumerable QueryPointsInOctreeLevel( + this PointSet self, int level + ) + => QueryPointsInOctreeLevel(self.Root.Value, level); + /// + /// Returns LoD points for given octree depth/front, where level 0 is the root node. + /// Front will include leafs higher up than given level. + /// + public static IEnumerable QueryPointsInOctreeLevel( + this IPointCloudNode node, int level + ) + { + if (level < 0) yield break; - /// - /// Gets approximate number of points at given octree level within given bounds. - /// For cells that only partially overlap the specified bounds all points are counted anyway. - /// For performance reasons, in order to avoid per-point bounds checks. - /// - public static long CountPointsInOctreeLevel( - this PointSet self, int level, Box3d bounds - ) - => CountPointsInOctreeLevel(self.Root.Value, level, bounds); - - /// - /// Gets approximate number of points at given octree level within given bounds. - /// For cells that only partially overlap the specified bounds all points are counted anyway. - /// For performance reasons, in order to avoid per-point bounds checks. - /// - public static long CountPointsInOctreeLevel( - this IPointCloudNode node, int level, Box3d bounds - ) + if (level == 0 || node.IsLeaf()) { - if (level < 0) return 0; - if (!node.BoundingBoxExactGlobal.Intersects(bounds)) return 0; + var ps = node.PositionsAbsolute; - if (level == 0 || node.IsLeaf()) - { - return node.Positions.Value.Length; - } - else + T[]? Verified(T[]? xs, string name) { - var sum = 0L; - if (node.Subnodes != null) - { - var nextLevel = level - 1; - for (var i = 0; i < 8; i++) - { - var n = node.Subnodes[i]; - if (n == null) continue; - sum += CountPointsInOctreeLevel(n.Value, nextLevel, bounds); - } - } - return sum; + if (ps == null || xs == null) return xs; + + if (ps.Length == xs.Length) return xs; + Report.ErrorNoPrefix($"[QueryPointsInOctreeLevel] inconsistent length: {ps.Length} positions, but {xs.Length} {name}."); + + var rs = new T[ps.Length]; + if (rs.Length == 0) return rs; + var lastX = xs[xs.Length - 1]; + var imax = Math.Min(ps.Length, xs.Length); + for (var i = 0; i < imax; i++) rs[i] = xs[i]; + for (var i = imax; i < ps.Length; i++) rs[i] = lastX; + return rs; } + var cs = Verified(node.TryGetColors4b()?.Value, "colors"); + var ns = Verified(node.TryGetNormals3f()?.Value, "normals"); + var js = Verified(node.TryGetIntensities()?.Value, "intensities"); + var ks = Verified(node.TryGetClassifications()?.Value, "classifications"); + var qs = node.PartIndices; + + var chunk = new Chunk(ps, cs, ns, js, ks, qs, partIndexRange: null, bbox: null); + yield return chunk; } - - - - /// - /// Returns points in given octree level, where level 0 is the root node. - /// - public static IEnumerable QueryPointsInOctreeLevel( - this PointSet self, int level - ) - => QueryPointsInOctreeLevel(self.Root.Value, level); - - /// - /// Returns LoD points for given octree depth/front, where level 0 is the root node. - /// Front will include leafs higher up than given level. - /// - public static IEnumerable QueryPointsInOctreeLevel( - this IPointCloudNode node, int level - ) + else { - if (level < 0) yield break; + if (node.Subnodes == null) yield break; - if (level == 0 || node.IsLeaf()) + for (var i = 0; i < 8; i++) { - var ps = node.PositionsAbsolute; - - T[]? Verified(T[]? xs, string name) - { - if (ps == null || xs == null) return xs; - - if (ps.Length == xs.Length) return xs; - Report.ErrorNoPrefix($"[QueryPointsInOctreeLevel] inconsistent length: {ps.Length} positions, but {xs.Length} {name}."); - - var rs = new T[ps.Length]; - if (rs.Length == 0) return rs; - var lastX = xs[xs.Length - 1]; - var imax = Math.Min(ps.Length, xs.Length); - for (var i = 0; i < imax; i++) rs[i] = xs[i]; - for (var i = imax; i < ps.Length; i++) rs[i] = lastX; - return rs; - } - var cs = Verified(node.TryGetColors4b()?.Value, "colors"); - var ns = Verified(node.TryGetNormals3f()?.Value, "normals"); - var js = Verified(node.TryGetIntensities()?.Value, "intensities"); - var ks = Verified(node.TryGetClassifications()?.Value, "classifications"); - var qs = node.PartIndices; - - var chunk = new Chunk(ps, cs, ns, js, ks, qs, partIndexRange: null, bbox: null); - yield return chunk; - } - else - { - if (node.Subnodes == null) yield break; - - for (var i = 0; i < 8; i++) - { - var n = node.Subnodes[i]; - if (n == null) continue; - foreach (var x in QueryPointsInOctreeLevel(n.Value, level - 1)) yield return x; - } + var n = node.Subnodes[i]; + if (n == null) continue; + foreach (var x in QueryPointsInOctreeLevel(n.Value, level - 1)) yield return x; } } + } - /// - /// Returns lod points for given octree depth/front of cells intersecting given bounds, where level 0 is the root node. - /// Front will include leafs higher up than given level. - /// - public static IEnumerable QueryPointsInOctreeLevel( - this PointSet self, int level, Box3d bounds - ) - => QueryPointsInOctreeLevel(self.Root.Value, level, bounds); + /// + /// Returns lod points for given octree depth/front of cells intersecting given bounds, where level 0 is the root node. + /// Front will include leafs higher up than given level. + /// + public static IEnumerable QueryPointsInOctreeLevel( + this PointSet self, int level, Box3d bounds + ) + => QueryPointsInOctreeLevel(self.Root.Value, level, bounds); - /// - /// Returns lod points for given octree depth/front of cells intersecting given bounds, where level 0 is the root node. - /// Front will include leafs higher up than given level. - /// - public static IEnumerable QueryPointsInOctreeLevel( - this IPointCloudNode node, int level, Box3d bounds - ) + /// + /// Returns lod points for given octree depth/front of cells intersecting given bounds, where level 0 is the root node. + /// Front will include leafs higher up than given level. + /// + public static IEnumerable QueryPointsInOctreeLevel( + this IPointCloudNode node, int level, Box3d bounds + ) + { + if (level < 0) yield break; + if (!node.BoundingBoxExactGlobal.Intersects(bounds)) yield break; + + if (level == 0 || node.IsLeaf()) + { + var chunk = new Chunk( + node.PositionsAbsolute, node.Colors?.Value, node.Normals?.Value, node.Intensities?.Value, node.Classifications?.Value, + node.PartIndices, partIndexRange: null, + bbox: null + ); + yield return chunk; + } + else { - if (level < 0) yield break; - if (!node.BoundingBoxExactGlobal.Intersects(bounds)) yield break; + if (node.Subnodes == null) yield break; - if (level == 0 || node.IsLeaf()) + for (var i = 0; i < 8; i++) { - var chunk = new Chunk( - node.PositionsAbsolute, node.Colors?.Value, node.Normals?.Value, node.Intensities?.Value, node.Classifications?.Value, - node.PartIndices, partIndexRange: null, - bbox: null - ); - yield return chunk; - } - else - { - if (node.Subnodes == null) yield break; - - for (var i = 0; i < 8; i++) - { - var n = node.Subnodes[i]; - if (n == null) continue; - foreach (var x in QueryPointsInOctreeLevel(n.Value, level - 1, bounds)) yield return x; - } + var n = node.Subnodes[i]; + if (n == null) continue; + foreach (var x in QueryPointsInOctreeLevel(n.Value, level - 1, bounds)) yield return x; } } } diff --git a/src/Aardvark.Geometry.PointSet/Queries/QueriesPlane3d.cs b/src/Aardvark.Geometry.PointSet/Queries/QueriesPlane3d.cs index b396d56e..72b3c4fd 100644 --- a/src/Aardvark.Geometry.PointSet/Queries/QueriesPlane3d.cs +++ b/src/Aardvark.Geometry.PointSet/Queries/QueriesPlane3d.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -17,286 +17,285 @@ You should have received a copy of the GNU Affero General Public License using Aardvark.Base; using Aardvark.Data.Points; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static partial class Queries { + #region Query points + /// + /// All points within maxDistance of given plane. /// - public static partial class Queries - { - #region Query points + public static IEnumerable QueryPointsNearPlane( + this PointSet self, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue + ) + => QueryPointsNearPlane(self.Root.Value, plane, maxDistance, minCellExponent); - /// - /// All points within maxDistance of given plane. - /// - public static IEnumerable QueryPointsNearPlane( - this PointSet self, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue - ) - => QueryPointsNearPlane(self.Root.Value, plane, maxDistance, minCellExponent); - - /// - /// All points within maxDistance of given plane. - /// - public static IEnumerable QueryPointsNearPlane( - this IPointCloudNode node, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue - ) - => QueryPoints(node, - n => plane.Contains(maxDistance, node.BoundingBoxExactGlobal), - n => !node.BoundingBoxExactGlobal.Intersects(plane, maxDistance), - p => Math.Abs(plane.Height(p)) <= maxDistance, - minCellExponent - ); + /// + /// All points within maxDistance of given plane. + /// + public static IEnumerable QueryPointsNearPlane( + this IPointCloudNode node, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue + ) + => QueryPoints(node, + n => plane.Contains(maxDistance, node.BoundingBoxExactGlobal), + n => !node.BoundingBoxExactGlobal.Intersects(plane, maxDistance), + p => Math.Abs(plane.Height(p)) <= maxDistance, + minCellExponent + ); - /// - /// All points within maxDistance of ANY of the given planes. - /// - public static IEnumerable QueryPointsNearPlanes( - this PointSet self, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue - ) - => QueryPointsNearPlanes(self.Root.Value, planes, maxDistance, minCellExponent); + /// + /// All points within maxDistance of ANY of the given planes. + /// + public static IEnumerable QueryPointsNearPlanes( + this PointSet self, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue + ) + => QueryPointsNearPlanes(self.Root.Value, planes, maxDistance, minCellExponent); - /// - /// All points within maxDistance of ANY of the given planes. - /// - public static IEnumerable QueryPointsNearPlanes( - this IPointCloudNode node, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue - ) - => QueryPoints(node, - n => planes.Any(plane => plane.Contains(maxDistance, node.BoundingBoxExactGlobal)), - n => !planes.Any(plane => node.BoundingBoxExactGlobal.Intersects(plane, maxDistance)), - p => planes.Any(plane => Math.Abs(plane.Height(p)) <= maxDistance), - minCellExponent - ); + /// + /// All points within maxDistance of ANY of the given planes. + /// + public static IEnumerable QueryPointsNearPlanes( + this IPointCloudNode node, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue + ) + => QueryPoints(node, + n => planes.Any(plane => plane.Contains(maxDistance, node.BoundingBoxExactGlobal)), + n => !planes.Any(plane => node.BoundingBoxExactGlobal.Intersects(plane, maxDistance)), + p => planes.Any(plane => Math.Abs(plane.Height(p)) <= maxDistance), + minCellExponent + ); - /// - /// All points NOT within maxDistance of given plane. - /// - public static IEnumerable QueryPointsNotNearPlane( - this PointSet self, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue - ) - => QueryPointsNotNearPlane(self.Root.Value, plane, maxDistance, minCellExponent); + /// + /// All points NOT within maxDistance of given plane. + /// + public static IEnumerable QueryPointsNotNearPlane( + this PointSet self, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue + ) + => QueryPointsNotNearPlane(self.Root.Value, plane, maxDistance, minCellExponent); - /// - /// All points NOT within maxDistance of given plane. - /// - public static IEnumerable QueryPointsNotNearPlane( - this IPointCloudNode node, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue - ) - => QueryPoints(node, - n => !node.BoundingBoxExactGlobal.Intersects(plane, maxDistance), - n => plane.Contains(maxDistance, node.BoundingBoxExactGlobal), - p => Math.Abs(plane.Height(p)) > maxDistance, - minCellExponent - ); + /// + /// All points NOT within maxDistance of given plane. + /// + public static IEnumerable QueryPointsNotNearPlane( + this IPointCloudNode node, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue + ) + => QueryPoints(node, + n => !node.BoundingBoxExactGlobal.Intersects(plane, maxDistance), + n => plane.Contains(maxDistance, node.BoundingBoxExactGlobal), + p => Math.Abs(plane.Height(p)) > maxDistance, + minCellExponent + ); - /// - /// All points NOT within maxDistance of ALL the given planes. - /// - public static IEnumerable QueryPointsNotNearPlanes( - this PointSet self, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue - ) - => QueryPointsNotNearPlanes(self.Root.Value, planes, maxDistance, minCellExponent); + /// + /// All points NOT within maxDistance of ALL the given planes. + /// + public static IEnumerable QueryPointsNotNearPlanes( + this PointSet self, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue + ) + => QueryPointsNotNearPlanes(self.Root.Value, planes, maxDistance, minCellExponent); - /// - /// All points NOT within maxDistance of ALL the given planes. - /// - public static IEnumerable QueryPointsNotNearPlanes( - this IPointCloudNode node, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue - ) - => QueryPoints(node, - n => !planes.Any(plane => node.BoundingBoxExactGlobal.Intersects(plane, maxDistance)), - n => planes.Any(plane => plane.Contains(maxDistance, node.BoundingBoxExactGlobal)), - p => !planes.Any(plane => Math.Abs(plane.Height(p)) <= maxDistance), - minCellExponent - ); + /// + /// All points NOT within maxDistance of ALL the given planes. + /// + public static IEnumerable QueryPointsNotNearPlanes( + this IPointCloudNode node, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue + ) + => QueryPoints(node, + n => !planes.Any(plane => node.BoundingBoxExactGlobal.Intersects(plane, maxDistance)), + n => planes.Any(plane => plane.Contains(maxDistance, node.BoundingBoxExactGlobal)), + p => !planes.Any(plane => Math.Abs(plane.Height(p)) <= maxDistance), + minCellExponent + ); - #endregion + #endregion - #region Count exact + #region Count exact - /// - /// Count points within maxDistance of given plane. - /// - public static long CountPointsNearPlane( - this PointSet self, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsNearPlane(self.Root.Value, plane, maxDistance, minCellExponent); + /// + /// Count points within maxDistance of given plane. + /// + public static long CountPointsNearPlane( + this PointSet self, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsNearPlane(self.Root.Value, plane, maxDistance, minCellExponent); - /// - /// Count points within maxDistance of given plane. - /// - public static long CountPointsNearPlane( - this IPointCloudNode node, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPoints(node, - n => plane.Contains(maxDistance, node.BoundingBoxExactGlobal), - n => !node.BoundingBoxExactGlobal.Intersects(plane, maxDistance), - p => Math.Abs(plane.Height(p)) <= maxDistance, - minCellExponent - ); + /// + /// Count points within maxDistance of given plane. + /// + public static long CountPointsNearPlane( + this IPointCloudNode node, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPoints(node, + n => plane.Contains(maxDistance, node.BoundingBoxExactGlobal), + n => !node.BoundingBoxExactGlobal.Intersects(plane, maxDistance), + p => Math.Abs(plane.Height(p)) <= maxDistance, + minCellExponent + ); - /// - /// Count points within maxDistance of ANY of the given planes. - /// - public static long CountPointsNearPlanes( - this PointSet self, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsNearPlanes(self.Root.Value, planes, maxDistance, minCellExponent); + /// + /// Count points within maxDistance of ANY of the given planes. + /// + public static long CountPointsNearPlanes( + this PointSet self, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsNearPlanes(self.Root.Value, planes, maxDistance, minCellExponent); - /// - /// Count points within maxDistance of ANY of the given planes. - /// - public static long CountPointsNearPlanes( - this IPointCloudNode node, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPoints(node, - n => planes.Any(plane => plane.Contains(maxDistance, node.BoundingBoxExactGlobal)), - n => !planes.Any(plane => node.BoundingBoxExactGlobal.Intersects(plane, maxDistance)), - p => planes.Any(plane => Math.Abs(plane.Height(p)) <= maxDistance), - minCellExponent - ); + /// + /// Count points within maxDistance of ANY of the given planes. + /// + public static long CountPointsNearPlanes( + this IPointCloudNode node, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPoints(node, + n => planes.Any(plane => plane.Contains(maxDistance, node.BoundingBoxExactGlobal)), + n => !planes.Any(plane => node.BoundingBoxExactGlobal.Intersects(plane, maxDistance)), + p => planes.Any(plane => Math.Abs(plane.Height(p)) <= maxDistance), + minCellExponent + ); - /// - /// Count points NOT within maxDistance of given plane. - /// - public static long CountPointsNotNearPlane( - this PointSet self, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsNotNearPlane(self.Root.Value, plane, maxDistance, minCellExponent); + /// + /// Count points NOT within maxDistance of given plane. + /// + public static long CountPointsNotNearPlane( + this PointSet self, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsNotNearPlane(self.Root.Value, plane, maxDistance, minCellExponent); - /// - /// Count points NOT within maxDistance of given plane. - /// - public static long CountPointsNotNearPlane( - this IPointCloudNode node, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPoints(node, - n => !node.BoundingBoxExactGlobal.Intersects(plane, maxDistance), - n => plane.Contains(maxDistance, node.BoundingBoxExactGlobal), - p => Math.Abs(plane.Height(p)) > maxDistance, - minCellExponent - ); + /// + /// Count points NOT within maxDistance of given plane. + /// + public static long CountPointsNotNearPlane( + this IPointCloudNode node, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPoints(node, + n => !node.BoundingBoxExactGlobal.Intersects(plane, maxDistance), + n => plane.Contains(maxDistance, node.BoundingBoxExactGlobal), + p => Math.Abs(plane.Height(p)) > maxDistance, + minCellExponent + ); - /// - /// Count points NOT within maxDistance of ALL the given planes. - /// - public static long CountPointsNotNearPlanes( - this PointSet self, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsNotNearPlanes(self.Root.Value, planes, maxDistance, minCellExponent); + /// + /// Count points NOT within maxDistance of ALL the given planes. + /// + public static long CountPointsNotNearPlanes( + this PointSet self, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsNotNearPlanes(self.Root.Value, planes, maxDistance, minCellExponent); - /// - /// Count points NOT within maxDistance of ALL the given planes. - /// - public static long CountPointsNotNearPlanes( - this IPointCloudNode node, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPoints(node, - n => !planes.Any(plane => node.BoundingBoxExactGlobal.Intersects(plane, maxDistance)), - n => planes.Any(plane => plane.Contains(maxDistance, node.BoundingBoxExactGlobal)), - p => !planes.Any(plane => Math.Abs(plane.Height(p)) <= maxDistance), - minCellExponent - ); + /// + /// Count points NOT within maxDistance of ALL the given planes. + /// + public static long CountPointsNotNearPlanes( + this IPointCloudNode node, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPoints(node, + n => !planes.Any(plane => node.BoundingBoxExactGlobal.Intersects(plane, maxDistance)), + n => planes.Any(plane => plane.Contains(maxDistance, node.BoundingBoxExactGlobal)), + p => !planes.Any(plane => Math.Abs(plane.Height(p)) <= maxDistance), + minCellExponent + ); - #endregion + #endregion - #region Count approximately + #region Count approximately - /// - /// Count points approximately within maxDistance of given plane. - /// Result is always equal or greater than exact number. - /// Faster than CountPointsNearPlane. - /// - public static long CountPointsApproximatelyNearPlane( - this PointSet self, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsApproximatelyNearPlane(self.Root.Value, plane, maxDistance, minCellExponent); + /// + /// Count points approximately within maxDistance of given plane. + /// Result is always equal or greater than exact number. + /// Faster than CountPointsNearPlane. + /// + public static long CountPointsApproximatelyNearPlane( + this PointSet self, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsApproximatelyNearPlane(self.Root.Value, plane, maxDistance, minCellExponent); - /// - /// Count points approximately within maxDistance of given plane. - /// Result is always equal or greater than exact number. - /// Faster than CountPointsNearPlane. - /// - public static long CountPointsApproximatelyNearPlane( - this IPointCloudNode node, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsApproximately(node, - n => plane.Contains(maxDistance, node.BoundingBoxExactGlobal), - n => !node.BoundingBoxExactGlobal.Intersects(plane, maxDistance), - minCellExponent - ); + /// + /// Count points approximately within maxDistance of given plane. + /// Result is always equal or greater than exact number. + /// Faster than CountPointsNearPlane. + /// + public static long CountPointsApproximatelyNearPlane( + this IPointCloudNode node, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsApproximately(node, + n => plane.Contains(maxDistance, node.BoundingBoxExactGlobal), + n => !node.BoundingBoxExactGlobal.Intersects(plane, maxDistance), + minCellExponent + ); - /// - /// Count points approximately within maxDistance of ANY of the given planes. - /// Result is always equal or greater than exact number. - /// Faster than CountPointsNearPlanes. - /// - public static long CountPointsApproximatelyNearPlanes( - this PointSet self, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsApproximatelyNearPlanes(self.Root.Value, planes, maxDistance, minCellExponent); + /// + /// Count points approximately within maxDistance of ANY of the given planes. + /// Result is always equal or greater than exact number. + /// Faster than CountPointsNearPlanes. + /// + public static long CountPointsApproximatelyNearPlanes( + this PointSet self, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsApproximatelyNearPlanes(self.Root.Value, planes, maxDistance, minCellExponent); - /// - /// Count points approximately within maxDistance of ANY of the given planes. - /// Result is always equal or greater than exact number. - /// Faster than CountPointsNearPlanes. - /// - public static long CountPointsApproximatelyNearPlanes( - this IPointCloudNode node, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsApproximately(node, - n => planes.Any(plane => plane.Contains(maxDistance, node.BoundingBoxExactGlobal)), - n => !planes.Any(plane => node.BoundingBoxExactGlobal.Intersects(plane, maxDistance)), - minCellExponent - ); + /// + /// Count points approximately within maxDistance of ANY of the given planes. + /// Result is always equal or greater than exact number. + /// Faster than CountPointsNearPlanes. + /// + public static long CountPointsApproximatelyNearPlanes( + this IPointCloudNode node, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsApproximately(node, + n => planes.Any(plane => plane.Contains(maxDistance, node.BoundingBoxExactGlobal)), + n => !planes.Any(plane => node.BoundingBoxExactGlobal.Intersects(plane, maxDistance)), + minCellExponent + ); - /// - /// Count points approximately NOT within maxDistance of given plane. - /// Result is always equal or greater than exact number. - /// Faster than CountPointsNotNearPlane. - /// - public static long CountPointsApproximatelyNotNearPlane( - this PointSet self, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsApproximatelyNotNearPlane(self.Root.Value, plane, maxDistance, minCellExponent); + /// + /// Count points approximately NOT within maxDistance of given plane. + /// Result is always equal or greater than exact number. + /// Faster than CountPointsNotNearPlane. + /// + public static long CountPointsApproximatelyNotNearPlane( + this PointSet self, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsApproximatelyNotNearPlane(self.Root.Value, plane, maxDistance, minCellExponent); - /// - /// Count points approximately NOT within maxDistance of given plane. - /// Result is always equal or greater than exact number. - /// Faster than CountPointsNotNearPlane. - /// - public static long CountPointsApproximatelyNotNearPlane( - this IPointCloudNode node, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsApproximately(node, - n => !node.BoundingBoxExactGlobal.Intersects(plane, maxDistance), - n => plane.Contains(maxDistance, node.BoundingBoxExactGlobal), - minCellExponent - ); + /// + /// Count points approximately NOT within maxDistance of given plane. + /// Result is always equal or greater than exact number. + /// Faster than CountPointsNotNearPlane. + /// + public static long CountPointsApproximatelyNotNearPlane( + this IPointCloudNode node, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsApproximately(node, + n => !node.BoundingBoxExactGlobal.Intersects(plane, maxDistance), + n => plane.Contains(maxDistance, node.BoundingBoxExactGlobal), + minCellExponent + ); - /// - /// Count points approximately NOT within maxDistance of ALL the given planes. - /// Result is always equal or greater than exact number. - /// Faster than CountPointsNotNearPlanes. - /// - public static long CountPointsApproximatelyNotNearPlanes( - this PointSet self, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsApproximatelyNotNearPlanes(self.Root.Value, planes, maxDistance, minCellExponent); + /// + /// Count points approximately NOT within maxDistance of ALL the given planes. + /// Result is always equal or greater than exact number. + /// Faster than CountPointsNotNearPlanes. + /// + public static long CountPointsApproximatelyNotNearPlanes( + this PointSet self, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsApproximatelyNotNearPlanes(self.Root.Value, planes, maxDistance, minCellExponent); - /// - /// Count points approximately NOT within maxDistance of ALL the given planes. - /// Result is always equal or greater than exact number. - /// Faster than CountPointsNotNearPlanes. - /// - public static long CountPointsApproximatelyNotNearPlanes( - this IPointCloudNode node, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsApproximately(node, - n => !planes.Any(plane => node.BoundingBoxExactGlobal.Intersects(plane, maxDistance)), - n => planes.Any(plane => plane.Contains(maxDistance, node.BoundingBoxExactGlobal)), - minCellExponent - ); - - #endregion - } + /// + /// Count points approximately NOT within maxDistance of ALL the given planes. + /// Result is always equal or greater than exact number. + /// Faster than CountPointsNotNearPlanes. + /// + public static long CountPointsApproximatelyNotNearPlanes( + this IPointCloudNode node, Plane3d[] planes, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsApproximately(node, + n => !planes.Any(plane => node.BoundingBoxExactGlobal.Intersects(plane, maxDistance)), + n => planes.Any(plane => plane.Contains(maxDistance, node.BoundingBoxExactGlobal)), + minCellExponent + ); + + #endregion } diff --git a/src/Aardvark.Geometry.PointSet/Queries/QueriesPolygon3d.cs b/src/Aardvark.Geometry.PointSet/Queries/QueriesPolygon3d.cs index b707cdd4..8c9164c4 100644 --- a/src/Aardvark.Geometry.PointSet/Queries/QueriesPolygon3d.cs +++ b/src/Aardvark.Geometry.PointSet/Queries/QueriesPolygon3d.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -16,358 +16,357 @@ You should have received a copy of the GNU Affero General Public License using System.Collections.Generic; using System.Linq; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static partial class Queries { + #region Query points + /// + /// All points within maxDistance of given polygon. /// - public static partial class Queries - { - #region Query points - - /// - /// All points within maxDistance of given polygon. - /// - public static IEnumerable QueryPointsNearPolygon( - this PointSet self, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue - ) - => QueryPointsNearPolygon(self.Root.Value, polygon, maxDistance, minCellExponent); - - /// - /// All points within maxDistance of given polygon. - /// - public static IEnumerable QueryPointsNearPolygon( - this IPointCloudNode node, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue - ) - { - var bounds = polygon.BoundingBox3d(maxDistance); - var plane = polygon.GetPlane3d(); - var w2p = plane.GetWorldToPlane(); - var poly2d = new Polygon2d(polygon.GetPointArray().Map(p => w2p.TransformPos(p).XY)); - return QueryPoints(node, - n => false, - n => !n.BoundingBoxExactGlobal.Intersects(bounds), - p => polygon.Contains(plane, w2p, poly2d, maxDistance, p, out double d), - minCellExponent - ); - } + public static IEnumerable QueryPointsNearPolygon( + this PointSet self, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue + ) + => QueryPointsNearPolygon(self.Root.Value, polygon, maxDistance, minCellExponent); - /// - /// All points within maxDistance of ANY of the given polygons. - /// - public static IEnumerable QueryPointsNearPolygons( - this PointSet self, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue - ) - => QueryPointsNearPolygons(self.Root.Value, polygons, maxDistance, minCellExponent); + /// + /// All points within maxDistance of given polygon. + /// + public static IEnumerable QueryPointsNearPolygon( + this IPointCloudNode node, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue + ) + { + var bounds = polygon.BoundingBox3d(maxDistance); + var plane = polygon.GetPlane3d(); + var w2p = plane.GetWorldToPlane(); + var poly2d = new Polygon2d(polygon.GetPointArray().Map(p => w2p.TransformPos(p).XY)); + return QueryPoints(node, + n => false, + n => !n.BoundingBoxExactGlobal.Intersects(bounds), + p => polygon.Contains(plane, w2p, poly2d, maxDistance, p, out double d), + minCellExponent + ); + } - /// - /// All points within maxDistance of ANY of the given polygons. - /// - public static IEnumerable QueryPointsNearPolygons( - this IPointCloudNode node, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue - ) - { - var bounds = polygons.Map(x => x.BoundingBox3d(maxDistance)); - var planes = polygons.Map(x => x.GetPlane3d()); - var w2p = planes.Map(x => x.GetWorldToPlane()); - var poly2d = polygons.Map((x, i) => new Polygon2d(x.GetPointArray().Map(p => w2p[i].TransformPos(p).XY))); - return QueryPoints(node, - n => false, - n => !bounds.Any(b => n.BoundingBoxExactGlobal.Intersects(b)), - p => planes.Any((plane, i) => polygons[i].Contains(plane, w2p[i], poly2d[i], maxDistance, p, out double d)), - minCellExponent - ); - } + /// + /// All points within maxDistance of ANY of the given polygons. + /// + public static IEnumerable QueryPointsNearPolygons( + this PointSet self, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue + ) + => QueryPointsNearPolygons(self.Root.Value, polygons, maxDistance, minCellExponent); - /// - /// All points NOT within maxDistance of given polygon. - /// - public static IEnumerable QueryPointsNotNearPolygon( - this PointSet self, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue - ) - => QueryPointsNotNearPolygon(self.Root.Value, polygon, maxDistance, minCellExponent); + /// + /// All points within maxDistance of ANY of the given polygons. + /// + public static IEnumerable QueryPointsNearPolygons( + this IPointCloudNode node, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue + ) + { + var bounds = polygons.Map(x => x.BoundingBox3d(maxDistance)); + var planes = polygons.Map(x => x.GetPlane3d()); + var w2p = planes.Map(x => x.GetWorldToPlane()); + var poly2d = polygons.Map((x, i) => new Polygon2d(x.GetPointArray().Map(p => w2p[i].TransformPos(p).XY))); + return QueryPoints(node, + n => false, + n => !bounds.Any(b => n.BoundingBoxExactGlobal.Intersects(b)), + p => planes.Any((plane, i) => polygons[i].Contains(plane, w2p[i], poly2d[i], maxDistance, p, out double d)), + minCellExponent + ); + } - /// - /// All points NOT within maxDistance of given polygon. - /// - public static IEnumerable QueryPointsNotNearPolygon( - this IPointCloudNode node, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue - ) - { - var bounds = polygon.BoundingBox3d(maxDistance); - var plane = polygon.GetPlane3d(); - var w2p = plane.GetWorldToPlane(); - var poly2d = new Polygon2d(polygon.GetPointArray().Map(p => w2p.TransformPos(p).XY)); - return QueryPoints(node, - n => !n.BoundingBoxExactGlobal.Intersects(bounds), - n => false, - p => !polygon.Contains(plane, w2p, poly2d, maxDistance, p, out double d), - minCellExponent - ); - } + /// + /// All points NOT within maxDistance of given polygon. + /// + public static IEnumerable QueryPointsNotNearPolygon( + this PointSet self, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue + ) + => QueryPointsNotNearPolygon(self.Root.Value, polygon, maxDistance, minCellExponent); - /// - /// All points NOT within maxDistance of ALL the given polygons. - /// - public static IEnumerable QueryPointsNotNearPolygons( - this PointSet self, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue - ) - => QueryPointsNotNearPolygons(self.Root.Value, polygons, maxDistance, minCellExponent); + /// + /// All points NOT within maxDistance of given polygon. + /// + public static IEnumerable QueryPointsNotNearPolygon( + this IPointCloudNode node, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue + ) + { + var bounds = polygon.BoundingBox3d(maxDistance); + var plane = polygon.GetPlane3d(); + var w2p = plane.GetWorldToPlane(); + var poly2d = new Polygon2d(polygon.GetPointArray().Map(p => w2p.TransformPos(p).XY)); + return QueryPoints(node, + n => !n.BoundingBoxExactGlobal.Intersects(bounds), + n => false, + p => !polygon.Contains(plane, w2p, poly2d, maxDistance, p, out double d), + minCellExponent + ); + } - /// - /// All points NOT within maxDistance of ALL the given polygons. - /// - public static IEnumerable QueryPointsNotNearPolygons( - this IPointCloudNode node, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue - ) - { - var bounds = polygons.Map(x => x.BoundingBox3d(maxDistance)); - var planes = polygons.Map(x => x.GetPlane3d()); - var w2p = planes.Map(x => x.GetWorldToPlane()); - var poly2d = polygons.Map((x, i) => new Polygon2d(x.GetPointArray().Map(p => w2p[i].TransformPos(p).XY))); - return QueryPoints(node, - n => !bounds.Any(b => n.BoundingBoxExactGlobal.Intersects(b)), - n => false, - p => !planes.Any((plane, i) => polygons[i].Contains(plane, w2p[i], poly2d[i], maxDistance, p, out double d)), - minCellExponent - ); - } + /// + /// All points NOT within maxDistance of ALL the given polygons. + /// + public static IEnumerable QueryPointsNotNearPolygons( + this PointSet self, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue + ) + => QueryPointsNotNearPolygons(self.Root.Value, polygons, maxDistance, minCellExponent); - #endregion - - #region Count exact + /// + /// All points NOT within maxDistance of ALL the given polygons. + /// + public static IEnumerable QueryPointsNotNearPolygons( + this IPointCloudNode node, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue + ) + { + var bounds = polygons.Map(x => x.BoundingBox3d(maxDistance)); + var planes = polygons.Map(x => x.GetPlane3d()); + var w2p = planes.Map(x => x.GetWorldToPlane()); + var poly2d = polygons.Map((x, i) => new Polygon2d(x.GetPointArray().Map(p => w2p[i].TransformPos(p).XY))); + return QueryPoints(node, + n => !bounds.Any(b => n.BoundingBoxExactGlobal.Intersects(b)), + n => false, + p => !planes.Any((plane, i) => polygons[i].Contains(plane, w2p[i], poly2d[i], maxDistance, p, out double d)), + minCellExponent + ); + } - /// - /// Count points within maxDistance of given polygon. - /// - public static long CountPointsNearPolygon( - this PointSet self, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsNearPolygon(self.Root.Value, polygon, maxDistance, minCellExponent); + #endregion + + #region Count exact - /// - /// Count points within maxDistance of given polygon. - /// - public static long CountPointsNearPolygon( - this IPointCloudNode node, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue - ) - { - var bounds = polygon.BoundingBox3d(maxDistance); - var plane = polygon.GetPlane3d(); - var w2p = plane.GetWorldToPlane(); - var poly2d = new Polygon2d(polygon.GetPointArray().Map(p => w2p.TransformPos(p).XY)); - return CountPoints(node, - n => false, - n => !n.BoundingBoxExactGlobal.Intersects(bounds), - p => polygon.Contains(plane, w2p, poly2d, maxDistance, p, out double d), - minCellExponent - ); - } + /// + /// Count points within maxDistance of given polygon. + /// + public static long CountPointsNearPolygon( + this PointSet self, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsNearPolygon(self.Root.Value, polygon, maxDistance, minCellExponent); - /// - /// Count points within maxDistance of ANY of the given polygons. - /// - public static long CountPointsNearPolygons( - this PointSet self, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsNearPolygons(self.Root.Value, polygons, maxDistance, minCellExponent); + /// + /// Count points within maxDistance of given polygon. + /// + public static long CountPointsNearPolygon( + this IPointCloudNode node, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue + ) + { + var bounds = polygon.BoundingBox3d(maxDistance); + var plane = polygon.GetPlane3d(); + var w2p = plane.GetWorldToPlane(); + var poly2d = new Polygon2d(polygon.GetPointArray().Map(p => w2p.TransformPos(p).XY)); + return CountPoints(node, + n => false, + n => !n.BoundingBoxExactGlobal.Intersects(bounds), + p => polygon.Contains(plane, w2p, poly2d, maxDistance, p, out double d), + minCellExponent + ); + } - /// - /// Count points within maxDistance of ANY of the given polygons. - /// - public static long CountPointsNearPolygons( - this IPointCloudNode node, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue - ) - { - var bounds = polygons.Map(x => x.BoundingBox3d(maxDistance)); - var planes = polygons.Map(x => x.GetPlane3d()); - var w2p = planes.Map(x => x.GetWorldToPlane()); - var poly2d = polygons.Map((x, i) => new Polygon2d(x.GetPointArray().Map(p => w2p[i].TransformPos(p).XY))); - return CountPoints(node, - n => false, - n => !bounds.Any(b => n.BoundingBoxExactGlobal.Intersects(b)), - p => planes.Any((plane, i) => polygons[i].Contains(plane, w2p[i], poly2d[i], maxDistance, p, out double d)), - minCellExponent - ); - } + /// + /// Count points within maxDistance of ANY of the given polygons. + /// + public static long CountPointsNearPolygons( + this PointSet self, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsNearPolygons(self.Root.Value, polygons, maxDistance, minCellExponent); - /// - /// Count points NOT within maxDistance of given polygon. - /// - public static long CountPointsNotNearPolygon( - this PointSet self, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsNotNearPolygon(self.Root.Value, polygon, maxDistance, minCellExponent); + /// + /// Count points within maxDistance of ANY of the given polygons. + /// + public static long CountPointsNearPolygons( + this IPointCloudNode node, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue + ) + { + var bounds = polygons.Map(x => x.BoundingBox3d(maxDistance)); + var planes = polygons.Map(x => x.GetPlane3d()); + var w2p = planes.Map(x => x.GetWorldToPlane()); + var poly2d = polygons.Map((x, i) => new Polygon2d(x.GetPointArray().Map(p => w2p[i].TransformPos(p).XY))); + return CountPoints(node, + n => false, + n => !bounds.Any(b => n.BoundingBoxExactGlobal.Intersects(b)), + p => planes.Any((plane, i) => polygons[i].Contains(plane, w2p[i], poly2d[i], maxDistance, p, out double d)), + minCellExponent + ); + } - /// - /// Count points NOT within maxDistance of given polygon. - /// - public static long CountPointsNotNearPolygon( - this IPointCloudNode node, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue - ) - { - var bounds = polygon.BoundingBox3d(maxDistance); - var plane = polygon.GetPlane3d(); - var w2p = plane.GetWorldToPlane(); - var poly2d = new Polygon2d(polygon.GetPointArray().Map(p => w2p.TransformPos(p).XY)); - return CountPoints(node, - n => !n.BoundingBoxExactGlobal.Intersects(bounds), - n => false, - p => !polygon.Contains(plane, w2p, poly2d, maxDistance, p, out double d), - minCellExponent - ); - } + /// + /// Count points NOT within maxDistance of given polygon. + /// + public static long CountPointsNotNearPolygon( + this PointSet self, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsNotNearPolygon(self.Root.Value, polygon, maxDistance, minCellExponent); - /// - /// Count points NOT within maxDistance of ALL the given polygons. - /// - public static long CountPointsNotNearPolygons( - this PointSet self, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsNotNearPolygons(self.Root.Value, polygons, maxDistance, minCellExponent); + /// + /// Count points NOT within maxDistance of given polygon. + /// + public static long CountPointsNotNearPolygon( + this IPointCloudNode node, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue + ) + { + var bounds = polygon.BoundingBox3d(maxDistance); + var plane = polygon.GetPlane3d(); + var w2p = plane.GetWorldToPlane(); + var poly2d = new Polygon2d(polygon.GetPointArray().Map(p => w2p.TransformPos(p).XY)); + return CountPoints(node, + n => !n.BoundingBoxExactGlobal.Intersects(bounds), + n => false, + p => !polygon.Contains(plane, w2p, poly2d, maxDistance, p, out double d), + minCellExponent + ); + } - /// - /// Count points NOT within maxDistance of ALL the given polygons. - /// - public static long CountPointsNotNearPolygons( - this IPointCloudNode node, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue - ) - { - var bounds = polygons.Map(x => x.BoundingBox3d(maxDistance)); - var planes = polygons.Map(x => x.GetPlane3d()); - var w2p = planes.Map(x => x.GetWorldToPlane()); - var poly2d = polygons.Map((x, i) => new Polygon2d(x.GetPointArray().Map(p => w2p[i].TransformPos(p).XY))); - return CountPoints(node, - n => !bounds.Any(b => n.BoundingBoxExactGlobal.Intersects(b)), - n => false, - p => !planes.Any((plane, i) => polygons[i].Contains(plane, w2p[i], poly2d[i], maxDistance, p, out double d)), - minCellExponent - ); - } + /// + /// Count points NOT within maxDistance of ALL the given polygons. + /// + public static long CountPointsNotNearPolygons( + this PointSet self, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsNotNearPolygons(self.Root.Value, polygons, maxDistance, minCellExponent); - #endregion + /// + /// Count points NOT within maxDistance of ALL the given polygons. + /// + public static long CountPointsNotNearPolygons( + this IPointCloudNode node, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue + ) + { + var bounds = polygons.Map(x => x.BoundingBox3d(maxDistance)); + var planes = polygons.Map(x => x.GetPlane3d()); + var w2p = planes.Map(x => x.GetWorldToPlane()); + var poly2d = polygons.Map((x, i) => new Polygon2d(x.GetPointArray().Map(p => w2p[i].TransformPos(p).XY))); + return CountPoints(node, + n => !bounds.Any(b => n.BoundingBoxExactGlobal.Intersects(b)), + n => false, + p => !planes.Any((plane, i) => polygons[i].Contains(plane, w2p[i], poly2d[i], maxDistance, p, out double d)), + minCellExponent + ); + } - #region Count approximately + #endregion - /// - /// Count points approximately within maxDistance of given polygon. - /// Result is always equal or greater than exact number. - /// Faster than CountPointsNearPolygon. - /// - public static long CountPointsApproximatelyNearPolygon( - this PointSet self, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsApproximatelyNearPolygon(self.Root.Value, polygon, maxDistance, minCellExponent); + #region Count approximately - /// - /// Count points approximately within maxDistance of given polygon. - /// Result is always equal or greater than exact number. - /// Faster than CountPointsNearPolygon. - /// - public static long CountPointsApproximatelyNearPolygon( - this IPointCloudNode node, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue - ) - { - var bounds = polygon.BoundingBox3d(maxDistance); - var plane = polygon.GetPlane3d(); - var w2p = plane.GetWorldToPlane(); - var poly2d = new Polygon2d(polygon.GetPointArray().Map(p => w2p.TransformPos(p).XY)); - return CountPointsApproximately(node, - n => false, - n => !n.BoundingBoxExactGlobal.Intersects(bounds), - minCellExponent - ); - } + /// + /// Count points approximately within maxDistance of given polygon. + /// Result is always equal or greater than exact number. + /// Faster than CountPointsNearPolygon. + /// + public static long CountPointsApproximatelyNearPolygon( + this PointSet self, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsApproximatelyNearPolygon(self.Root.Value, polygon, maxDistance, minCellExponent); - /// - /// Count points approximately within maxDistance of ANY of the given polygons. - /// Result is always equal or greater than exact number. - /// Faster than CountPointsNearPolygons. - /// - public static long CountPointsApproximatelyNearPolygons( - this PointSet self, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsApproximatelyNearPolygons(self.Root.Value, polygons, maxDistance, minCellExponent); + /// + /// Count points approximately within maxDistance of given polygon. + /// Result is always equal or greater than exact number. + /// Faster than CountPointsNearPolygon. + /// + public static long CountPointsApproximatelyNearPolygon( + this IPointCloudNode node, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue + ) + { + var bounds = polygon.BoundingBox3d(maxDistance); + var plane = polygon.GetPlane3d(); + var w2p = plane.GetWorldToPlane(); + var poly2d = new Polygon2d(polygon.GetPointArray().Map(p => w2p.TransformPos(p).XY)); + return CountPointsApproximately(node, + n => false, + n => !n.BoundingBoxExactGlobal.Intersects(bounds), + minCellExponent + ); + } - /// - /// Count points approximately within maxDistance of ANY of the given polygons. - /// Result is always equal or greater than exact number. - /// Faster than CountPointsNearPolygons. - /// - public static long CountPointsApproximatelyNearPolygons( - this IPointCloudNode node, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue - ) - { - var bounds = polygons.Map(x => x.BoundingBox3d(maxDistance)); - var planes = polygons.Map(x => x.GetPlane3d()); - var w2p = planes.Map(x => x.GetWorldToPlane()); - var poly2d = polygons.Map((x, i) => new Polygon2d(x.GetPointArray().Map(p => w2p[i].TransformPos(p).XY))); - return CountPointsApproximately(node, - n => false, - n => !bounds.Any(b => n.BoundingBoxExactGlobal.Intersects(b)), - minCellExponent - ); - } + /// + /// Count points approximately within maxDistance of ANY of the given polygons. + /// Result is always equal or greater than exact number. + /// Faster than CountPointsNearPolygons. + /// + public static long CountPointsApproximatelyNearPolygons( + this PointSet self, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsApproximatelyNearPolygons(self.Root.Value, polygons, maxDistance, minCellExponent); - /// - /// Count points approximately NOT within maxDistance of given polygon. - /// Result is always equal or greater than exact number. - /// Faster than CountPointsNotNearPolygon. - /// - public static long CountPointsApproximatelyNotNearPolygon( - this PointSet self, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsApproximatelyNotNearPolygon(self.Root.Value, polygon, maxDistance, minCellExponent); + /// + /// Count points approximately within maxDistance of ANY of the given polygons. + /// Result is always equal or greater than exact number. + /// Faster than CountPointsNearPolygons. + /// + public static long CountPointsApproximatelyNearPolygons( + this IPointCloudNode node, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue + ) + { + var bounds = polygons.Map(x => x.BoundingBox3d(maxDistance)); + var planes = polygons.Map(x => x.GetPlane3d()); + var w2p = planes.Map(x => x.GetWorldToPlane()); + var poly2d = polygons.Map((x, i) => new Polygon2d(x.GetPointArray().Map(p => w2p[i].TransformPos(p).XY))); + return CountPointsApproximately(node, + n => false, + n => !bounds.Any(b => n.BoundingBoxExactGlobal.Intersects(b)), + minCellExponent + ); + } - /// - /// Count points approximately NOT within maxDistance of given polygon. - /// Result is always equal or greater than exact number. - /// Faster than CountPointsNotNearPolygon. - /// - public static long CountPointsApproximatelyNotNearPolygon( - this IPointCloudNode node, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue - ) - { - var bounds = polygon.BoundingBox3d(maxDistance); - var plane = polygon.GetPlane3d(); - var w2p = plane.GetWorldToPlane(); - var poly2d = new Polygon2d(polygon.GetPointArray().Map(p => w2p.TransformPos(p).XY)); - return CountPointsApproximately(node, - n => !n.BoundingBoxExactGlobal.Intersects(bounds), - n => false, - minCellExponent - ); - } + /// + /// Count points approximately NOT within maxDistance of given polygon. + /// Result is always equal or greater than exact number. + /// Faster than CountPointsNotNearPolygon. + /// + public static long CountPointsApproximatelyNotNearPolygon( + this PointSet self, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsApproximatelyNotNearPolygon(self.Root.Value, polygon, maxDistance, minCellExponent); - /// - /// Count points approximately NOT within maxDistance of ALL the given polygons. - /// Result is always equal or greater than exact number. - /// Faster than CountPointsNotNearPolygons. - /// - public static long CountPointsApproximatelyNotNearPolygons( - this PointSet self, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue - ) - => CountPointsApproximatelyNotNearPolygons(self.Root.Value, polygons, maxDistance, minCellExponent); + /// + /// Count points approximately NOT within maxDistance of given polygon. + /// Result is always equal or greater than exact number. + /// Faster than CountPointsNotNearPolygon. + /// + public static long CountPointsApproximatelyNotNearPolygon( + this IPointCloudNode node, Polygon3d polygon, double maxDistance, int minCellExponent = int.MinValue + ) + { + var bounds = polygon.BoundingBox3d(maxDistance); + var plane = polygon.GetPlane3d(); + var w2p = plane.GetWorldToPlane(); + var poly2d = new Polygon2d(polygon.GetPointArray().Map(p => w2p.TransformPos(p).XY)); + return CountPointsApproximately(node, + n => !n.BoundingBoxExactGlobal.Intersects(bounds), + n => false, + minCellExponent + ); + } - /// - /// Count points approximately NOT within maxDistance of ALL the given polygons. - /// Result is always equal or greater than exact number. - /// Faster than CountPointsNotNearPolygons. - /// - public static long CountPointsApproximatelyNotNearPolygons( - this IPointCloudNode node, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue - ) - { - var bounds = polygons.Map(x => x.BoundingBox3d(maxDistance)); - var planes = polygons.Map(x => x.GetPlane3d()); - var w2p = planes.Map(x => x.GetWorldToPlane()); - var poly2d = polygons.Map((x, i) => new Polygon2d(x.GetPointArray().Map(p => w2p[i].TransformPos(p).XY))); - return CountPointsApproximately(node, - n => !bounds.Any(b => n.BoundingBoxExactGlobal.Intersects(b)), - n => false, - minCellExponent - ); - } + /// + /// Count points approximately NOT within maxDistance of ALL the given polygons. + /// Result is always equal or greater than exact number. + /// Faster than CountPointsNotNearPolygons. + /// + public static long CountPointsApproximatelyNotNearPolygons( + this PointSet self, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue + ) + => CountPointsApproximatelyNotNearPolygons(self.Root.Value, polygons, maxDistance, minCellExponent); - #endregion + /// + /// Count points approximately NOT within maxDistance of ALL the given polygons. + /// Result is always equal or greater than exact number. + /// Faster than CountPointsNotNearPolygons. + /// + public static long CountPointsApproximatelyNotNearPolygons( + this IPointCloudNode node, Polygon3d[] polygons, double maxDistance, int minCellExponent = int.MinValue + ) + { + var bounds = polygons.Map(x => x.BoundingBox3d(maxDistance)); + var planes = polygons.Map(x => x.GetPlane3d()); + var w2p = planes.Map(x => x.GetWorldToPlane()); + var poly2d = polygons.Map((x, i) => new Polygon2d(x.GetPointArray().Map(p => w2p[i].TransformPos(p).XY))); + return CountPointsApproximately(node, + n => !bounds.Any(b => n.BoundingBoxExactGlobal.Intersects(b)), + n => false, + minCellExponent + ); } + + #endregion } diff --git a/src/Aardvark.Geometry.PointSet/Queries/QueriesRayLine3d.cs b/src/Aardvark.Geometry.PointSet/Queries/QueriesRayLine3d.cs index e50fae25..119e29a0 100644 --- a/src/Aardvark.Geometry.PointSet/Queries/QueriesRayLine3d.cs +++ b/src/Aardvark.Geometry.PointSet/Queries/QueriesRayLine3d.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -11,264 +11,262 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ +using Aardvark.Base; +using Aardvark.Data; +using Aardvark.Data.Points; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using Aardvark.Base; -using Aardvark.Data; -using Aardvark.Data.Points; -using Microsoft.FSharp.Core; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static partial class Queries { /// + /// Points within given distance of a ray. /// - public static partial class Queries + public static IEnumerable QueryPointsNearRay( + this PointSet self, Ray3d ray, double maxDistanceToRay, int minCellExponent = int.MinValue + ) { - /// - /// Points within given distance of a ray. - /// - public static IEnumerable QueryPointsNearRay( - this PointSet self, Ray3d ray, double maxDistanceToRay, int minCellExponent = int.MinValue - ) - { - ray.Direction = ray.Direction.Normalized; - var data = self.Root.Value; - var bbox = data.BoundingBoxExactGlobal; + ray.Direction = ray.Direction.Normalized; + var data = self.Root.Value; + var bbox = data.BoundingBoxExactGlobal; - var line = Clip(bbox, ray); - if (!line.HasValue) return Enumerable.Empty(); + var line = Clip(bbox, ray); + if (!line.HasValue) return []; - return self.QueryPointsNearLineSegment(line.Value, maxDistanceToRay, minCellExponent); - } + return self.QueryPointsNearLineSegment(line.Value, maxDistanceToRay, minCellExponent); + } - /// - /// Points within given distance of a ray. - /// - public static IEnumerable QueryPointsNearRayCustom( - this PointSet self, Ray3d ray, double maxDistanceToRay, params Durable.Def[] customAttributes - ) - => QueryPointsNearRayCustom(self, ray, maxDistanceToRay, int.MinValue, customAttributes); - - /// - /// Points within given distance of a ray. - /// - public static IEnumerable QueryPointsNearRayCustom( - this PointSet self, Ray3d ray, double maxDistanceToRay, int minCellExponent, params Durable.Def[] customAttributes - ) - { - ray.Direction = ray.Direction.Normalized; - var data = self.Root.Value; - var bbox = data.BoundingBoxExactGlobal; + /// + /// Points within given distance of a ray. + /// + public static IEnumerable QueryPointsNearRayCustom( + this PointSet self, Ray3d ray, double maxDistanceToRay, params Durable.Def[] customAttributes + ) + => QueryPointsNearRayCustom(self, ray, maxDistanceToRay, int.MinValue, customAttributes); - var line = Clip(bbox, ray); - if (!line.HasValue) return Enumerable.Empty(); + /// + /// Points within given distance of a ray. + /// + public static IEnumerable QueryPointsNearRayCustom( + this PointSet self, Ray3d ray, double maxDistanceToRay, int minCellExponent, params Durable.Def[] customAttributes + ) + { + ray.Direction = ray.Direction.Normalized; + var data = self.Root.Value; + var bbox = data.BoundingBoxExactGlobal; - return self.QueryPointsNearLineSegmentCustom(line.Value, maxDistanceToRay, minCellExponent, customAttributes); - } + var line = Clip(bbox, ray); + if (!line.HasValue) return []; - /// - /// Points within given distance of a line segment (at most 1000). - /// - public static IEnumerable QueryPointsNearLineSegment( - this PointSet self, Line3d lineSegment, double maxDistanceToRay, int minCellExponent = int.MinValue - ) - => QueryPointsNearLineSegment(self.Root.Value, lineSegment, maxDistanceToRay, minCellExponent); - - /// - /// Points within given distance of a line segment (at most 1000). - /// - public static IEnumerable QueryPointsNearLineSegmentCustom( - this PointSet self, Line3d lineSegment, double maxDistanceToRay, params Durable.Def[] customAttributes - ) - => QueryPointsNearLineSegmentCustom(self, lineSegment, maxDistanceToRay, int.MinValue, customAttributes); - - /// - /// Points within given distance of a line segment (at most 1000). - /// - public static IEnumerable QueryPointsNearLineSegmentCustom( - this PointSet self, Line3d lineSegment, double maxDistanceToRay, int minCellExponent, params Durable.Def[] customAttributes - ) - => QueryPointsNearLineSegmentCustom(self.Root.Value, lineSegment, maxDistanceToRay, minCellExponent, customAttributes); - - - /// - /// Points within given distance of a line segment (at most 1000). - /// - public static IEnumerable QueryPointsNearLineSegment( - this IPointCloudNode node, Line3d lineSegment, double maxDistanceToRay, int minCellExponent = int.MinValue - ) - { - if (!node.HasPositions) yield break; + return self.QueryPointsNearLineSegmentCustom(line.Value, maxDistanceToRay, minCellExponent, customAttributes); + } - var centerGlobal = node.Center; - var s0Local = lineSegment.P0 - centerGlobal; - var s1Local = lineSegment.P1 - centerGlobal; - var rayLocal = new Ray3d(s0Local, (s1Local - s0Local).Normalized); + /// + /// Points within given distance of a line segment (at most 1000). + /// + public static IEnumerable QueryPointsNearLineSegment( + this PointSet self, Line3d lineSegment, double maxDistanceToRay, int minCellExponent = int.MinValue + ) + => QueryPointsNearLineSegment(self.Root.Value, lineSegment, maxDistanceToRay, minCellExponent); - var worstCaseDist = node.BoundingBoxExactLocal.Size3f.Length * 0.5 + maxDistanceToRay; - var d0 = rayLocal.GetMinimalDistanceTo((V3d)node.BoundingBoxExactLocal.Center); - if (d0 > worstCaseDist) yield break; + /// + /// Points within given distance of a line segment (at most 1000). + /// + public static IEnumerable QueryPointsNearLineSegmentCustom( + this PointSet self, Line3d lineSegment, double maxDistanceToRay, params Durable.Def[] customAttributes + ) + => QueryPointsNearLineSegmentCustom(self, lineSegment, maxDistanceToRay, int.MinValue, customAttributes); + + /// + /// Points within given distance of a line segment (at most 1000). + /// + public static IEnumerable QueryPointsNearLineSegmentCustom( + this PointSet self, Line3d lineSegment, double maxDistanceToRay, int minCellExponent, params Durable.Def[] customAttributes + ) + => QueryPointsNearLineSegmentCustom(self.Root.Value, lineSegment, maxDistanceToRay, minCellExponent, customAttributes); + + + /// + /// Points within given distance of a line segment (at most 1000). + /// + public static IEnumerable QueryPointsNearLineSegment( + this IPointCloudNode node, Line3d lineSegment, double maxDistanceToRay, int minCellExponent = int.MinValue + ) + { + if (!node.HasPositions) yield break; + + var centerGlobal = node.Center; + var s0Local = lineSegment.P0 - centerGlobal; + var s1Local = lineSegment.P1 - centerGlobal; + var rayLocal = new Ray3d(s0Local, (s1Local - s0Local).Normalized); - if (node.IsLeaf || node.Cell.Exponent == minCellExponent) + var worstCaseDist = node.BoundingBoxExactLocal.Size3f.Length * 0.5 + maxDistanceToRay; + var d0 = rayLocal.GetMinimalDistanceTo((V3d)node.BoundingBoxExactLocal.Center); + if (d0 > worstCaseDist) yield break; + + if (node.IsLeaf || node.Cell.Exponent == minCellExponent) + { + if (node.HasKdTree) { - if (node.HasKdTree) - { - var indexArray = node.KdTree.Value.GetClosestToLine( - (V3f)s0Local, (V3f)s1Local, - (float)maxDistanceToRay, - node.PointCountCell - ); + var indexArray = node.KdTree.Value.GetClosestToLine( + (V3f)s0Local, (V3f)s1Local, + (float)maxDistanceToRay, + node.PointCountCell + ); - if (indexArray.Count > 0) - { - var ia = indexArray.MapToArray(x => (int)x.Index); - var ps = new V3d[ia.Length]; - var cs = node.HasColors ? new C4b[ia.Length] : null; - var ns = node.HasNormals ? new V3f[ia.Length] : null; - var js = node.HasIntensities ? new int[ia.Length] : null; - var ks = node.HasClassifications ? new byte[ia.Length] : null; - var qs = PartIndexUtils.Subset(node.PartIndices, ia); - //var ds = new double[ia.Count]; - - for (var i = 0; i < ia.Length; i++) - { - var index = ia[i]; - ps[i] = centerGlobal + (V3d)node.Positions.Value[index]; - if (node.HasColors) cs![i] = node.Colors.Value[index]; - if (node.HasNormals) ns![i] = node.Normals.Value[index]; - if (node.HasIntensities) js![i] = node.Intensities.Value[index]; - if (node.HasClassifications) ks![i] = node.Classifications.Value[index]; - //ds[i] = ia[i].Dist; - } - var chunk = new Chunk(ps, cs, ns, js, ks, qs, partIndexRange: null, bbox: null); - yield return chunk; - } - } - else + if (indexArray.Count > 0) { - // do it without kd-tree ;-) - var psLocal = node.Positions.Value; + var ia = indexArray.MapToArray(x => (int)x.Index); + var ps = new V3d[ia.Length]; + var cs = node.HasColors ? new C4b[ia.Length] : null; + var ns = node.HasNormals ? new V3f[ia.Length] : null; + var js = node.HasIntensities ? new int[ia.Length] : null; + var ks = node.HasClassifications ? new byte[ia.Length] : null; + var qs = PartIndexUtils.Subset(node.PartIndices, ia); + //var ds = new double[ia.Count]; - var ia = new List(); - for (var i = 0; i < psLocal.Length; i++) + for (var i = 0; i < ia.Length; i++) { - var d = rayLocal.GetMinimalDistanceTo((V3d)psLocal[i]); - if (d > maxDistanceToRay) continue; - ia.Add(i); - } - - if (ia.Count > 0) - { - yield return new Chunk( - node.PositionsAbsolute.Subset(ia), - node.Colors?.Value.Subset(ia), - node.Normals?.Value.Subset(ia), - node.Intensities?.Value.Subset(ia), - node.Classifications?.Value.Subset(ia), - partIndices: PartIndexUtils.Subset(node.PartIndices, ia), - partIndexRange: null, - bbox: null); - throw new NotImplementedException("PARTINDICES"); + var index = ia[i]; + ps[i] = centerGlobal + (V3d)node.Positions.Value[index]; + if (node.HasColors) cs![i] = node.Colors.Value[index]; + if (node.HasNormals) ns![i] = node.Normals.Value[index]; + if (node.HasIntensities) js![i] = node.Intensities.Value[index]; + if (node.HasClassifications) ks![i] = node.Classifications.Value[index]; + //ds[i] = ia[i].Dist; } + var chunk = new Chunk(ps, cs, ns, js, ks, qs, partIndexRange: null, bbox: null); + yield return chunk; } } - else // inner node + else { - for (var i = 0; i < 8; i++) + // do it without kd-tree ;-) + var psLocal = node.Positions.Value; + + var ia = new List(); + for (var i = 0; i < psLocal.Length; i++) { - var n = node.Subnodes![i]; - if (n == null) continue; - var xs = QueryPointsNearLineSegment(n.Value, lineSegment, maxDistanceToRay, minCellExponent); - foreach (var x in xs) yield return x; + var d = rayLocal.GetMinimalDistanceTo((V3d)psLocal[i]); + if (d > maxDistanceToRay) continue; + ia.Add(i); + } + + if (ia.Count > 0) + { + yield return new Chunk( + node.PositionsAbsolute.Subset(ia), + node.Colors?.Value.Subset(ia), + node.Normals?.Value.Subset(ia), + node.Intensities?.Value.Subset(ia), + node.Classifications?.Value.Subset(ia), + partIndices: PartIndexUtils.Subset(node.PartIndices, ia), + partIndexRange: null, + bbox: null); + throw new NotImplementedException("PARTINDICES"); } } } - - /// - /// Points within given distance of a line segment (at most 1000). - /// - public static IEnumerable QueryPointsNearLineSegmentCustom( - this IPointCloudNode node, Line3d lineSegment, double maxDistanceToRay, params Durable.Def[] customAttributes - ) - => QueryPointsNearLineSegmentCustom(node, lineSegment, maxDistanceToRay, int.MinValue, customAttributes); - - /// - /// Points within given distance of a line segment (at most 1000). - /// - public static IEnumerable QueryPointsNearLineSegmentCustom( - this IPointCloudNode node, Line3d lineSegment, double maxDistanceToRay, int minCellExponent, params Durable.Def[] customAttributes - ) + else // inner node { - if (!node.HasPositions) yield break; + for (var i = 0; i < 8; i++) + { + var n = node.Subnodes![i]; + if (n == null) continue; + var xs = QueryPointsNearLineSegment(n.Value, lineSegment, maxDistanceToRay, minCellExponent); + foreach (var x in xs) yield return x; + } + } + } + + /// + /// Points within given distance of a line segment (at most 1000). + /// + public static IEnumerable QueryPointsNearLineSegmentCustom( + this IPointCloudNode node, Line3d lineSegment, double maxDistanceToRay, params Durable.Def[] customAttributes + ) + => QueryPointsNearLineSegmentCustom(node, lineSegment, maxDistanceToRay, int.MinValue, customAttributes); - var centerGlobal = node.Center; - var s0Local = lineSegment.P0 - centerGlobal; - var s1Local = lineSegment.P1 - centerGlobal; - var rayLocal = new Ray3d(s0Local, (s1Local - s0Local).Normalized); + /// + /// Points within given distance of a line segment (at most 1000). + /// + public static IEnumerable QueryPointsNearLineSegmentCustom( + this IPointCloudNode node, Line3d lineSegment, double maxDistanceToRay, int minCellExponent, params Durable.Def[] customAttributes + ) + { + if (!node.HasPositions) yield break; - var worstCaseDist = node.BoundingBoxExactLocal.Size3f.Length * 0.5 + maxDistanceToRay; - var d0 = rayLocal.GetMinimalDistanceTo((V3d)node.BoundingBoxExactLocal.Center); - if (d0 > worstCaseDist) yield break; + var centerGlobal = node.Center; + var s0Local = lineSegment.P0 - centerGlobal; + var s1Local = lineSegment.P1 - centerGlobal; + var rayLocal = new Ray3d(s0Local, (s1Local - s0Local).Normalized); - if (node.IsLeaf || node.Cell.Exponent == minCellExponent) + var worstCaseDist = node.BoundingBoxExactLocal.Size3f.Length * 0.5 + maxDistanceToRay; + var d0 = rayLocal.GetMinimalDistanceTo((V3d)node.BoundingBoxExactLocal.Center); + if (d0 > worstCaseDist) yield break; + + if (node.IsLeaf || node.Cell.Exponent == minCellExponent) + { + if (!node.HasKdTree) throw new Exception("No kd-tree. Error 575ebf66-6fdf-4656-85d6-b2a9e387fea9."); + + var closest = node.KdTree.Value.GetClosestToLine( + (V3f)s0Local, (V3f)s1Local, + (float)maxDistanceToRay, + node.PointCountCell + ); + + if (closest.Count > 0) { - if (!node.HasKdTree) throw new Exception("No kd-tree. Error 575ebf66-6fdf-4656-85d6-b2a9e387fea9."); - - var closest = node.KdTree.Value.GetClosestToLine( - (V3f)s0Local, (V3f)s1Local, - (float)maxDistanceToRay, - node.PointCountCell - ); + var ia = closest.Map(x => (int)x.Index); + var ps = node.PositionsAbsolute.Subset(ia); + var data = + ImmutableDictionary.Empty + .Add(GenericChunk.Defs.Positions3d, ps) + ; - if (closest.Count > 0) + var attributes = customAttributes.Where(node.Has).Select(def => (def, value: node.Properties[def])); + foreach (var (def, value) in attributes) { - var ia = closest.Map(x => (int)x.Index); - var ps = node.PositionsAbsolute.Subset(ia); - var data = - ImmutableDictionary.Empty - .Add(GenericChunk.Defs.Positions3d, ps) - ; - - var attributes = customAttributes.Where(node.Has).Select(def => (def, value: node.Properties[def])); - foreach (var (def, value) in attributes) - { - data = data.Add(def, value.Subset(ia)); - } + data = data.Add(def, value.Subset(ia)); + } - var chunk = new GenericChunk(data); + var chunk = new GenericChunk(data); - yield return chunk; - } + yield return chunk; } - else // inner node + } + else // inner node + { + for (var i = 0; i < 8; i++) { - for (var i = 0; i < 8; i++) - { - var n = node.Subnodes![i]; - if (n == null) continue; - var xs = QueryPointsNearLineSegmentCustom(n.Value, lineSegment, maxDistanceToRay, minCellExponent, customAttributes); - foreach (var x in xs) yield return x; - } + var n = node.Subnodes![i]; + if (n == null) continue; + var xs = QueryPointsNearLineSegmentCustom(n.Value, lineSegment, maxDistanceToRay, minCellExponent, customAttributes); + foreach (var x in xs) yield return x; } } + } - /// - /// Clips given ray on box, or returns null if ray does not intersect box. - /// - private static Line3d? Clip(Box3d box, Ray3d ray0) - { - ray0.Direction = ray0.Direction.Normalized; + /// + /// Clips given ray on box, or returns null if ray does not intersect box. + /// + private static Line3d? Clip(Box3d box, Ray3d ray0) + { + ray0.Direction = ray0.Direction.Normalized; - if (!box.Intersects(ray0, out double t0)) return null; - var p0 = ray0.GetPointOnRay(t0); + if (!box.Intersects(ray0, out double t0)) return null; + var p0 = ray0.GetPointOnRay(t0); - var ray1 = new Ray3d(ray0.GetPointOnRay(t0 + box.Size.Length), -ray0.Direction); - if (!box.Intersects(ray1, out double t1)) throw new InvalidOperationException(); - var p1 = ray1.GetPointOnRay(t1); + var ray1 = new Ray3d(ray0.GetPointOnRay(t0 + box.Size.Length), -ray0.Direction); + if (!box.Intersects(ray1, out double t1)) throw new InvalidOperationException(); + var p1 = ray1.GetPointOnRay(t1); - return new Line3d(p0, p1); - } + return new Line3d(p0, p1); } } diff --git a/src/Aardvark.Geometry.PointSet/Queries/QueriesV3d.cs b/src/Aardvark.Geometry.PointSet/Queries/QueriesV3d.cs index 6f377707..7f7a248d 100644 --- a/src/Aardvark.Geometry.PointSet/Queries/QueriesV3d.cs +++ b/src/Aardvark.Geometry.PointSet/Queries/QueriesV3d.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -13,168 +13,165 @@ You should have received a copy of the GNU Affero General Public License */ #define PARANOID using Aardvark.Base; -using Aardvark.Data; using Aardvark.Data.Points; using System; -using System.Collections.Generic; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static partial class Queries { + #region Query points + /// + /// Points within given distance to a query point. /// - public static partial class Queries - { - #region Query points - - /// - /// Points within given distance to a query point. - /// - public static PointsNearObject QueryPointsNearPoint( - this PointSet self, V3d query, double maxDistanceToPoint, int maxCount - ) - => QueryPointsNearPoint(self.Root.Value, query, maxDistanceToPoint, maxCount); + public static PointsNearObject QueryPointsNearPoint( + this PointSet self, V3d query, double maxDistanceToPoint, int maxCount + ) + => QueryPointsNearPoint(self.Root.Value, query, maxDistanceToPoint, maxCount); #if TODO - /// - /// Points within given distance to a query point. - /// - public static PointsNearObject QueryPointsNearPointCustom( - this PointSet self, V3d query, double maxDistanceToPoint, int maxCount, params Durable.Def[] customAttributes - ) - => QueryPointsNearPointCustom(self.Root.Value, query, maxDistanceToPoint, maxCount, customAttributes); + /// + /// Points within given distance to a query point. + /// + public static PointsNearObject QueryPointsNearPointCustom( + this PointSet self, V3d query, double maxDistanceToPoint, int maxCount, params Durable.Def[] customAttributes + ) + => QueryPointsNearPointCustom(self.Root.Value, query, maxDistanceToPoint, maxCount, customAttributes); #endif - /// - /// Points within given distance to a query point. - /// - public static PointsNearObject QueryPointsNearPoint( - this IPointCloudNode node, V3d query, double maxDistanceToPoint, int maxCount - ) - { - if (node == null) return PointsNearObject.Empty; + /// + /// Points within given distance to a query point. + /// + public static PointsNearObject QueryPointsNearPoint( + this IPointCloudNode node, V3d query, double maxDistanceToPoint, int maxCount + ) + { + if (node == null) return PointsNearObject.Empty; - // if query point is farther from bounding box than maxDistanceToPoint, - // then there cannot be a result and we are done - var eps = node.BoundingBoxExactGlobal.Distance(query); - if (eps > maxDistanceToPoint) return PointsNearObject.Empty; + // if query point is farther from bounding box than maxDistanceToPoint, + // then there cannot be a result and we are done + var eps = node.BoundingBoxExactGlobal.Distance(query); + if (eps > maxDistanceToPoint) return PointsNearObject.Empty; - if (node.IsLeaf()) - { - var nodePositions = node.Positions; + if (node.IsLeaf()) + { + var nodePositions = node.Positions; #if PARANOID - if (nodePositions.Value.Length <= 0) throw new InvalidOperationException(); + if (nodePositions.Value.Length <= 0) throw new InvalidOperationException(); #endif - var center = node.Center; - - var closest = node.KdTree!.Value.GetClosest((V3f)(query - center), (float)maxDistanceToPoint, maxCount).ToArray(); - if (closest.Length > 0) - { - var ia = closest.Map(x => (int)x.Index); - var ds = closest.Map(x => (double)x.Dist); - var ps = node.PositionsAbsolute.Subset(ia); - var cs = node.Colors?.Value?.Subset(ia); - var ns = node.Normals?.Value?.Subset(ia); - var js = node.Intensities?.Value?.Subset(ia); - node.TryGetPartIndices(out var pis); - pis = pis?.Subset(ia); - var ks = node.Classifications?.Value?.Subset(ia); - var chunk = new PointsNearObject(query, maxDistanceToPoint, ps, cs, ns, js, pis, ks, ds); - return chunk; - } - else - { - return PointsNearObject.Empty; - } + var center = node.Center; + + var closest = node.KdTree!.Value.GetClosest((V3f)(query - center), (float)maxDistanceToPoint, maxCount).ToArray(); + if (closest.Length > 0) + { + var ia = closest.Map(x => (int)x.Index); + var ds = closest.Map(x => (double)x.Dist); + var ps = node.PositionsAbsolute.Subset(ia); + var cs = node.Colors?.Value?.Subset(ia); + var ns = node.Normals?.Value?.Subset(ia); + var js = node.Intensities?.Value?.Subset(ia); + node.TryGetPartIndices(out var pis); + pis = pis?.Subset(ia); + var ks = node.Classifications?.Value?.Subset(ia); + var chunk = new PointsNearObject(query, maxDistanceToPoint, ps, cs, ns, js, pis, ks, ds); + return chunk; } else { - // first traverse octant containing query point - var index = node.GetSubIndex(query); - var n = node.Subnodes![index]; - var result = n != null ? n.Value.QueryPointsNearPoint(query, maxDistanceToPoint, maxCount) : PointsNearObject.Empty; + return PointsNearObject.Empty; + } + } + else + { + // first traverse octant containing query point + var index = node.GetSubIndex(query); + var n = node.Subnodes![index]; + var result = n != null ? n.Value.QueryPointsNearPoint(query, maxDistanceToPoint, maxCount) : PointsNearObject.Empty; + if (!result.IsEmpty && result.MaxDistance < maxDistanceToPoint) maxDistanceToPoint = result.MaxDistance; + + // now traverse other octants + for (var i = 0; i < 8; i++) + { + if (i == index) continue; + n = node.Subnodes[i]; + if (n == null) continue; + var x = n.Value.QueryPointsNearPoint(query, maxDistanceToPoint, maxCount); + result = result.Merge(x, maxCount); if (!result.IsEmpty && result.MaxDistance < maxDistanceToPoint) maxDistanceToPoint = result.MaxDistance; - - // now traverse other octants - for (var i = 0; i < 8; i++) - { - if (i == index) continue; - n = node.Subnodes[i]; - if (n == null) continue; - var x = n.Value.QueryPointsNearPoint(query, maxDistanceToPoint, maxCount); - result = result.Merge(x, maxCount); - if (!result.IsEmpty && result.MaxDistance < maxDistanceToPoint) maxDistanceToPoint = result.MaxDistance; - } - - return result; } + + return result; } + } #if TODO - /// - /// Points within given distance to a query point. - /// - public static IEnumerable QueryPointsNearPointCustom( - this IPointCloudNode node, V3d query, double maxDistanceToPoint, int maxCount, params Durable.Def[] customAttributes - ) - { - if (node == null) yield break; + /// + /// Points within given distance to a query point. + /// + public static IEnumerable QueryPointsNearPointCustom( + this IPointCloudNode node, V3d query, double maxDistanceToPoint, int maxCount, params Durable.Def[] customAttributes + ) + { + if (node == null) yield break; - // if query point is farther from bounding box than maxDistanceToPoint, - // then there cannot be a result and we are done - var eps = node.BoundingBoxExactGlobal.Distance(query); - if (eps > maxDistanceToPoint) yield break; + // if query point is farther from bounding box than maxDistanceToPoint, + // then there cannot be a result and we are done + var eps = node.BoundingBoxExactGlobal.Distance(query); + if (eps > maxDistanceToPoint) yield break; - if (node.IsLeaf()) - { - var nodePositions = node.Positions; + if (node.IsLeaf()) + { + var nodePositions = node.Positions; #if PARANOID - if (nodePositions.Value.Length <= 0) throw new InvalidOperationException(); + if (nodePositions.Value.Length <= 0) throw new InvalidOperationException(); #endif - var center = node.Center; - - var closest = node.KdTree.Value.GetClosest((V3f)(query - center), (float)maxDistanceToPoint, maxCount).ToArray(); - if (closest.Length > 0) - { - var ia = closest.Map(x => (int)x.Index); - var ds = closest.Map(x => (double)x.Dist); - var ps = node.PositionsAbsolute.Subset(ia); - var cs = node.Colors?.Value?.Subset(ia); - var ns = node.Normals?.Value?.Subset(ia); - var js = node.Intensities?.Value?.Subset(ia); - var ks = node.Classifications?.Value?.Subset(ia); - var chunk = new PointsNearObject(query, maxDistanceToPoint, ps, cs, ns, js, ks, ds); - return chunk; - } - else - { - yield break; - } + var center = node.Center; + + var closest = node.KdTree.Value.GetClosest((V3f)(query - center), (float)maxDistanceToPoint, maxCount).ToArray(); + if (closest.Length > 0) + { + var ia = closest.Map(x => (int)x.Index); + var ds = closest.Map(x => (double)x.Dist); + var ps = node.PositionsAbsolute.Subset(ia); + var cs = node.Colors?.Value?.Subset(ia); + var ns = node.Normals?.Value?.Subset(ia); + var js = node.Intensities?.Value?.Subset(ia); + var ks = node.Classifications?.Value?.Subset(ia); + var chunk = new PointsNearObject(query, maxDistanceToPoint, ps, cs, ns, js, ks, ds); + return chunk; } else { - // first traverse octant containing query point - var index = node.GetSubIndex(query); - var n = node.Subnodes[index]; - var xs = n.Value.QueryPointsNearPointCustom(query, maxDistanceToPoint, maxCount, customAttributes); - foreach (var x in xs) yield return x; + yield break; + } + } + else + { + // first traverse octant containing query point + var index = node.GetSubIndex(query); + var n = node.Subnodes[index]; + var xs = n.Value.QueryPointsNearPointCustom(query, maxDistanceToPoint, maxCount, customAttributes); + foreach (var x in xs) yield return x; + if (!result.IsEmpty && result.MaxDistance < maxDistanceToPoint) maxDistanceToPoint = result.MaxDistance; + + // now traverse other octants + for (var i = 0; i < 8; i++) + { + if (i == index) continue; + n = node.Subnodes[i]; + if (n == null) continue; + var x = n.Value.QueryPointsNearPointCustom(query, maxDistanceToPoint, maxCount, customAttributes); + result = result.Merge(x, maxCount); if (!result.IsEmpty && result.MaxDistance < maxDistanceToPoint) maxDistanceToPoint = result.MaxDistance; - - // now traverse other octants - for (var i = 0; i < 8; i++) - { - if (i == index) continue; - n = node.Subnodes[i]; - if (n == null) continue; - var x = n.Value.QueryPointsNearPointCustom(query, maxDistanceToPoint, maxCount, customAttributes); - result = result.Merge(x, maxCount); - if (!result.IsEmpty && result.MaxDistance < maxDistanceToPoint) maxDistanceToPoint = result.MaxDistance; - } - - return result; } + + return result; } + } #endif #endregion - } } diff --git a/src/Aardvark.Geometry.PointSet/Queries/QueriesViewFrustum.cs b/src/Aardvark.Geometry.PointSet/Queries/QueriesViewFrustum.cs index 7e0e9f97..d57a3eaf 100644 --- a/src/Aardvark.Geometry.PointSet/Queries/QueriesViewFrustum.cs +++ b/src/Aardvark.Geometry.PointSet/Queries/QueriesViewFrustum.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -15,32 +15,31 @@ You should have received a copy of the GNU Affero General Public License using Aardvark.Data.Points; using System.Collections.Generic; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static partial class Queries { /// + /// Returns points inside view frustum (defined by viewProjection and canonicalViewVolume). /// - public static partial class Queries + public static IEnumerable QueryPointsInViewFrustum( + this PointSet self, M44d viewProjection, Box3d canonicalViewVolume + ) { - /// - /// Returns points inside view frustum (defined by viewProjection and canonicalViewVolume). - /// - public static IEnumerable QueryPointsInViewFrustum( - this PointSet self, M44d viewProjection, Box3d canonicalViewVolume - ) - { - var t = viewProjection.Inverse; - var cs = canonicalViewVolume.ComputeCorners().Map(v => t.TransformPosProj(v)); - var hull = new Hull3d(new[] - { - new Plane3d(cs[0], cs[2], cs[1]), // near - new Plane3d(cs[5], cs[7], cs[4]), // far - new Plane3d(cs[0], cs[1], cs[4]), // bottom - new Plane3d(cs[1], cs[3], cs[5]), // left - new Plane3d(cs[4], cs[6], cs[0]), // right - new Plane3d(cs[3], cs[2], cs[7]), // top - }); + var t = viewProjection.Inverse; + var cs = canonicalViewVolume.ComputeCorners().Map(v => t.TransformPosProj(v)); + var hull = new Hull3d( + [ + new Plane3d(cs[0], cs[2], cs[1]), // near + new Plane3d(cs[5], cs[7], cs[4]), // far + new Plane3d(cs[0], cs[1], cs[4]), // bottom + new Plane3d(cs[1], cs[3], cs[5]), // left + new Plane3d(cs[4], cs[6], cs[0]), // right + new Plane3d(cs[3], cs[2], cs[7]), // top + ]); - return QueryPointsInsideConvexHull(self, hull); - } + return QueryPointsInsideConvexHull(self, hull); } } diff --git a/src/Aardvark.Geometry.PointSet/Utils/BaseExtensions.cs b/src/Aardvark.Geometry.PointSet/Utils/BaseExtensions.cs index a53c0bfa..d5b5bc0e 100644 --- a/src/Aardvark.Geometry.PointSet/Utils/BaseExtensions.cs +++ b/src/Aardvark.Geometry.PointSet/Utils/BaseExtensions.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -12,108 +12,106 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ using Aardvark.Base; -using System; using System.Linq; using System.Runtime.CompilerServices; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static class BaseExtensions { + public static Box2l[] SplitAtCenter(this Box2l self) + { + var c = self.Center; + return + [ + new(self.Min.X, self.Min.Y, c.X, c.Y ), + new(c.X, self.Min.Y, self.Max.X, c.Y ), + new(self.Min.X, c.Y, c.X, self.Max.Y), + new(c.X, c.Y, self.Max.X, self.Max.Y) + ]; + } + + /// + /// Projects outline of a box from given position to a plane. + /// Returns null if position is inside the box. /// - public static class BaseExtensions + /// Box to project. + /// Project from. + /// Transformation from world/box-space to plane z=0 + public static V2d[]? GetOutlineProjected(this Box3d box, V3d fromPosition, M44d box2plane) { - public static Box2l[] SplitAtCenter(this Box2l self) + var ps = box.GetOutlineCornersCW(fromPosition); + if (ps == null) return null; + var qs = ps.Map(p => box2plane.TransformPosProj(p)); + + var behindPositionCount = 0; + for (var i = 0; i < qs.Length; i++) { - var c = self.Center; - return new Box2l[] - { - new(self.Min.X, self.Min.Y, c.X, c.Y ), - new(c.X, self.Min.Y, self.Max.X, c.Y ), - new(self.Min.X, c.Y, c.X, self.Max.Y), - new(c.X, c.Y, self.Max.X, self.Max.Y) - }; + if (qs[i].Z < 0.0) behindPositionCount++; } - + if (behindPositionCount == qs.Length) return []; + if (behindPositionCount > 0) return null; - /// - /// Projects outline of a box from given position to a plane. - /// Returns null if position is inside the box. - /// - /// Box to project. - /// Project from. - /// Transformation from world/box-space to plane z=0 - public static V2d[]? GetOutlineProjected(this Box3d box, V3d fromPosition, M44d box2plane) - { - var ps = box.GetOutlineCornersCW(fromPosition); - if (ps == null) return null; - var qs = ps.Map(p => box2plane.TransformPosProj(p)); - - var behindPositionCount = 0; - for (var i = 0; i < qs.Length; i++) - { - if (qs[i].Z < 0.0) behindPositionCount++; - } - if (behindPositionCount == qs.Length) return Array.Empty(); - if (behindPositionCount > 0) return null; - - return qs.Map(p => p.XY); - } + return qs.Map(p => p.XY); + } - /// - /// Returns true if the plane with a supplied epsilon tolerance fully contains the box. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Contains( - this Plane3d plane, Box3d box, double eps) - { - var signs = box.GetIntersectionSignsWithPlane(plane, eps); - return signs == Signs.Zero; - } + /// + /// Returns true if the plane with a supplied epsilon tolerance fully contains the box. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Contains( + this Plane3d plane, Box3d box, double eps) + { + var signs = box.GetIntersectionSignsWithPlane(plane, eps); + return signs == Signs.Zero; + } - /// - /// Returns true if the plane with a supplied epsilon tolerance intersects or fully contains the box. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IntersectsOrContains( - this Plane3d plane, Box3d box, double eps) - { - var signs = box.GetIntersectionSignsWithPlane(plane, eps); - return (signs != Signs.Negative && signs != Signs.Positive) || signs == Signs.Zero; - } + /// + /// Returns true if the plane with a supplied epsilon tolerance intersects or fully contains the box. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IntersectsOrContains( + this Plane3d plane, Box3d box, double eps) + { + var signs = box.GetIntersectionSignsWithPlane(plane, eps); + return (signs != Signs.Negative && signs != Signs.Positive) || signs == Signs.Zero; + } - /// - /// Bounding box of polygon with additional epsilon tolerance with respect to polygon normal. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Box3d BoundingBox3d( - this Polygon3d self, double eps) + /// + /// Bounding box of polygon with additional epsilon tolerance with respect to polygon normal. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Box3d BoundingBox3d( + this Polygon3d self, double eps) + { + var bb = Box3d.Invalid; + var v = self.ComputeNormal() * eps; + foreach (var p in self.Points) { - var bb = Box3d.Invalid; - var v = self.ComputeNormal() * eps; - foreach (var p in self.Points) - { - bb.ExtendBy(p + v); - bb.ExtendBy(p - v); - } - return bb; + bb.ExtendBy(p + v); + bb.ExtendBy(p - v); } + return bb; + } - /// - /// Returns true if the Hull3d completely contains the box. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Contains( - this Hull3d self, Box3d box) + /// + /// Returns true if the Hull3d completely contains the box. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Contains( + this Hull3d self, Box3d box) + { + var planes = self.PlaneArray; + var imax = self.PlaneCount; + var corners = box.Corners.ToArray(); + for (var i = 0; i < imax; i++) { - var planes = self.PlaneArray; - var imax = self.PlaneCount; - var corners = box.Corners.ToArray(); - for (var i = 0; i < imax; i++) - { - var plane = planes[i]; - for (var j = 0; j < 8; j++) if (plane.Height(corners[j]) > 0) return false; - } - return true; + var plane = planes[i]; + for (var j = 0; j < 8; j++) if (plane.Height(corners[j]) > 0) return false; } + return true; } } diff --git a/src/Aardvark.Geometry.PointSet/Utils/Codec.cs b/src/Aardvark.Geometry.PointSet/Utils/Codec.cs index 556d2782..06d5285b 100644 --- a/src/Aardvark.Geometry.PointSet/Utils/Codec.cs +++ b/src/Aardvark.Geometry.PointSet/Utils/Codec.cs @@ -7,612 +7,613 @@ using System.Runtime.InteropServices; using System.Text; -namespace Aardvark.Data +#pragma warning disable IDE0130 + +namespace Aardvark.Data; + +/// +/// +public static class Codec { - /// - /// - public static class Codec + private static readonly Dictionary s_encoders; + private static readonly Dictionary s_decoders; + + static Codec() { - private static readonly Dictionary s_encoders; - private static readonly Dictionary s_decoders; + // force Durable.Octree initializer + if (Durable.Octree.NodeId == null) throw new InvalidOperationException("Invariant 98c78cd6-cef2-4f0b-bb8e-907064c305c4."); - static Codec() + s_encoders = new Dictionary { - // force Durable.Octree initializer - if (Durable.Octree.NodeId == null) throw new InvalidOperationException("Invariant 98c78cd6-cef2-4f0b-bb8e-907064c305c4."); + [Durable.Primitives.GuidDef.Id] = EncodeGuid, + [Durable.Primitives.GuidArray.Id] = EncodeGuidArray, + + [Durable.Primitives.UInt8.Id] = EncodeUInt8, + [Durable.Primitives.UInt8Array.Id] = EncodeUInt8Array, + [Durable.Primitives.Int16.Id] = EncodeInt16, + [Durable.Primitives.Int16Array.Id] = EncodeInt16Array, + [Durable.Primitives.UInt16.Id] = EncodeUInt16, + [Durable.Primitives.UInt16Array.Id] = EncodeUInt16Array, + [Durable.Primitives.Int32.Id] = EncodeInt32, + [Durable.Primitives.Int32Array.Id] = EncodeInt32Array, + [Durable.Primitives.UInt32.Id] = EncodeUInt32, + [Durable.Primitives.UInt32Array.Id] = EncodeUInt32Array, + [Durable.Primitives.Int64.Id] = EncodeInt64, + [Durable.Primitives.Int64Array.Id] = EncodeInt64Array, + [Durable.Primitives.UInt64.Id] = EncodeUInt64, + [Durable.Primitives.UInt64Array.Id] = EncodeUInt64Array, + [Durable.Primitives.Float32.Id] = EncodeFloat32, + [Durable.Primitives.Float32Array.Id] = EncodeFloat32Array, + [Durable.Primitives.Float64.Id] = EncodeFloat64, + [Durable.Primitives.Float64Array.Id] = EncodeFloat64Array, + [Durable.Primitives.StringUTF8.Id] = EncodeStringUtf8, + [Durable.Primitives.DurableMap.Id] = EncodeDurableMapWithoutHeader, + + [Durable.Aardvark.Cell.Id] = EncodeCell, + [Durable.Aardvark.CellArray.Id] = EncodeCellArray, + [Durable.Aardvark.V2f.Id] = EncodeV2f, + [Durable.Aardvark.V2fArray.Id] = EncodeV2fArray, + [Durable.Aardvark.V3f.Id] = EncodeV3f, + [Durable.Aardvark.V3fArray.Id] = EncodeV3fArray, + [Durable.Aardvark.V4f.Id] = EncodeV4f, + [Durable.Aardvark.V4fArray.Id] = EncodeV4fArray, + [Durable.Aardvark.V2d.Id] = EncodeV2d, + [Durable.Aardvark.V2dArray.Id] = EncodeV2dArray, + [Durable.Aardvark.V3d.Id] = EncodeV3d, + [Durable.Aardvark.V3dArray.Id] = EncodeV3dArray, + [Durable.Aardvark.V4d.Id] = EncodeV4d, + [Durable.Aardvark.V4dArray.Id] = EncodeV4dArray, + [Durable.Aardvark.M33f.Id] = EncodeM33f, + [Durable.Aardvark.M33fArray.Id] = EncodeM33fArray, + [Durable.Aardvark.M33d.Id] = EncodeM33d, + [Durable.Aardvark.M33dArray.Id] = EncodeM33dArray, + [Durable.Aardvark.Box2f.Id] = EncodeBox2f, + [Durable.Aardvark.Box2fArray.Id] = EncodeBox2fArray, + [Durable.Aardvark.Box2d.Id] = EncodeBox2d, + [Durable.Aardvark.Box2dArray.Id] = EncodeBox2dArray, + [Durable.Aardvark.Box3f.Id] = EncodeBox3f, + [Durable.Aardvark.Box3fArray.Id] = EncodeBox3fArray, + [Durable.Aardvark.Box3d.Id] = EncodeBox3d, + [Durable.Aardvark.Box3dArray.Id] = EncodeBox3dArray, + [Durable.Aardvark.Range1i.Id] = EncodeRange1i, + [Durable.Aardvark.Range1iArray.Id] = EncodeRange1iArray, + + [Durable.Aardvark.C3b.Id] = EncodeC3b, + [Durable.Aardvark.C3bArray.Id] = EncodeC3bArray, + [Durable.Aardvark.C3f.Id] = EncodeC3f, + [Durable.Aardvark.C3fArray.Id] = EncodeC3fArray, + [Durable.Aardvark.C4b.Id] = EncodeC4b, + [Durable.Aardvark.C4bArray.Id] = EncodeC4bArray, + [Durable.Aardvark.C4f.Id] = EncodeC4f, + [Durable.Aardvark.C4fArray.Id] = EncodeC4fArray, + }; - s_encoders = new Dictionary - { - [Durable.Primitives.GuidDef.Id] = EncodeGuid, - [Durable.Primitives.GuidArray.Id] = EncodeGuidArray, - - [Durable.Primitives.UInt8.Id] = EncodeUInt8, - [Durable.Primitives.UInt8Array.Id] = EncodeUInt8Array, - [Durable.Primitives.Int16.Id] = EncodeInt16, - [Durable.Primitives.Int16Array.Id] = EncodeInt16Array, - [Durable.Primitives.UInt16.Id] = EncodeUInt16, - [Durable.Primitives.UInt16Array.Id] = EncodeUInt16Array, - [Durable.Primitives.Int32.Id] = EncodeInt32, - [Durable.Primitives.Int32Array.Id] = EncodeInt32Array, - [Durable.Primitives.UInt32.Id] = EncodeUInt32, - [Durable.Primitives.UInt32Array.Id] = EncodeUInt32Array, - [Durable.Primitives.Int64.Id] = EncodeInt64, - [Durable.Primitives.Int64Array.Id] = EncodeInt64Array, - [Durable.Primitives.UInt64.Id] = EncodeUInt64, - [Durable.Primitives.UInt64Array.Id] = EncodeUInt64Array, - [Durable.Primitives.Float32.Id] = EncodeFloat32, - [Durable.Primitives.Float32Array.Id] = EncodeFloat32Array, - [Durable.Primitives.Float64.Id] = EncodeFloat64, - [Durable.Primitives.Float64Array.Id] = EncodeFloat64Array, - [Durable.Primitives.StringUTF8.Id] = EncodeStringUtf8, - [Durable.Primitives.DurableMap.Id] = EncodeDurableMapWithoutHeader, - - [Durable.Aardvark.Cell.Id] = EncodeCell, - [Durable.Aardvark.CellArray.Id] = EncodeCellArray, - [Durable.Aardvark.V2f.Id] = EncodeV2f, - [Durable.Aardvark.V2fArray.Id] = EncodeV2fArray, - [Durable.Aardvark.V3f.Id] = EncodeV3f, - [Durable.Aardvark.V3fArray.Id] = EncodeV3fArray, - [Durable.Aardvark.V4f.Id] = EncodeV4f, - [Durable.Aardvark.V4fArray.Id] = EncodeV4fArray, - [Durable.Aardvark.V2d.Id] = EncodeV2d, - [Durable.Aardvark.V2dArray.Id] = EncodeV2dArray, - [Durable.Aardvark.V3d.Id] = EncodeV3d, - [Durable.Aardvark.V3dArray.Id] = EncodeV3dArray, - [Durable.Aardvark.V4d.Id] = EncodeV4d, - [Durable.Aardvark.V4dArray.Id] = EncodeV4dArray, - [Durable.Aardvark.M33f.Id] = EncodeM33f, - [Durable.Aardvark.M33fArray.Id] = EncodeM33fArray, - [Durable.Aardvark.M33d.Id] = EncodeM33d, - [Durable.Aardvark.M33dArray.Id] = EncodeM33dArray, - [Durable.Aardvark.Box2f.Id] = EncodeBox2f, - [Durable.Aardvark.Box2fArray.Id] = EncodeBox2fArray, - [Durable.Aardvark.Box2d.Id] = EncodeBox2d, - [Durable.Aardvark.Box2dArray.Id] = EncodeBox2dArray, - [Durable.Aardvark.Box3f.Id] = EncodeBox3f, - [Durable.Aardvark.Box3fArray.Id] = EncodeBox3fArray, - [Durable.Aardvark.Box3d.Id] = EncodeBox3d, - [Durable.Aardvark.Box3dArray.Id] = EncodeBox3dArray, - [Durable.Aardvark.Range1i.Id] = EncodeRange1i, - [Durable.Aardvark.Range1iArray.Id] = EncodeRange1iArray, - - [Durable.Aardvark.C3b.Id] = EncodeC3b, - [Durable.Aardvark.C3bArray.Id] = EncodeC3bArray, - [Durable.Aardvark.C3f.Id] = EncodeC3f, - [Durable.Aardvark.C3fArray.Id] = EncodeC3fArray, - [Durable.Aardvark.C4b.Id] = EncodeC4b, - [Durable.Aardvark.C4bArray.Id] = EncodeC4bArray, - [Durable.Aardvark.C4f.Id] = EncodeC4f, - [Durable.Aardvark.C4fArray.Id] = EncodeC4fArray, - }; - - s_decoders = new Dictionary - { - [Durable.Primitives.GuidDef.Id] = DecodeGuid, - [Durable.Primitives.GuidArray.Id] = DecodeGuidArray, - [Durable.Primitives.UInt8.Id] = DecodeUInt8, - [Durable.Primitives.UInt8Array.Id] = DecodeUInt8Array, - [Durable.Primitives.Int16.Id] = DecodeInt16, - [Durable.Primitives.Int16Array.Id] = DecodeInt16Array, - [Durable.Primitives.UInt16.Id] = DecodeUInt16, - [Durable.Primitives.UInt16Array.Id] = DecodeUInt16Array, - [Durable.Primitives.Int32.Id] = DecodeInt32, - [Durable.Primitives.Int32Array.Id] = DecodeInt32Array, - [Durable.Primitives.UInt32.Id] = DecodeUInt32, - [Durable.Primitives.UInt32Array.Id] = DecodeUInt32Array, - [Durable.Primitives.Int64.Id] = DecodeInt64, - [Durable.Primitives.Int64Array.Id] = DecodeInt64Array, - [Durable.Primitives.UInt64.Id] = DecodeUInt64, - [Durable.Primitives.UInt64Array.Id] = DecodeUInt64Array, - [Durable.Primitives.Float32.Id] = DecodeFloat32, - [Durable.Primitives.Float32Array.Id] = DecodeFloat32Array, - [Durable.Primitives.Float64.Id] = DecodeFloat64, - [Durable.Primitives.Float64Array.Id] = DecodeFloat64Array, - [Durable.Primitives.StringUTF8.Id] = DecodeStringUtf8, - [Durable.Primitives.DurableMap.Id] = DecodeDurableMapWithoutHeader, - - [Durable.Aardvark.Cell.Id] = DecodeCell, - [Durable.Aardvark.CellArray.Id] = DecodeCellArray, - [Durable.Aardvark.V2f.Id] = DecodeV2f, - [Durable.Aardvark.V2fArray.Id] = DecodeV2fArray, - [Durable.Aardvark.V3f.Id] = DecodeV3f, - [Durable.Aardvark.V3fArray.Id] = DecodeV3fArray, - [Durable.Aardvark.V4f.Id] = DecodeV4f, - [Durable.Aardvark.V4fArray.Id] = DecodeV4fArray, - [Durable.Aardvark.V2d.Id] = DecodeV2d, - [Durable.Aardvark.V2dArray.Id] = DecodeV2dArray, - [Durable.Aardvark.V3d.Id] = DecodeV3d, - [Durable.Aardvark.V3dArray.Id] = DecodeV3dArray, - [Durable.Aardvark.V4d.Id] = DecodeV4d, - [Durable.Aardvark.V4dArray.Id] = DecodeV4dArray, - [Durable.Aardvark.M33f.Id] = DecodeM33f, - [Durable.Aardvark.M33fArray.Id] = DecodeM33fArray, - [Durable.Aardvark.M33d.Id] = DecodeM33d, - [Durable.Aardvark.M33dArray.Id] = DecodeM33dArray, - [Durable.Aardvark.Box2f.Id] = DecodeBox2f, - [Durable.Aardvark.Box2fArray.Id] = DecodeBox2fArray, - [Durable.Aardvark.Box2d.Id] = DecodeBox2d, - [Durable.Aardvark.Box2dArray.Id] = DecodeBox2dArray, - [Durable.Aardvark.Box3f.Id] = DecodeBox3f, - [Durable.Aardvark.Box3fArray.Id] = DecodeBox3fArray, - [Durable.Aardvark.Box3d.Id] = DecodeBox3d, - [Durable.Aardvark.Box3dArray.Id] = DecodeBox3dArray, - [Durable.Aardvark.Range1i.Id] = DecodeRange1i, - [Durable.Aardvark.Range1iArray.Id] = DecodeRange1iArray, - - [Durable.Aardvark.C3b.Id] = DecodeC3b, - [Durable.Aardvark.C3bArray.Id] = DecodeC3bArray, - [Durable.Aardvark.C3f.Id] = DecodeC3f, - [Durable.Aardvark.C3fArray.Id] = DecodeC3fArray, - [Durable.Aardvark.C4b.Id] = DecodeC4b, - [Durable.Aardvark.C4bArray.Id] = DecodeC4bArray, - [Durable.Aardvark.C4f.Id] = DecodeC4f, - [Durable.Aardvark.C4fArray.Id] = DecodeC4fArray, - }; + s_decoders = new Dictionary + { + [Durable.Primitives.GuidDef.Id] = DecodeGuid, + [Durable.Primitives.GuidArray.Id] = DecodeGuidArray, + [Durable.Primitives.UInt8.Id] = DecodeUInt8, + [Durable.Primitives.UInt8Array.Id] = DecodeUInt8Array, + [Durable.Primitives.Int16.Id] = DecodeInt16, + [Durable.Primitives.Int16Array.Id] = DecodeInt16Array, + [Durable.Primitives.UInt16.Id] = DecodeUInt16, + [Durable.Primitives.UInt16Array.Id] = DecodeUInt16Array, + [Durable.Primitives.Int32.Id] = DecodeInt32, + [Durable.Primitives.Int32Array.Id] = DecodeInt32Array, + [Durable.Primitives.UInt32.Id] = DecodeUInt32, + [Durable.Primitives.UInt32Array.Id] = DecodeUInt32Array, + [Durable.Primitives.Int64.Id] = DecodeInt64, + [Durable.Primitives.Int64Array.Id] = DecodeInt64Array, + [Durable.Primitives.UInt64.Id] = DecodeUInt64, + [Durable.Primitives.UInt64Array.Id] = DecodeUInt64Array, + [Durable.Primitives.Float32.Id] = DecodeFloat32, + [Durable.Primitives.Float32Array.Id] = DecodeFloat32Array, + [Durable.Primitives.Float64.Id] = DecodeFloat64, + [Durable.Primitives.Float64Array.Id] = DecodeFloat64Array, + [Durable.Primitives.StringUTF8.Id] = DecodeStringUtf8, + [Durable.Primitives.DurableMap.Id] = DecodeDurableMapWithoutHeader, + + [Durable.Aardvark.Cell.Id] = DecodeCell, + [Durable.Aardvark.CellArray.Id] = DecodeCellArray, + [Durable.Aardvark.V2f.Id] = DecodeV2f, + [Durable.Aardvark.V2fArray.Id] = DecodeV2fArray, + [Durable.Aardvark.V3f.Id] = DecodeV3f, + [Durable.Aardvark.V3fArray.Id] = DecodeV3fArray, + [Durable.Aardvark.V4f.Id] = DecodeV4f, + [Durable.Aardvark.V4fArray.Id] = DecodeV4fArray, + [Durable.Aardvark.V2d.Id] = DecodeV2d, + [Durable.Aardvark.V2dArray.Id] = DecodeV2dArray, + [Durable.Aardvark.V3d.Id] = DecodeV3d, + [Durable.Aardvark.V3dArray.Id] = DecodeV3dArray, + [Durable.Aardvark.V4d.Id] = DecodeV4d, + [Durable.Aardvark.V4dArray.Id] = DecodeV4dArray, + [Durable.Aardvark.M33f.Id] = DecodeM33f, + [Durable.Aardvark.M33fArray.Id] = DecodeM33fArray, + [Durable.Aardvark.M33d.Id] = DecodeM33d, + [Durable.Aardvark.M33dArray.Id] = DecodeM33dArray, + [Durable.Aardvark.Box2f.Id] = DecodeBox2f, + [Durable.Aardvark.Box2fArray.Id] = DecodeBox2fArray, + [Durable.Aardvark.Box2d.Id] = DecodeBox2d, + [Durable.Aardvark.Box2dArray.Id] = DecodeBox2dArray, + [Durable.Aardvark.Box3f.Id] = DecodeBox3f, + [Durable.Aardvark.Box3fArray.Id] = DecodeBox3fArray, + [Durable.Aardvark.Box3d.Id] = DecodeBox3d, + [Durable.Aardvark.Box3dArray.Id] = DecodeBox3dArray, + [Durable.Aardvark.Range1i.Id] = DecodeRange1i, + [Durable.Aardvark.Range1iArray.Id] = DecodeRange1iArray, + + [Durable.Aardvark.C3b.Id] = DecodeC3b, + [Durable.Aardvark.C3bArray.Id] = DecodeC3bArray, + [Durable.Aardvark.C3f.Id] = DecodeC3f, + [Durable.Aardvark.C3fArray.Id] = DecodeC3fArray, + [Durable.Aardvark.C4b.Id] = DecodeC4b, + [Durable.Aardvark.C4bArray.Id] = DecodeC4bArray, + [Durable.Aardvark.C4f.Id] = DecodeC4f, + [Durable.Aardvark.C4fArray.Id] = DecodeC4fArray, + }; + } + + #region Encode + + private static readonly Action EncodeGuid = (s, o) => s.Write(((Guid)o).ToByteArray(), 0, 16); + private static readonly Action EncodeGuidArray = (s, o) => EncodeArray(s, (Guid[])o); + private static readonly Action EncodeUInt8 = (s, o) => s.Write((byte)o); + private static readonly Action EncodeUInt8Array = (s, o) => EncodeArray(s, (byte[])o); + private static readonly Action EncodeInt16 = (s, o) => s.Write((short)o); + private static readonly Action EncodeInt16Array = (s, o) => EncodeArray(s, (short[])o); + private static readonly Action EncodeUInt16 = (s, o) => s.Write((ushort)o); + private static readonly Action EncodeUInt16Array = (s, o) => EncodeArray(s, (ushort[])o); + private static readonly Action EncodeInt32 = (s, o) => s.Write((int)o); + private static readonly Action EncodeInt32Array = (s, o) => EncodeArray(s, (int[])o); + private static readonly Action EncodeUInt32 = (s, o) => s.Write((uint)o); + private static readonly Action EncodeUInt32Array = (s, o) => EncodeArray(s, (uint[])o); + private static readonly Action EncodeInt64 = (s, o) => s.Write((long)o); + private static readonly Action EncodeInt64Array = (s, o) => EncodeArray(s, (long[])o); + private static readonly Action EncodeUInt64 = (s, o) => s.Write((ulong)o); + private static readonly Action EncodeUInt64Array = (s, o) => EncodeArray(s, (ulong[])o); + private static readonly Action EncodeFloat32 = (s, o) => s.Write((float)o); + private static readonly Action EncodeFloat32Array = (s, o) => EncodeArray(s, (float[])o); + private static readonly Action EncodeFloat64 = (s, o) => s.Write((double)o); + private static readonly Action EncodeFloat64Array = (s, o) => EncodeArray(s, (double[])o); + private static readonly Action EncodeStringUtf8 = (s, o) => EncodeArray(s, Encoding.UTF8.GetBytes((string)o)); + + private static readonly Action EncodeDurableMapWithoutHeader = + (s, o) => + { + var xs = (IEnumerable>)o; + var count = xs.Count(); + s.Write(count); + foreach (var x in xs) Encode(s, x.Key, x.Value); + }; + + private static readonly Action EncodeCell = + (s, o) => { var x = (Cell)o; s.Write(x.X); s.Write(x.Y); s.Write(x.Z); s.Write(x.Exponent); }; + private static readonly Action EncodeCellArray = + (s, o) => EncodeArray(s, (Cell[])o); + + + private static readonly Action EncodeV2f = + (s, o) => { var x = (V2f)o; s.Write(x.X); s.Write(x.Y); }; + private static readonly Action EncodeV2fArray = + (s, o) => EncodeArray(s, (V2f[])o); + + private static readonly Action EncodeV3f = + (s, o) => { var x = (V3f)o; s.Write(x.X); s.Write(x.Y); s.Write(x.Z); }; + private static readonly Action EncodeV3fArray = + (s, o) => EncodeArray(s, (V3f[])o); + + private static readonly Action EncodeV4f = + (s, o) => { var x = (V4f)o; s.Write(x.X); s.Write(x.Y); s.Write(x.Z); s.Write(x.W); }; + private static readonly Action EncodeV4fArray = + (s, o) => EncodeArray(s, (V4f[])o); + + + private static readonly Action EncodeV2d = + (s, o) => { var x = (V2d)o; s.Write(x.X); s.Write(x.Y); }; + private static readonly Action EncodeV2dArray = + (s, o) => EncodeArray(s, (V2d[])o); + + private static readonly Action EncodeV3d = + (s, o) => { var x = (V3d)o; s.Write(x.X); s.Write(x.Y); s.Write(x.Z); }; + private static readonly Action EncodeV3dArray = + (s, o) => EncodeArray(s, (V3d[])o); + + private static readonly Action EncodeV4d = + (s, o) => { var x = (V4d)o; s.Write(x.X); s.Write(x.Y); s.Write(x.Z); s.Write(x.W); }; + private static readonly Action EncodeV4dArray = + (s, o) => EncodeArray(s, (V4d[])o); + + + + private static readonly Action EncodeM33f = + (s, o) => { var x = (M33f)o; s.Write(x.M00); s.Write(x.M01); s.Write(x.M02); s.Write(x.M10); s.Write(x.M11); s.Write(x.M12); s.Write(x.M20); s.Write(x.M21); s.Write(x.M22); }; + private static readonly Action EncodeM33fArray = + (s, o) => EncodeArray(s, (M33f[])o); + + private static readonly Action EncodeM33d = + (s, o) => { var x = (M33d)o; s.Write(x.M00); s.Write(x.M01); s.Write(x.M02); s.Write(x.M10); s.Write(x.M11); s.Write(x.M12); s.Write(x.M20); s.Write(x.M21); s.Write(x.M22); }; + private static readonly Action EncodeM33dArray = + (s, o) => EncodeArray(s, (M33d[])o); + + + private static readonly Action EncodeBox2f = + (s, o) => { var x = (Box2f)o; EncodeV2f(s, x.Min); EncodeV2f(s, x.Max); }; + private static readonly Action EncodeBox2fArray = + (s, o) => EncodeArray(s, (Box2f[])o); + + private static readonly Action EncodeBox2d = + (s, o) => { var x = (Box2d)o; EncodeV2d(s, x.Min); EncodeV2d(s, x.Max); }; + private static readonly Action EncodeBox2dArray = + (s, o) => EncodeArray(s, (Box2d[])o); + + + private static readonly Action EncodeBox3f = + (s, o) => { var x = (Box3f)o; EncodeV3f(s, x.Min); EncodeV3f(s, x.Max); }; + private static readonly Action EncodeBox3fArray = + (s, o) => EncodeArray(s, (Box3f[])o); + + private static readonly Action EncodeBox3d = + (s, o) => { var x = (Box3d)o; EncodeV3d(s, x.Min); EncodeV3d(s, x.Max); }; + private static readonly Action EncodeBox3dArray = + (s, o) => EncodeArray(s, (Box3d[])o); + + private static readonly Action EncodeRange1i = + (s, o) => { var x = (Range1i)o; s.Write(x.Min); s.Write(x.Max); }; + private static readonly Action EncodeRange1iArray = + (s, o) => EncodeArray(s, (Range1i[])o); + + /// + /// Wrong implemention (should be serialized as BGR, not RGB) according to Durable definition. + /// For backwards compatibility, this should be fixed with magic, by obsoleting Aardvark.C3b durable definition used by this implemention and creating new Aardvark.C3b durable def. + /// + private static readonly Action EncodeC3b = + (s, o) => { var x = (C3b)o; s.Write(x.R); s.Write(x.G); s.Write(x.B); }; + /// + /// This should be correctly serialized, since the array is serialzed in memory layout, which is BGR. See EncodeC3b. + /// + private static readonly Action EncodeC3bArray = + (s, o) => EncodeArray(s, (C3b[])o); + + private static readonly Action EncodeC3f = + (s, o) => { var x = (C3f)o; s.Write(x.R); s.Write(x.G); s.Write(x.B); }; + private static readonly Action EncodeC3fArray = + (s, o) => EncodeArray(s, (C3f[])o); + + /// + /// Correct implemention, serialized as BGRA (because it was added later after the problem was known). + /// + private static readonly Action EncodeC4b = + (s, o) => { var x = (C4b)o; s.Write(x.B); s.Write(x.G); s.Write(x.R); s.Write(x.A); }; + private static readonly Action EncodeC4bArray = + (s, o) => EncodeArray(s, (C4b[])o); + + private static readonly Action EncodeC4f = + (s, o) => { var x = (C4f)o; s.Write(x.R); s.Write(x.G); s.Write(x.B); s.Write(x.A); }; + private static readonly Action EncodeC4fArray = + (s, o) => EncodeArray(s, (C4f[])o); + + private static unsafe void EncodeArray(BinaryWriter s, params T[] xs) where T : struct + { + var gc = GCHandle.Alloc(xs, GCHandleType.Pinned); + var size = xs.Length * Marshal.SizeOf(); + var dst = new byte[size]; + try + { + Marshal.Copy(gc.AddrOfPinnedObject(), dst, 0, size); + s.Write(xs.Length); + s.Write(dst); } + finally + { + gc.Free(); + } + } - #region Encode - - private static readonly Action EncodeGuid = (s, o) => s.Write(((Guid)o).ToByteArray(), 0, 16); - private static readonly Action EncodeGuidArray = (s, o) => EncodeArray(s, (Guid[])o); - private static readonly Action EncodeUInt8 = (s, o) => s.Write((byte)o); - private static readonly Action EncodeUInt8Array = (s, o) => EncodeArray(s, (byte[])o); - private static readonly Action EncodeInt16 = (s, o) => s.Write((short)o); - private static readonly Action EncodeInt16Array = (s, o) => EncodeArray(s, (short[])o); - private static readonly Action EncodeUInt16 = (s, o) => s.Write((ushort)o); - private static readonly Action EncodeUInt16Array = (s, o) => EncodeArray(s, (ushort[])o); - private static readonly Action EncodeInt32 = (s, o) => s.Write((int)o); - private static readonly Action EncodeInt32Array = (s, o) => EncodeArray(s, (int[])o); - private static readonly Action EncodeUInt32 = (s, o) => s.Write((uint)o); - private static readonly Action EncodeUInt32Array = (s, o) => EncodeArray(s, (uint[])o); - private static readonly Action EncodeInt64 = (s, o) => s.Write((long)o); - private static readonly Action EncodeInt64Array = (s, o) => EncodeArray(s, (long[])o); - private static readonly Action EncodeUInt64 = (s, o) => s.Write((ulong)o); - private static readonly Action EncodeUInt64Array = (s, o) => EncodeArray(s, (ulong[])o); - private static readonly Action EncodeFloat32 = (s, o) => s.Write((float)o); - private static readonly Action EncodeFloat32Array = (s, o) => EncodeArray(s, (float[])o); - private static readonly Action EncodeFloat64 = (s, o) => s.Write((double)o); - private static readonly Action EncodeFloat64Array = (s, o) => EncodeArray(s, (double[])o); - private static readonly Action EncodeStringUtf8 = (s, o) => EncodeArray(s, Encoding.UTF8.GetBytes((string)o)); - - private static readonly Action EncodeDurableMapWithoutHeader = - (s, o) => - { - var xs = (IEnumerable>)o; - var count = xs.Count(); - s.Write(count); - foreach (var x in xs) Encode(s, x.Key, x.Value); - }; - - private static readonly Action EncodeCell = - (s, o) => { var x = (Cell)o; s.Write(x.X); s.Write(x.Y); s.Write(x.Z); s.Write(x.Exponent); }; - private static readonly Action EncodeCellArray = - (s, o) => EncodeArray(s, (Cell[])o); - - - private static readonly Action EncodeV2f = - (s, o) => { var x = (V2f)o; s.Write(x.X); s.Write(x.Y); }; - private static readonly Action EncodeV2fArray = - (s, o) => EncodeArray(s, (V2f[])o); - - private static readonly Action EncodeV3f = - (s, o) => { var x = (V3f)o; s.Write(x.X); s.Write(x.Y); s.Write(x.Z); }; - private static readonly Action EncodeV3fArray = - (s, o) => EncodeArray(s, (V3f[])o); - - private static readonly Action EncodeV4f = - (s, o) => { var x = (V4f)o; s.Write(x.X); s.Write(x.Y); s.Write(x.Z); s.Write(x.W); }; - private static readonly Action EncodeV4fArray = - (s, o) => EncodeArray(s, (V4f[])o); - - - private static readonly Action EncodeV2d = - (s, o) => { var x = (V2d)o; s.Write(x.X); s.Write(x.Y); }; - private static readonly Action EncodeV2dArray = - (s, o) => EncodeArray(s, (V2d[])o); - - private static readonly Action EncodeV3d = - (s, o) => { var x = (V3d)o; s.Write(x.X); s.Write(x.Y); s.Write(x.Z); }; - private static readonly Action EncodeV3dArray = - (s, o) => EncodeArray(s, (V3d[])o); - - private static readonly Action EncodeV4d = - (s, o) => { var x = (V4d)o; s.Write(x.X); s.Write(x.Y); s.Write(x.Z); s.Write(x.W); }; - private static readonly Action EncodeV4dArray = - (s, o) => EncodeArray(s, (V4d[])o); - - - - private static readonly Action EncodeM33f = - (s, o) => { var x = (M33f)o; s.Write(x.M00); s.Write(x.M01); s.Write(x.M02); s.Write(x.M10); s.Write(x.M11); s.Write(x.M12); s.Write(x.M20); s.Write(x.M21); s.Write(x.M22); }; - private static readonly Action EncodeM33fArray = - (s, o) => EncodeArray(s, (M33f[])o); - - private static readonly Action EncodeM33d = - (s, o) => { var x = (M33d)o; s.Write(x.M00); s.Write(x.M01); s.Write(x.M02); s.Write(x.M10); s.Write(x.M11); s.Write(x.M12); s.Write(x.M20); s.Write(x.M21); s.Write(x.M22); }; - private static readonly Action EncodeM33dArray = - (s, o) => EncodeArray(s, (M33d[])o); - - - private static readonly Action EncodeBox2f = - (s, o) => { var x = (Box2f)o; EncodeV2f(s, x.Min); EncodeV2f(s, x.Max); }; - private static readonly Action EncodeBox2fArray = - (s, o) => EncodeArray(s, (Box2f[])o); - - private static readonly Action EncodeBox2d = - (s, o) => { var x = (Box2d)o; EncodeV2d(s, x.Min); EncodeV2d(s, x.Max); }; - private static readonly Action EncodeBox2dArray = - (s, o) => EncodeArray(s, (Box2d[])o); - - - private static readonly Action EncodeBox3f = - (s, o) => { var x = (Box3f)o; EncodeV3f(s, x.Min); EncodeV3f(s, x.Max); }; - private static readonly Action EncodeBox3fArray = - (s, o) => EncodeArray(s, (Box3f[])o); - - private static readonly Action EncodeBox3d = - (s, o) => { var x = (Box3d)o; EncodeV3d(s, x.Min); EncodeV3d(s, x.Max); }; - private static readonly Action EncodeBox3dArray = - (s, o) => EncodeArray(s, (Box3d[])o); - - private static readonly Action EncodeRange1i = - (s, o) => { var x = (Range1i)o; s.Write(x.Min); s.Write(x.Max); }; - private static readonly Action EncodeRange1iArray = - (s, o) => EncodeArray(s, (Range1i[])o); - - /// - /// Wrong implemention (should be serialized as BGR, not RGB) according to Durable definition. - /// For backwards compatibility, this should be fixed with magic, by obsoleting Aardvark.C3b durable definition used by this implemention and creating new Aardvark.C3b durable def. - /// - private static readonly Action EncodeC3b = - (s, o) => { var x = (C3b)o; s.Write(x.R); s.Write(x.G); s.Write(x.B); }; - /// - /// This should be correctly serialized, since the array is serialzed in memory layout, which is BGR. See EncodeC3b. - /// - private static readonly Action EncodeC3bArray = - (s, o) => EncodeArray(s, (C3b[])o); - - private static readonly Action EncodeC3f = - (s, o) => { var x = (C3f)o; s.Write(x.R); s.Write(x.G); s.Write(x.B); }; - private static readonly Action EncodeC3fArray = - (s, o) => EncodeArray(s, (C3f[])o); - - /// - /// Correct implemention, serialized as BGRA (because it was added later after the problem was known). - /// - private static readonly Action EncodeC4b = - (s, o) => { var x = (C4b)o; s.Write(x.B); s.Write(x.G); s.Write(x.R); s.Write(x.A); }; - private static readonly Action EncodeC4bArray = - (s, o) => EncodeArray(s, (C4b[])o); - - private static readonly Action EncodeC4f = - (s, o) => { var x = (C4f)o; s.Write(x.R); s.Write(x.G); s.Write(x.B); s.Write(x.A); }; - private static readonly Action EncodeC4fArray = - (s, o) => EncodeArray(s, (C4f[])o); - - private static unsafe void EncodeArray(BinaryWriter s, params T[] xs) where T : struct + private static void Encode(BinaryWriter stream, Durable.Def def, object x) + { + if (def.Type != Durable.Primitives.Unit.Id) { - var gc = GCHandle.Alloc(xs, GCHandleType.Pinned); - var size = xs.Length * Marshal.SizeOf(); - var dst = new byte[size]; - try + if (s_encoders.TryGetValue(def.Type, out var encoder)) { - Marshal.Copy(gc.AddrOfPinnedObject(), dst, 0, size); - s.Write(xs.Length); - s.Write(dst); + EncodeGuid(stream, def.Id); + ((Action)encoder)(stream, x); } - finally + else { - gc.Free(); + var unknownDef = Durable.Get(def.Type); + throw new InvalidOperationException($"Unknown definition {unknownDef}."); } } - - private static void Encode(BinaryWriter stream, Durable.Def def, object x) + else { - if (def.Type != Durable.Primitives.Unit.Id) + if (s_encoders.TryGetValue(def.Id, out var encoder)) { - if (s_encoders.TryGetValue(def.Type, out var encoder)) - { - EncodeGuid(stream, def.Id); - ((Action)encoder)(stream, x); - } - else - { - var unknownDef = Durable.Get(def.Type); - throw new InvalidOperationException($"Unknown definition {unknownDef}."); - } + ((Action)encoder)(stream, x); } else { - if (s_encoders.TryGetValue(def.Id, out var encoder)) - { - ((Action)encoder)(stream, x); - } - else - { - var unknownDef = Durable.Get(def.Id); - throw new InvalidOperationException($"Unknown definition {unknownDef}."); - } + var unknownDef = Durable.Get(def.Id); + throw new InvalidOperationException($"Unknown definition {unknownDef}."); } } + } - #endregion + #endregion - #region Decode + #region Decode - private static readonly Func DecodeGuid = s => new Guid(s.ReadBytes(16)); - private static readonly Func DecodeStringUtf8 = s => Encoding.UTF8.GetString(DecodeArray(s)); + private static readonly Func DecodeGuid = s => new Guid(s.ReadBytes(16)); + private static readonly Func DecodeStringUtf8 = s => Encoding.UTF8.GetString(DecodeArray(s)); - private static readonly Func DecodeUInt8 = s => s.ReadByte(); - private static readonly Func DecodeUInt8Array = s => DecodeArray(s); + private static readonly Func DecodeUInt8 = s => s.ReadByte(); + private static readonly Func DecodeUInt8Array = s => DecodeArray(s); - private static readonly Func DecodeInt16 = s => s.ReadInt16(); - private static readonly Func DecodeInt16Array = s => DecodeArray(s); + private static readonly Func DecodeInt16 = s => s.ReadInt16(); + private static readonly Func DecodeInt16Array = s => DecodeArray(s); - private static readonly Func DecodeUInt16 = s => s.ReadUInt16(); - private static readonly Func DecodeUInt16Array = s => DecodeArray(s); + private static readonly Func DecodeUInt16 = s => s.ReadUInt16(); + private static readonly Func DecodeUInt16Array = s => DecodeArray(s); - private static readonly Func DecodeInt32 = s => s.ReadInt32(); - private static readonly Func DecodeInt32Array = s => DecodeArray(s); + private static readonly Func DecodeInt32 = s => s.ReadInt32(); + private static readonly Func DecodeInt32Array = s => DecodeArray(s); - private static readonly Func DecodeUInt32 = s => s.ReadUInt32(); - private static readonly Func DecodeUInt32Array = s => DecodeArray(s); + private static readonly Func DecodeUInt32 = s => s.ReadUInt32(); + private static readonly Func DecodeUInt32Array = s => DecodeArray(s); - private static readonly Func DecodeInt64 = s => s.ReadInt64(); - private static readonly Func DecodeInt64Array = s => DecodeArray(s); + private static readonly Func DecodeInt64 = s => s.ReadInt64(); + private static readonly Func DecodeInt64Array = s => DecodeArray(s); - private static readonly Func DecodeUInt64 = s => s.ReadUInt64(); - private static readonly Func DecodeUInt64Array = s => DecodeArray(s); + private static readonly Func DecodeUInt64 = s => s.ReadUInt64(); + private static readonly Func DecodeUInt64Array = s => DecodeArray(s); - private static readonly Func DecodeFloat32 = s => s.ReadSingle(); - private static readonly Func DecodeFloat32Array = s => DecodeArray(s); + private static readonly Func DecodeFloat32 = s => s.ReadSingle(); + private static readonly Func DecodeFloat32Array = s => DecodeArray(s); - private static readonly Func DecodeFloat64 = s => s.ReadDouble(); - private static readonly Func DecodeFloat64Array = s => DecodeArray(s); + private static readonly Func DecodeFloat64 = s => s.ReadDouble(); + private static readonly Func DecodeFloat64Array = s => DecodeArray(s); - private static readonly Func DecodeDurableMapWithoutHeader = - s => - { - var count = s.ReadInt32(); - var entries = new KeyValuePair[count]; - for (var i = 0; i < count; i++) - { - var e = Decode(s); - entries[i] = new KeyValuePair(e.Item1, e.Item2); - } - return ImmutableDictionary.CreateRange(entries); - }; - - private static readonly Func DecodeCell = s => new Cell(s.ReadInt64(), s.ReadInt64(), s.ReadInt64(), s.ReadInt32()); - private static readonly Func DecodeCellArray = s => DecodeArray(s); - - private static readonly Func DecodeV2f = s => new V2f(s.ReadSingle(), s.ReadSingle()); - private static readonly Func DecodeV2fArray = s => DecodeArray(s); - private static readonly Func DecodeV3f = s => new V3f(s.ReadSingle(), s.ReadSingle(), s.ReadSingle()); - private static readonly Func DecodeV3fArray = s => DecodeArray(s); - private static readonly Func DecodeV4f = s => new V4f(s.ReadSingle(), s.ReadSingle(), s.ReadSingle(), s.ReadSingle()); - private static readonly Func DecodeV4fArray = s => DecodeArray(s); - - private static readonly Func DecodeV2d = s => new V2d(s.ReadDouble(), s.ReadDouble()); - private static readonly Func DecodeV2dArray = s => DecodeArray(s); - private static readonly Func DecodeV3d = s => new V3d(s.ReadDouble(), s.ReadDouble(), s.ReadDouble()); - private static readonly Func DecodeV3dArray = s => DecodeArray(s); - private static readonly Func DecodeV4d = s => new V4d(s.ReadDouble(), s.ReadDouble(), s.ReadDouble(), s.ReadDouble()); - private static readonly Func DecodeV4dArray = s => DecodeArray(s); - - private static readonly Func DecodeM33f = s => new M33f(s.ReadSingle(), s.ReadSingle(), s.ReadSingle(), s.ReadSingle(), s.ReadSingle(), s.ReadSingle(), s.ReadSingle(), s.ReadSingle(), s.ReadSingle()); - private static readonly Func DecodeM33fArray = s => DecodeArray(s); - private static readonly Func DecodeM33d = s => new M33d(s.ReadDouble(), s.ReadDouble(), s.ReadDouble(), s.ReadDouble(), s.ReadDouble(), s.ReadDouble(), s.ReadDouble(), s.ReadDouble(), s.ReadDouble()); - private static readonly Func DecodeM33dArray = s => DecodeArray(s); - - private static readonly Func DecodeBox2f = s => new Box2f((V2f)DecodeV2f(s), (V2f)DecodeV2f(s)); - private static readonly Func DecodeBox2fArray = s => DecodeArray(s); - private static readonly Func DecodeBox2d = s => new Box2d((V2d)DecodeV2d(s), (V2d)DecodeV2d(s)); - private static readonly Func DecodeBox2dArray = s => DecodeArray(s); - - private static readonly Func DecodeBox3f = s => new Box3f((V3f)DecodeV3f(s), (V3f)DecodeV3f(s)); - private static readonly Func DecodeBox3fArray = s => DecodeArray(s); - private static readonly Func DecodeBox3d = s => new Box3d((V3d)DecodeV3d(s), (V3d)DecodeV3d(s)); - private static readonly Func DecodeBox3dArray = s => DecodeArray(s); - - private static readonly Func DecodeRange1i = s => new Range1i(s.ReadInt32(), s.ReadInt32()); - private static readonly Func DecodeRange1iArray = s => DecodeArray(s); - - /// - /// Wrong implemention (should be serialized as BGR, not RGB) according to Durable definition. - /// For backwards compatibility, this should be fixed with magic, by obsoleting Aardvark.C3b durable definition used by this implemention and creating new Aardvark.C3b durable def. - /// - private static readonly Func DecodeC3b = s => new C3b(s.ReadByte(), s.ReadByte(), s.ReadByte()); - /// - /// This should be correctly deserialized, since the array is serialized in memory layout, which is BGR. See EncodeC3b. - /// - private static readonly Func DecodeC3bArray = s => DecodeArray(s); - - private static readonly Func DecodeC3f = s => new C3f(s.ReadSingle(), s.ReadSingle(), s.ReadSingle()); - private static readonly Func DecodeC3fArray = s => DecodeArray(s); - - /// - /// Correct implemention, serialized as BGRA (because it was added later after the problem was known). - /// - private static readonly Func DecodeC4b = s => + private static readonly Func DecodeDurableMapWithoutHeader = + s => { - var b = s.ReadByte(); - var g = s.ReadByte(); - var r = s.ReadByte(); - var a = s.ReadByte(); - return new C4b(r, g, b, a); + var count = s.ReadInt32(); + var entries = new KeyValuePair[count]; + for (var i = 0; i < count; i++) + { + var e = Decode(s); + entries[i] = new KeyValuePair(e.Item1, e.Item2); + } + return ImmutableDictionary.CreateRange(entries); }; - private static readonly Func DecodeC4bArray = s => DecodeArray(s); - private static readonly Func DecodeC4f = s => new C4f(s.ReadSingle(), s.ReadSingle(), s.ReadSingle(), s.ReadSingle()); - private static readonly Func DecodeC4fArray = s => DecodeArray(s); + private static readonly Func DecodeCell = s => new Cell(s.ReadInt64(), s.ReadInt64(), s.ReadInt64(), s.ReadInt32()); + private static readonly Func DecodeCellArray = s => DecodeArray(s); + + private static readonly Func DecodeV2f = s => new V2f(s.ReadSingle(), s.ReadSingle()); + private static readonly Func DecodeV2fArray = s => DecodeArray(s); + private static readonly Func DecodeV3f = s => new V3f(s.ReadSingle(), s.ReadSingle(), s.ReadSingle()); + private static readonly Func DecodeV3fArray = s => DecodeArray(s); + private static readonly Func DecodeV4f = s => new V4f(s.ReadSingle(), s.ReadSingle(), s.ReadSingle(), s.ReadSingle()); + private static readonly Func DecodeV4fArray = s => DecodeArray(s); + + private static readonly Func DecodeV2d = s => new V2d(s.ReadDouble(), s.ReadDouble()); + private static readonly Func DecodeV2dArray = s => DecodeArray(s); + private static readonly Func DecodeV3d = s => new V3d(s.ReadDouble(), s.ReadDouble(), s.ReadDouble()); + private static readonly Func DecodeV3dArray = s => DecodeArray(s); + private static readonly Func DecodeV4d = s => new V4d(s.ReadDouble(), s.ReadDouble(), s.ReadDouble(), s.ReadDouble()); + private static readonly Func DecodeV4dArray = s => DecodeArray(s); + + private static readonly Func DecodeM33f = s => new M33f(s.ReadSingle(), s.ReadSingle(), s.ReadSingle(), s.ReadSingle(), s.ReadSingle(), s.ReadSingle(), s.ReadSingle(), s.ReadSingle(), s.ReadSingle()); + private static readonly Func DecodeM33fArray = s => DecodeArray(s); + private static readonly Func DecodeM33d = s => new M33d(s.ReadDouble(), s.ReadDouble(), s.ReadDouble(), s.ReadDouble(), s.ReadDouble(), s.ReadDouble(), s.ReadDouble(), s.ReadDouble(), s.ReadDouble()); + private static readonly Func DecodeM33dArray = s => DecodeArray(s); + + private static readonly Func DecodeBox2f = s => new Box2f((V2f)DecodeV2f(s), (V2f)DecodeV2f(s)); + private static readonly Func DecodeBox2fArray = s => DecodeArray(s); + private static readonly Func DecodeBox2d = s => new Box2d((V2d)DecodeV2d(s), (V2d)DecodeV2d(s)); + private static readonly Func DecodeBox2dArray = s => DecodeArray(s); + + private static readonly Func DecodeBox3f = s => new Box3f((V3f)DecodeV3f(s), (V3f)DecodeV3f(s)); + private static readonly Func DecodeBox3fArray = s => DecodeArray(s); + private static readonly Func DecodeBox3d = s => new Box3d((V3d)DecodeV3d(s), (V3d)DecodeV3d(s)); + private static readonly Func DecodeBox3dArray = s => DecodeArray(s); + + private static readonly Func DecodeRange1i = s => new Range1i(s.ReadInt32(), s.ReadInt32()); + private static readonly Func DecodeRange1iArray = s => DecodeArray(s); + + /// + /// Wrong implemention (should be serialized as BGR, not RGB) according to Durable definition. + /// For backwards compatibility, this should be fixed with magic, by obsoleting Aardvark.C3b durable definition used by this implemention and creating new Aardvark.C3b durable def. + /// + private static readonly Func DecodeC3b = s => new C3b(s.ReadByte(), s.ReadByte(), s.ReadByte()); + /// + /// This should be correctly deserialized, since the array is serialized in memory layout, which is BGR. See EncodeC3b. + /// + private static readonly Func DecodeC3bArray = s => DecodeArray(s); + + private static readonly Func DecodeC3f = s => new C3f(s.ReadSingle(), s.ReadSingle(), s.ReadSingle()); + private static readonly Func DecodeC3fArray = s => DecodeArray(s); + + /// + /// Correct implemention, serialized as BGRA (because it was added later after the problem was known). + /// + private static readonly Func DecodeC4b = s => + { + var b = s.ReadByte(); + var g = s.ReadByte(); + var r = s.ReadByte(); + var a = s.ReadByte(); + return new C4b(r, g, b, a); + }; + private static readonly Func DecodeC4bArray = s => DecodeArray(s); + + private static readonly Func DecodeC4f = s => new C4f(s.ReadSingle(), s.ReadSingle(), s.ReadSingle(), s.ReadSingle()); + private static readonly Func DecodeC4fArray = s => DecodeArray(s); - private static unsafe T[] DecodeArray(BinaryReader s) where T : struct + private static unsafe T[] DecodeArray(BinaryReader s) where T : struct + { + var count = s.ReadInt32(); + var size = count * Marshal.SizeOf(); + var buffer = s.ReadBytes(size); + var xs = new T[count]; + var gc = GCHandle.Alloc(xs, GCHandleType.Pinned); + try { - var count = s.ReadInt32(); - var size = count * Marshal.SizeOf(); - var buffer = s.ReadBytes(size); - var xs = new T[count]; - var gc = GCHandle.Alloc(xs, GCHandleType.Pinned); - try - { - Marshal.Copy(buffer, 0, gc.AddrOfPinnedObject(), size); - return xs; - } - finally - { - gc.Free(); - } + Marshal.Copy(buffer, 0, gc.AddrOfPinnedObject(), size); + return xs; + } + finally + { + gc.Free(); } + } - private static readonly Func DecodeGuidArray = s => DecodeArray(s); + private static readonly Func DecodeGuidArray = s => DecodeArray(s); - private static (Durable.Def, object) Decode(BinaryReader stream) + private static (Durable.Def, object) Decode(BinaryReader stream) + { + var key = (Guid)DecodeGuid(stream); + if (!Durable.TryGet(key, out var def)) { - var key = (Guid)DecodeGuid(stream); - if (!Durable.TryGet(key, out var def)) - { - stream.BaseStream.Position -= 16; - def = Durable.Get(Durable.Primitives.DurableMap.Id); - } + stream.BaseStream.Position -= 16; + def = Durable.Get(Durable.Primitives.DurableMap.Id); + } - if (def.Type != Durable.Primitives.Unit.Id) + if (def.Type != Durable.Primitives.Unit.Id) + { + if (s_decoders.TryGetValue(def.Type, out var decoder)) { - if (s_decoders.TryGetValue(def.Type, out var decoder)) - { - var o = ((Func)decoder)(stream); - return (def, o); - } - else - { - var unknownDef = Durable.Get(def.Type); - throw new InvalidOperationException($"Unknown definition {unknownDef}."); - } + var o = ((Func)decoder)(stream); + return (def, o); } else { - if (s_decoders.TryGetValue(def.Id, out var decoder)) - { - var o = ((Func)decoder)(stream); - return (def, o); - } - else - { - var unknownDef = Durable.Get(def.Id); - throw new InvalidOperationException($"Unknown definition {unknownDef}."); - } + var unknownDef = Durable.Get(def.Type); + throw new InvalidOperationException($"Unknown definition {unknownDef}."); } } - - #endregion - - #region Serialization - - /// - /// Serializes value x to byte array. - /// Can be deserialized with Deserialize. - /// - public static byte[] Serialize(Durable.Def def, T x) where T : notnull + else { - using var ms = new MemoryStream(); - using var bw = new BinaryWriter(ms); - if (def.Type == Durable.Primitives.Unit.Id) + if (s_decoders.TryGetValue(def.Id, out var decoder)) { - // encode type of primitive value, so we can roundtrip with Deserialize - // (since it is not encoded by the Encode function called below) - EncodeGuid(bw, def.Id); + var o = ((Func)decoder)(stream); + return (def, o); } - - Encode(bw, def, x); - bw.Flush(); - return ms.ToArray(); - } - - /// - /// Serializes value x to stream. - /// Can be deserialized with Deserialize. - /// - public static void Serialize(BinaryWriter stream, Durable.Def def, T x) where T : notnull - { - if (def.Type == Durable.Primitives.Unit.Id) + else { - // encode type of primitive value, so we can roundtrip with Deserialize - // (since it is not encoded by the Encode function called below) - EncodeGuid(stream, def.Id); + var unknownDef = Durable.Get(def.Id); + throw new InvalidOperationException($"Unknown definition {unknownDef}."); } - - Encode(stream, def, x); - } - - /// - /// Serializes value x to stream. - /// Can be deserialized with Deserialize. - /// - public static void Serialize(Stream stream, Durable.Def def, T x) where T : notnull - { - using var bw = new BinaryWriter(stream); - Serialize(bw, def, x); } + } + #endregion - /// - /// Deserializes value from stream. - /// - public static (Durable.Def, object) Deserialize(BinaryReader stream) - => Decode(stream); + #region Serialization - /// - /// Deserializes value from stream. - /// - public static (Durable.Def, object) Deserialize(Stream stream) + /// + /// Serializes value x to byte array. + /// Can be deserialized with Deserialize. + /// + public static byte[] Serialize(Durable.Def def, T x) where T : notnull + { + using var ms = new MemoryStream(); + using var bw = new BinaryWriter(ms); + if (def.Type == Durable.Primitives.Unit.Id) { - using var br = new BinaryReader(stream); - return Decode(br); + // encode type of primitive value, so we can roundtrip with Deserialize + // (since it is not encoded by the Encode function called below) + EncodeGuid(bw, def.Id); } - /// - /// Deserializes value from byte array. - /// - public static (Durable.Def, object) Deserialize(byte[] buffer) - { - using var ms = new MemoryStream(buffer); - using var br = new BinaryReader(ms); - return Decode(br); - } + Encode(bw, def, x); + bw.Flush(); + return ms.ToArray(); + } - /// - /// Deserializes value from file. - /// - public static (Durable.Def, object) Deserialize(string filename) + /// + /// Serializes value x to stream. + /// Can be deserialized with Deserialize. + /// + public static void Serialize(BinaryWriter stream, Durable.Def def, T x) where T : notnull + { + if (def.Type == Durable.Primitives.Unit.Id) { - using var ms = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read); - using var br = new BinaryReader(ms); - return Decode(br); + // encode type of primitive value, so we can roundtrip with Deserialize + // (since it is not encoded by the Encode function called below) + EncodeGuid(stream, def.Id); } + Encode(stream, def, x); + } + /// + /// Serializes value x to stream. + /// Can be deserialized with Deserialize. + /// + public static void Serialize(Stream stream, Durable.Def def, T x) where T : notnull + { + using var bw = new BinaryWriter(stream); + Serialize(bw, def, x); + } - /// - /// Deserializes value from stream. - /// - public static T DeserializeAs(BinaryReader stream) => (T)Deserialize(stream).Item2; - /// - /// Deserializes value from stream. - /// - public static T DeserializeAs(Stream stream) => (T)Deserialize(stream).Item2; + /// + /// Deserializes value from stream. + /// + public static (Durable.Def, object) Deserialize(BinaryReader stream) + => Decode(stream); - /// - /// Deserializes value from byte array. - /// - public static T DeserializeAs(byte[] buffer) => (T)Deserialize(buffer).Item2; + /// + /// Deserializes value from stream. + /// + public static (Durable.Def, object) Deserialize(Stream stream) + { + using var br = new BinaryReader(stream); + return Decode(br); + } - /// - /// Deserializes value from file. - /// - public static T DeserializeAs(string filename) => (T)Deserialize(filename).Item2; + /// + /// Deserializes value from byte array. + /// + public static (Durable.Def, object) Deserialize(byte[] buffer) + { + using var ms = new MemoryStream(buffer); + using var br = new BinaryReader(ms); + return Decode(br); + } - #endregion + /// + /// Deserializes value from file. + /// + public static (Durable.Def, object) Deserialize(string filename) + { + using var ms = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read); + using var br = new BinaryReader(ms); + return Decode(br); } + + + + /// + /// Deserializes value from stream. + /// + public static T DeserializeAs(BinaryReader stream) => (T)Deserialize(stream).Item2; + + /// + /// Deserializes value from stream. + /// + public static T DeserializeAs(Stream stream) => (T)Deserialize(stream).Item2; + + /// + /// Deserializes value from byte array. + /// + public static T DeserializeAs(byte[] buffer) => (T)Deserialize(buffer).Item2; + + /// + /// Deserializes value from file. + /// + public static T DeserializeAs(string filename) => (T)Deserialize(filename).Item2; + + #endregion } diff --git a/src/Aardvark.Geometry.PointSet/Utils/Disposable.cs b/src/Aardvark.Geometry.PointSet/Utils/Disposable.cs index 28c0c67a..dd574eff 100644 --- a/src/Aardvark.Geometry.PointSet/Utils/Disposable.cs +++ b/src/Aardvark.Geometry.PointSet/Utils/Disposable.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -13,29 +13,23 @@ You should have received a copy of the GNU Affero General Public License */ using System; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +internal class Disposable(Action onDispose) : IDisposable { - internal class Disposable : IDisposable - { - private readonly object m_lock = new(); - private bool m_isDisposed = false; - private Action m_onDispose; + private readonly object m_lock = new(); + private bool m_isDisposed = false; + private Action m_onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose)); - public Disposable(Action onDispose) + public void Dispose() + { + lock (m_lock) { - m_onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose)); + if (m_isDisposed) throw new ObjectDisposedException("Disposable"); + m_isDisposed = true; } - public void Dispose() - { - lock (m_lock) - { - if (m_isDisposed) throw new ObjectDisposedException("Disposable"); - m_isDisposed = true; - } - - m_onDispose(); - m_onDispose = null!; - } + m_onDispose(); + m_onDispose = null!; } } diff --git a/src/Aardvark.Geometry.PointSet/Utils/EnumerableExtensions.cs b/src/Aardvark.Geometry.PointSet/Utils/EnumerableExtensions.cs index 23f8aa94..ac4dda3e 100644 --- a/src/Aardvark.Geometry.PointSet/Utils/EnumerableExtensions.cs +++ b/src/Aardvark.Geometry.PointSet/Utils/EnumerableExtensions.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -18,156 +18,155 @@ You should have received a copy of the GNU Affero General Public License using System.Threading; using System.Threading.Tasks; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static class EnumerableExtensions { /// + /// Returns average and standard deviation of given values. /// - public static class EnumerableExtensions + public static (float avg, float stddev) ComputeAvgAndStdDev(this float[] xs) { - /// - /// Returns average and standard deviation of given values. - /// - public static (float avg, float stddev) ComputeAvgAndStdDev(this float[] xs) + var n = xs.Length; + + var sum = 0.0f; + for (var i = 0; i < n; i++) sum += xs[i]; + var avg = sum / n; + + sum = 0.0f; + for (var i = 0; i < n; i++) { - var n = xs.Length; + var d = xs[i] - avg; + sum += d * d; + } + var sd = (float)Math.Sqrt(sum / n); - var sum = 0.0f; - for (var i = 0; i < n; i++) sum += xs[i]; - var avg = sum / n; + return (avg, sd); + } - sum = 0.0f; - for (var i = 0; i < n; i++) - { - var d = xs[i] - avg; - sum += d * d; - } - var sd = (float)Math.Sqrt(sum / n); + internal static bool Any(this IEnumerable xs, Func predicate) + { + var i = 0; + foreach (var x in xs) if (predicate(x, i++)) return true; + return false; + } - return (avg, sd); - } + internal static bool Any(this T[] xs, Func predicate) + { + for (var i = 0; i < xs.Length; i++) if (predicate(xs[i], i)) return true; + return false; + } - internal static bool Any(this IEnumerable xs, Func predicate) - { - var i = 0; - foreach (var x in xs) if (predicate(x, i++)) return true; - return false; - } + internal static T[]? Append(this T[]? self, T[]? other) + { + if (self == null || self.Length == 0) return other ?? []; + if (other == null || other.Length == 0) return self; - internal static bool Any(this T[] xs, Func predicate) - { - for (var i = 0; i < xs.Length; i++) if (predicate(xs[i], i)) return true; - return false; - } + var xs = new T[self.Length + other.Length]; + for (var i = 0; i < self.Length; i++) xs[i] = self[i]; + for (var j = 0; j < other.Length; j++) xs[self.Length + j] = other[j]; + return xs; + } - internal static T[]? Append(this T[]? self, T[]? other) - { - if (self == null || self.Length == 0) return other ?? Array.Empty(); - if (other == null || other.Length == 0) return self; + internal static T[] Take(this T[] self, int count) + { + if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if (self == null || count == 0) return []; + if (self.Length <= count) return self; - var xs = new T[self.Length + other.Length]; - for (var i = 0; i < self.Length; i++) xs[i] = self[i]; - for (var j = 0; j < other.Length; j++) xs[self.Length + j] = other[j]; - return xs; - } + var xs = new T[count]; + for (var i = 0; i < count; i++) xs[i] = self[i]; + return xs; + } - internal static T[] Take(this T[] self, int count) - { - if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); - if (self == null || count == 0) return Array.Empty(); - if (self.Length <= count) return self; + internal static T[] Reordered(this T[] self, int[] ia) + { + if (self == null) throw new ArgumentNullException(nameof(self)); + if (ia == null) throw new ArgumentNullException(nameof(self)); + if (self.Length != ia.Length) throw new ArgumentException(nameof(ia)); - var xs = new T[count]; - for (var i = 0; i < count; i++) xs[i] = self[i]; - return xs; - } + var xs = new T[ia.Length]; + for (var i = 0; i < ia.Length; i++) xs[i] = self[ia[i]]; + return xs; + } - internal static T[] Reordered(this T[] self, int[] ia) - { - if (self == null) throw new ArgumentNullException(nameof(self)); - if (ia == null) throw new ArgumentNullException(nameof(self)); - if (self.Length != ia.Length) throw new ArgumentException(nameof(ia)); + /// + /// + public static T MapReduceParallel(this IEnumerable xs, + Func reduce, + int maxLevelOfParallelism, + Action? onFinish = null, + CancellationToken ct = default + ) + { + if (maxLevelOfParallelism < 1) maxLevelOfParallelism = Environment.ProcessorCount; - var xs = new T[ia.Length]; - for (var i = 0; i < ia.Length; i++) xs[i] = self[ia[i]]; - return xs; - } + var queue = new Queue(xs); + var imax = queue.Count - 1; + var queueSemapore = new SemaphoreSlim(maxLevelOfParallelism); + var exception = default(Exception); - /// - /// - public static T MapReduceParallel(this IEnumerable xs, - Func reduce, - int maxLevelOfParallelism, - Action? onFinish = null, - CancellationToken ct = default - ) - { - if (maxLevelOfParallelism < 1) maxLevelOfParallelism = Environment.ProcessorCount; + var inFlightCount = 0; + + var sw = new Stopwatch(); sw.Start(); - var queue = new Queue(xs); - var imax = queue.Count - 1; - var queueSemapore = new SemaphoreSlim(maxLevelOfParallelism); - var exception = default(Exception); + for (var i = 0; i < imax; i++) + { + while (queue.Count < 2) + { + CheckCancellationOrException(); + Task.Delay(100).Wait(); + } - var inFlightCount = 0; + T a, b; + lock (queue) + { + a = queue.Dequeue(); + b = queue.Dequeue(); + if (a == null || b == null) throw new InvalidOperationException(); + } - var sw = new Stopwatch(); sw.Start(); + queueSemapore.Wait(); + CheckCancellationOrException(); - for (var i = 0; i < imax; i++) + Interlocked.Increment(ref inFlightCount); + Task.Run(() => { - while (queue.Count < 2) + try { + var r = reduce(a, b, ct); CheckCancellationOrException(); - Task.Delay(100).Wait(); + lock (queue) queue.Enqueue(r); } - - T a, b; - lock (queue) + catch (Exception e) { - a = queue.Dequeue(); - b = queue.Dequeue(); - if (a == null || b == null) throw new InvalidOperationException(); + Report.Error($"{e}"); + exception = e; + throw; } - - queueSemapore.Wait(); - CheckCancellationOrException(); - - Interlocked.Increment(ref inFlightCount); - Task.Run(() => + finally { - try - { - var r = reduce(a, b, ct); - CheckCancellationOrException(); - lock (queue) queue.Enqueue(r); - } - catch (Exception e) - { - Report.Error($"{e}"); - exception = e; - throw; - } - finally - { - Interlocked.Decrement(ref inFlightCount); - queueSemapore.Release(); - } - }); - } + Interlocked.Decrement(ref inFlightCount); + queueSemapore.Release(); + } + }); + } - while (inFlightCount > 0) { CheckCancellationOrException(); Task.Delay(100).Wait(); } - if (queue.Count != 1) { CheckCancellationOrException(); throw new InvalidOperationException(); } - var result = queue.Dequeue(); - - sw.Stop(); - onFinish?.Invoke(sw.Elapsed); + while (inFlightCount > 0) { CheckCancellationOrException(); Task.Delay(100).Wait(); } + if (queue.Count != 1) { CheckCancellationOrException(); throw new InvalidOperationException(); } + var result = queue.Dequeue(); + + sw.Stop(); + onFinish?.Invoke(sw.Elapsed); - return result; + return result; - void CheckCancellationOrException() - { - ct.ThrowIfCancellationRequested(); - if (exception != null) throw new Exception("MapReduceParallel failed. See inner exception.", exception); - } + void CheckCancellationOrException() + { + ct.ThrowIfCancellationRequested(); + if (exception != null) throw new Exception("MapReduceParallel failed. See inner exception.", exception); } } } diff --git a/src/Aardvark.Geometry.PointSet/Utils/FileHelpers.cs b/src/Aardvark.Geometry.PointSet/Utils/FileHelpers.cs index fb1826df..e6ffaf4d 100644 --- a/src/Aardvark.Geometry.PointSet/Utils/FileHelpers.cs +++ b/src/Aardvark.Geometry.PointSet/Utils/FileHelpers.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -17,60 +17,59 @@ You should have received a copy of the GNU Affero General Public License using System.Threading; using System.Threading.Tasks; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static class FileHelpers { /// + /// Computes MD5 hash of file content. + /// If fast is set to true, then only the first 1 MiB of data is used for hash. /// - public static class FileHelpers + public static string ComputeMd5Hash(string filename, bool fast) { - /// - /// Computes MD5 hash of file content. - /// If fast is set to true, then only the first 1 MiB of data is used for hash. - /// - public static string ComputeMd5Hash(string filename, bool fast) - { - var MiB = 1024 * 1024; + var MiB = 1024 * 1024; - var filesize = new FileInfo(filename).Length; + var filesize = new FileInfo(filename).Length; - if (fast) + if (fast) + { + if (filesize <= MiB) { - if (filesize <= MiB) - { - var buffer = File.ReadAllBytes(filename); - var hash = new Guid(MD5.Create().ComputeHash(buffer)).ToString(); - return hash; - } - else - { - using var fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.None); - var buffer = new byte[MiB]; - if (fs.Read(buffer, 0, MiB) != MiB) throw new InvalidOperationException(); - var hash = new Guid(MD5.Create().ComputeHash(buffer)).ToString(); - return hash; - } + var buffer = File.ReadAllBytes(filename); + var hash = new Guid(MD5.Create().ComputeHash(buffer)).ToString(); + return hash; } else { using var fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.None); - var cts = new CancellationTokenSource(); - try + var buffer = new byte[MiB]; + if (fs.Read(buffer, 0, MiB) != MiB) throw new InvalidOperationException(); + var hash = new Guid(MD5.Create().ComputeHash(buffer)).ToString(); + return hash; + } + } + else + { + using var fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.None); + var cts = new CancellationTokenSource(); + try + { + Task.Run(async () => { - Task.Run(async () => + while (!cts.IsCancellationRequested) { - while (!cts.IsCancellationRequested) - { - await Task.Delay(TimeSpan.FromSeconds(1)); - } - }, cts.Token); + await Task.Delay(TimeSpan.FromSeconds(1)); + } + }, cts.Token); - var hash = new Guid(MD5.Create().ComputeHash(fs)).ToString(); - return hash; - } - finally - { - cts.Cancel(); - } + var hash = new Guid(MD5.Create().ComputeHash(fs)).ToString(); + return hash; + } + finally + { + cts.Cancel(); } } } diff --git a/src/Aardvark.Geometry.PointSet/Utils/HashExtensions.cs b/src/Aardvark.Geometry.PointSet/Utils/HashExtensions.cs index 642f9de9..a5459e3c 100644 --- a/src/Aardvark.Geometry.PointSet/Utils/HashExtensions.cs +++ b/src/Aardvark.Geometry.PointSet/Utils/HashExtensions.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -18,421 +18,420 @@ You should have received a copy of the GNU Affero General Public License using System.Security.Cryptography; using Aardvark.Base; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public static class HashExtensions { - /// - /// - public static class HashExtensions - { - #region V[234][fdli] + #region V[234][fdli] - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V2f x) - => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); }); - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V2d x) - => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); }); - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V2l x) - => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); }); - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V2i x) - => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); }); - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V2f[] xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V2d[] xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V2l[] xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V2i[] xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); } - }); - } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V2f x) + => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); }); + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V2d x) + => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); }); + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V2l x) + => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); }); + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V2i x) + => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); }); + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V2f[] xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V2d[] xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V2l[] xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V2i[] xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); } + }); + } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V3f x) - => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); }); - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V3d x) - => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); }); - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V3l x) - => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); }); - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V3i x) - => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); }); - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V3f[] xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); bw.Write(xs[i].Z); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V3d[] xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); bw.Write(xs[i].Z); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V3l[] xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); bw.Write(xs[i].Z); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V3i[] xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); bw.Write(xs[i].Z); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); } - }); - } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V3f x) + => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); }); + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V3d x) + => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); }); + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V3l x) + => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); }); + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V3i x) + => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); }); + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V3f[] xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); bw.Write(xs[i].Z); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V3d[] xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); bw.Write(xs[i].Z); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V3l[] xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); bw.Write(xs[i].Z); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V3i[] xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); bw.Write(xs[i].Z); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); } + }); + } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V4f x) - => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); bw.Write(x.W); }); - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V4d x) - => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); bw.Write(x.W); }); - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V4l x) - => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); bw.Write(x.W); }); - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V4i x) - => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); bw.Write(x.W); }); - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V4f[] xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); bw.Write(xs[i].Z); bw.Write(xs[i].W); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V4d[] xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); bw.Write(xs[i].Z); bw.Write(xs[i].W); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V4l[] xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); bw.Write(xs[i].Z); bw.Write(xs[i].W); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this V4i[] xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); bw.Write(xs[i].Z); bw.Write(xs[i].W); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); bw.Write(x.W); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); bw.Write(x.W); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); bw.Write(x.W); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); bw.Write(x.W); } - }); - } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V4f x) + => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); bw.Write(x.W); }); + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V4d x) + => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); bw.Write(x.W); }); + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V4l x) + => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); bw.Write(x.W); }); + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V4i x) + => ComputeMd5Hash(bw => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); bw.Write(x.W); }); + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V4f[] xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); bw.Write(xs[i].Z); bw.Write(xs[i].W); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V4d[] xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); bw.Write(xs[i].Z); bw.Write(xs[i].W); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V4l[] xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); bw.Write(xs[i].Z); bw.Write(xs[i].W); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this V4i[] xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].X); bw.Write(xs[i].Y); bw.Write(xs[i].Z); bw.Write(xs[i].W); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); bw.Write(x.W); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); bw.Write(x.W); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); bw.Write(x.W); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + foreach (var x in xs) { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); bw.Write(x.W); } + }); + } - #endregion + #endregion - #region C[34][bf] + #region C[34][bf] - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this C3b x) - => ComputeMd5Hash(bw => { bw.Write(x.R); bw.Write(x.G); bw.Write(x.B); }); - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this C3f x) - => ComputeMd5Hash(bw => { bw.Write(x.R); bw.Write(x.G); bw.Write(x.B); }); - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this C4b x) - => ComputeMd5Hash(bw => { bw.Write(x.R); bw.Write(x.G); bw.Write(x.B); bw.Write(x.A); }); - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this C4f x) - => ComputeMd5Hash(bw => { bw.Write(x.R); bw.Write(x.G); bw.Write(x.B); bw.Write(x.A); }); - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this C3b[] xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].R); bw.Write(xs[i].G); bw.Write(xs[i].B); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this C3f[] xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].R); bw.Write(xs[i].G); bw.Write(xs[i].B); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this C4b[] xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].R); bw.Write(xs[i].G); bw.Write(xs[i].B); bw.Write(xs[i].A); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this C4f[] xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].R); bw.Write(xs[i].G); bw.Write(xs[i].B); bw.Write(xs[i].A); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - foreach (var x in xs) { bw.Write(x.R); bw.Write(x.G); bw.Write(x.B);} - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - foreach (var x in xs) { bw.Write(x.R); bw.Write(x.G); bw.Write(x.B); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - foreach (var x in xs) { bw.Write(x.R); bw.Write(x.G); bw.Write(x.B); bw.Write(x.A); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable xs) - { - if (xs == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - foreach (var x in xs) { bw.Write(x.R); bw.Write(x.G); bw.Write(x.B); bw.Write(x.A); } - }); - } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this C3b x) + => ComputeMd5Hash(bw => { bw.Write(x.R); bw.Write(x.G); bw.Write(x.B); }); + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this C3f x) + => ComputeMd5Hash(bw => { bw.Write(x.R); bw.Write(x.G); bw.Write(x.B); }); + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this C4b x) + => ComputeMd5Hash(bw => { bw.Write(x.R); bw.Write(x.G); bw.Write(x.B); bw.Write(x.A); }); + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this C4f x) + => ComputeMd5Hash(bw => { bw.Write(x.R); bw.Write(x.G); bw.Write(x.B); bw.Write(x.A); }); + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this C3b[] xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].R); bw.Write(xs[i].G); bw.Write(xs[i].B); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this C3f[] xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].R); bw.Write(xs[i].G); bw.Write(xs[i].B); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this C4b[] xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].R); bw.Write(xs[i].G); bw.Write(xs[i].B); bw.Write(xs[i].A); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this C4f[] xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + for (var i = 0; i < xs.Length; i++) { bw.Write(xs[i].R); bw.Write(xs[i].G); bw.Write(xs[i].B); bw.Write(xs[i].A); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + foreach (var x in xs) { bw.Write(x.R); bw.Write(x.G); bw.Write(x.B);} + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + foreach (var x in xs) { bw.Write(x.R); bw.Write(x.G); bw.Write(x.B); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + foreach (var x in xs) { bw.Write(x.R); bw.Write(x.G); bw.Write(x.B); bw.Write(x.A); } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable xs) + { + if (xs == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + foreach (var x in xs) { bw.Write(x.R); bw.Write(x.G); bw.Write(x.B); bw.Write(x.A); } + }); + } - #endregion + #endregion - #region Plane3d + #region Plane3d - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this Plane3d plane) - { - return ComputeMd5Hash(bw => { + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this Plane3d plane) + { + return ComputeMd5Hash(bw => { + bw.Write(plane.Point.X); bw.Write(plane.Point.Y); bw.Write(plane.Point.Z); + bw.Write(plane.Distance); + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this Plane3d[] planes) + { + return ComputeMd5Hash(bw => { + foreach (var plane in planes) + { bw.Write(plane.Point.X); bw.Write(plane.Point.Y); bw.Write(plane.Point.Z); bw.Write(plane.Distance); - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this Plane3d[] planes) - { - return ComputeMd5Hash(bw => { - foreach (var plane in planes) - { - bw.Write(plane.Point.X); bw.Write(plane.Point.Y); bw.Write(plane.Point.Z); - bw.Write(plane.Distance); - } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable planes) - { - return ComputeMd5Hash(bw => { - foreach (var plane in planes) - { - bw.Write(plane.Point.X); bw.Write(plane.Point.Y); bw.Write(plane.Point.Z); - bw.Write(plane.Distance); - } - }); - } + } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable planes) + { + return ComputeMd5Hash(bw => { + foreach (var plane in planes) + { + bw.Write(plane.Point.X); bw.Write(plane.Point.Y); bw.Write(plane.Point.Z); + bw.Write(plane.Distance); + } + }); + } + + #endregion - #endregion + #region Hull3d - #region Hull3d + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this Hull3d hull) + { + if (hull.PlaneArray == null) return Guid.Empty; - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this Hull3d hull) - { - if (hull.PlaneArray == null) return Guid.Empty; + return ComputeMd5Hash(bw => { + foreach (var plane in hull.PlaneArray) + { + bw.Write(plane.Point.X); bw.Write(plane.Point.Y); bw.Write(plane.Point.Z); + bw.Write(plane.Distance); + } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this Hull3d[] hulls) + { + if (hulls == null) return Guid.Empty; - return ComputeMd5Hash(bw => { + return ComputeMd5Hash(bw => { + foreach (var hull in hulls) + { foreach (var plane in hull.PlaneArray) { bw.Write(plane.Point.X); bw.Write(plane.Point.Y); bw.Write(plane.Point.Z); bw.Write(plane.Distance); } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this Hull3d[] hulls) - { - if (hulls == null) return Guid.Empty; - - return ComputeMd5Hash(bw => { - foreach (var hull in hulls) - { - foreach (var plane in hull.PlaneArray) - { - bw.Write(plane.Point.X); bw.Write(plane.Point.Y); bw.Write(plane.Point.Z); - bw.Write(plane.Distance); - } - } - }); - } - /// Computes MD5 hash of given data. - public static Guid ComputeMd5Hash(this IEnumerable hulls) - { - if (hulls == null) return Guid.Empty; + } + }); + } + /// Computes MD5 hash of given data. + public static Guid ComputeMd5Hash(this IEnumerable hulls) + { + if (hulls == null) return Guid.Empty; - return ComputeMd5Hash(bw => { - foreach (var hull in hulls) + return ComputeMd5Hash(bw => { + foreach (var hull in hulls) + { + foreach (var plane in hull.PlaneArray) { - foreach (var plane in hull.PlaneArray) - { - bw.Write(plane.Point.X); bw.Write(plane.Point.Y); bw.Write(plane.Point.Z); - bw.Write(plane.Distance); - } + bw.Write(plane.Point.X); bw.Write(plane.Point.Y); bw.Write(plane.Point.Z); + bw.Write(plane.Distance); } - }); - } + } + }); + } - #endregion - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Guid ComputeMd5Hash(Action writeDataToHash) - { - using var ms = new MemoryStream(); - using var bw = new BinaryWriter(ms); - writeDataToHash(bw); - ms.Seek(0, SeekOrigin.Begin); - return new Guid(MD5.Create().ComputeHash(ms)); - } + #endregion + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Guid ComputeMd5Hash(Action writeDataToHash) + { + using var ms = new MemoryStream(); + using var bw = new BinaryWriter(ms); + writeDataToHash(bw); + ms.Seek(0, SeekOrigin.Begin); + return new Guid(MD5.Create().ComputeHash(ms)); } } diff --git a/src/Aardvark.Geometry.PointSet/Utils/PointCloudFormatExtensions.cs b/src/Aardvark.Geometry.PointSet/Utils/PointCloudFormatExtensions.cs index 58d98b3b..c6954c34 100644 --- a/src/Aardvark.Geometry.PointSet/Utils/PointCloudFormatExtensions.cs +++ b/src/Aardvark.Geometry.PointSet/Utils/PointCloudFormatExtensions.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -13,13 +13,12 @@ You should have received a copy of the GNU Affero General Public License */ using Aardvark.Data.Points; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +public static class PointCloudFormatExtensions { /// - public static class PointCloudFormatExtensions - { - /// - public static PointSet ImportFile(this PointCloudFileFormat self, string filename, ImportConfig config) - => PointCloud.Chunks(self.ParseFile(filename, config.ParseConfig)/*.Take(4)*/, config); - } + public static PointSet ImportFile(this PointCloudFileFormat self, string filename, ImportConfig config) + => PointCloud.Chunks(self.ParseFile(filename, config.ParseConfig)/*.Take(4)*/, config); } diff --git a/src/Aardvark.Geometry.PointSet/Utils/StorageExtensions.cs b/src/Aardvark.Geometry.PointSet/Utils/StorageExtensions.cs index 874671a6..4ce04244 100644 --- a/src/Aardvark.Geometry.PointSet/Utils/StorageExtensions.cs +++ b/src/Aardvark.Geometry.PointSet/Utils/StorageExtensions.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -25,996 +25,994 @@ You should have received a copy of the GNU Affero General Public License using System.Text; using System.Text.Json.Nodes; using Uncodium.SimpleStore; -using static Aardvark.Base.IL.Constant; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +public static class Codec { - /// - public static class Codec - { - #region Generic + #region Generic - /// V3f[] -> byte[] - public static byte[] ArrayToBuffer(T[] data, int elementSizeInBytes, Action writeElement) - { - //if (data == null) return null; - var buffer = new byte[data.Length * elementSizeInBytes]; - using var ms = new MemoryStream(buffer); - using var bw = new BinaryWriter(ms); - for (var i = 0; i < data.Length; i++) writeElement(bw, data[i]); - return buffer; - } + /// V3f[] -> byte[] + public static byte[] ArrayToBuffer(T[] data, int elementSizeInBytes, Action writeElement) + { + //if (data == null) return null; + var buffer = new byte[data.Length * elementSizeInBytes]; + using var ms = new MemoryStream(buffer); + using var bw = new BinaryWriter(ms); + for (var i = 0; i < data.Length; i++) writeElement(bw, data[i]); + return buffer; + } - /// IList<V3f> -> byte[] - public static byte[] ArrayToBuffer(IList data, int elementSizeInBytes, Action writeElement) - { - //if (data == null) return null; - var buffer = new byte[data.Count * elementSizeInBytes]; - using var ms = new MemoryStream(buffer); - using var bw = new BinaryWriter(ms); - for (var i = 0; i < data.Count; i++) writeElement(bw, data[i]); - return buffer; - } + /// IList<V3f> -> byte[] + public static byte[] ArrayToBuffer(IList data, int elementSizeInBytes, Action writeElement) + { + //if (data == null) return null; + var buffer = new byte[data.Count * elementSizeInBytes]; + using var ms = new MemoryStream(buffer); + using var bw = new BinaryWriter(ms); + for (var i = 0; i < data.Count; i++) writeElement(bw, data[i]); + return buffer; + } - /// byte[] -> T[] - public static T[] BufferToArray(byte[] buffer, int elementSizeInBytes, Func readElement) - { - //if (buffer == null) return null; - var data = new T[buffer.Length / elementSizeInBytes]; - using var ms = new MemoryStream(buffer); - using var br = new BinaryReader(ms); - for (var i = 0; i < data.Length; i++) data[i] = readElement(br); - return data; - } + /// byte[] -> T[] + public static T[] BufferToArray(byte[] buffer, int elementSizeInBytes, Func readElement) + { + //if (buffer == null) return null; + var data = new T[buffer.Length / elementSizeInBytes]; + using var ms = new MemoryStream(buffer); + using var br = new BinaryReader(ms); + for (var i = 0; i < data.Length; i++) data[i] = readElement(br); + return data; + } - #endregion + #endregion - #region int[] + #region int[] - /// int[] -> byte[] - public static byte[] IntArrayToBuffer(int[] data) - => ArrayToBuffer(data, sizeof(int), (bw, x) => bw.Write(x)); + /// int[] -> byte[] + public static byte[] IntArrayToBuffer(int[] data) + => ArrayToBuffer(data, sizeof(int), (bw, x) => bw.Write(x)); - /// byte[] -> int[] - public static int[] BufferToIntArray(byte[] buffer) - => BufferToArray(buffer, sizeof(int), br => br.ReadInt32()); + /// byte[] -> int[] + public static int[] BufferToIntArray(byte[] buffer) + => BufferToArray(buffer, sizeof(int), br => br.ReadInt32()); - #endregion + #endregion - #region int16[] + #region int16[] - /// int16[] -> byte[] - public static byte[] Int16ArrayToBuffer(short[] data) - => ArrayToBuffer(data, sizeof(short), (bw, x) => bw.Write(x)); + /// int16[] -> byte[] + public static byte[] Int16ArrayToBuffer(short[] data) + => ArrayToBuffer(data, sizeof(short), (bw, x) => bw.Write(x)); - /// byte[] -> int16[] - public static short[] BufferToInt16Array(byte[] buffer) - => BufferToArray(buffer, sizeof(short), br => br.ReadInt16()); + /// byte[] -> int16[] + public static short[] BufferToInt16Array(byte[] buffer) + => BufferToArray(buffer, sizeof(short), br => br.ReadInt16()); - #endregion + #endregion - #region V2f[] + #region V2f[] - /// V2f[] -> byte[] - public static byte[] V2fArrayToBuffer(V2f[] data) - => ArrayToBuffer(data, 8, (bw, x) => { bw.Write(x.X); bw.Write(x.Y); }); + /// V2f[] -> byte[] + public static byte[] V2fArrayToBuffer(V2f[] data) + => ArrayToBuffer(data, 8, (bw, x) => { bw.Write(x.X); bw.Write(x.Y); }); - /// IList<V2f[]> -> byte[] - public static byte[] V2fArrayToBuffer(IList data) - => ArrayToBuffer(data, 8, (bw, x) => { bw.Write(x.X); bw.Write(x.Y); }); + /// IList<V2f[]> -> byte[] + public static byte[] V2fArrayToBuffer(IList data) + => ArrayToBuffer(data, 8, (bw, x) => { bw.Write(x.X); bw.Write(x.Y); }); - /// byte[] -> V2f[] - public static V2f[] BufferToV2fArray(byte[] buffer) - => BufferToArray(buffer, 8, br => new V2f(br.ReadSingle(), br.ReadSingle())); + /// byte[] -> V2f[] + public static V2f[] BufferToV2fArray(byte[] buffer) + => BufferToArray(buffer, 8, br => new V2f(br.ReadSingle(), br.ReadSingle())); - #endregion + #endregion - #region V2d[] + #region V2d[] - /// V2d[] -> byte[] - public static byte[] V2dArrayToBuffer(V2d[] data) - => ArrayToBuffer(data, 16, (bw, x) => { bw.Write(x.X); bw.Write(x.Y); }); + /// V2d[] -> byte[] + public static byte[] V2dArrayToBuffer(V2d[] data) + => ArrayToBuffer(data, 16, (bw, x) => { bw.Write(x.X); bw.Write(x.Y); }); - /// IList<V2d[]> -> byte[] - public static byte[] V2dArrayToBuffer(IList data) - => ArrayToBuffer(data, 16, (bw, x) => { bw.Write(x.X); bw.Write(x.Y); }); + /// IList<V2d[]> -> byte[] + public static byte[] V2dArrayToBuffer(IList data) + => ArrayToBuffer(data, 16, (bw, x) => { bw.Write(x.X); bw.Write(x.Y); }); - /// byte[] -> V2d[] - public static V2d[] BufferToV2dArray(byte[] buffer) - => BufferToArray(buffer, 16, br => new V2d(br.ReadDouble(), br.ReadDouble())); + /// byte[] -> V2d[] + public static V2d[] BufferToV2dArray(byte[] buffer) + => BufferToArray(buffer, 16, br => new V2d(br.ReadDouble(), br.ReadDouble())); - #endregion + #endregion - #region V3f[] + #region V3f[] - /// V3f[] -> byte[] - public static byte[] V3fArrayToBuffer(V3f[] data) - => ArrayToBuffer(data, 12, (bw, x) => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); }); + /// V3f[] -> byte[] + public static byte[] V3fArrayToBuffer(V3f[] data) + => ArrayToBuffer(data, 12, (bw, x) => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); }); - /// IList<V3f[]> -> byte[] - public static byte[] V3fArrayToBuffer(IList data) - => ArrayToBuffer(data, 12, (bw, x) => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); }); + /// IList<V3f[]> -> byte[] + public static byte[] V3fArrayToBuffer(IList data) + => ArrayToBuffer(data, 12, (bw, x) => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); }); - /// byte[] -> V3f[] - public static V3f[] BufferToV3fArray(byte[] buffer) - => BufferToArray(buffer, 12, br => new V3f(br.ReadSingle(), br.ReadSingle(), br.ReadSingle())); + /// byte[] -> V3f[] + public static V3f[] BufferToV3fArray(byte[] buffer) + => BufferToArray(buffer, 12, br => new V3f(br.ReadSingle(), br.ReadSingle(), br.ReadSingle())); - #endregion + #endregion - #region V3d[] + #region V3d[] - /// V3d[] -> byte[] - public static byte[] V3dArrayToBuffer(V3d[] data) - => ArrayToBuffer(data, 24, (bw, x) => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); }); - - /// byte[] -> V3d[] - public static V3d[] BufferToV3dArray(byte[] buffer) - => BufferToArray(buffer, 24, br => new V3d(br.ReadDouble(), br.ReadDouble(), br.ReadDouble())); + /// V3d[] -> byte[] + public static byte[] V3dArrayToBuffer(V3d[] data) + => ArrayToBuffer(data, 24, (bw, x) => { bw.Write(x.X); bw.Write(x.Y); bw.Write(x.Z); }); + + /// byte[] -> V3d[] + public static V3d[] BufferToV3dArray(byte[] buffer) + => BufferToArray(buffer, 24, br => new V3d(br.ReadDouble(), br.ReadDouble(), br.ReadDouble())); - #endregion + #endregion - #region C4b[] + #region C4b[] - /// C4b[] -> byte[] - public static byte[] C4bArrayToBuffer(C4b[] data) + /// C4b[] -> byte[] + public static byte[] C4bArrayToBuffer(C4b[] data) + { + //if (data == null) return null; + var buffer = new byte[data.Length * 4]; + using var ms = new MemoryStream(buffer); + + for (var i = 0; i < data.Length; i++) { - //if (data == null) return null; - var buffer = new byte[data.Length * 4]; - using var ms = new MemoryStream(buffer); - - for (var i = 0; i < data.Length; i++) - { - ms.WriteByte(data[i].R); ms.WriteByte(data[i].G); ms.WriteByte(data[i].B); ms.WriteByte(data[i].A); - } - - return buffer; + ms.WriteByte(data[i].R); ms.WriteByte(data[i].G); ms.WriteByte(data[i].B); ms.WriteByte(data[i].A); } + + return buffer; + } - /// byte[] -> C4b[] - public static C4b[] BufferToC4bArray(byte[] buffer) + /// byte[] -> C4b[] + public static C4b[] BufferToC4bArray(byte[] buffer) + { + //if (buffer == null) return null; + var data = new C4b[buffer.Length / 4]; + for (int i = 0, j = 0; i < data.Length; i++) { - //if (buffer == null) return null; - var data = new C4b[buffer.Length / 4]; - for (int i = 0, j = 0; i < data.Length; i++) - { - data[i] = new C4b(buffer[j++], buffer[j++], buffer[j++], buffer[j++]); - } - return data; + data[i] = new C4b(buffer[j++], buffer[j++], buffer[j++], buffer[j++]); } + return data; + } - #endregion - - #region PointRkdTreeDData + #endregion - /// PointRkdTreeDData -> byte[] - public static byte[] PointRkdTreeDDataToBuffer(PointRkdTreeDData data) - { - //if (data == null) return null; - var ms = new MemoryStream(); - using var coder = new BinaryWritingCoder(ms); - object x = data; coder.Code(ref x); - return ms.ToArray(); - } + #region PointRkdTreeDData - /// byte[] -> PointRkdTreeDData - public static PointRkdTreeDData BufferToPointRkdTreeDData(byte[] buffer) - { - //if (buffer == null) return null; - using var ms = new MemoryStream(buffer); - using var coder = new BinaryReadingCoder(ms); - object o = null!; - coder.Code(ref o); - return (PointRkdTreeDData)o; - } - - #endregion + /// PointRkdTreeDData -> byte[] + public static byte[] PointRkdTreeDDataToBuffer(PointRkdTreeDData data) + { + //if (data == null) return null; + var ms = new MemoryStream(); + using var coder = new BinaryWritingCoder(ms); + object x = data; coder.Code(ref x); + return ms.ToArray(); + } - #region PointRkdTreeFData + /// byte[] -> PointRkdTreeDData + public static PointRkdTreeDData BufferToPointRkdTreeDData(byte[] buffer) + { + //if (buffer == null) return null; + using var ms = new MemoryStream(buffer); + using var coder = new BinaryReadingCoder(ms); + object o = null!; + coder.Code(ref o); + return (PointRkdTreeDData)o; + } - /// PointRkdTreeDData -> byte[] - public static byte[] PointRkdTreeFDataToBuffer(PointRkdTreeFData data) - { - //if (data == null) return null; - var ms = new MemoryStream(); - using var coder = new BinaryWritingCoder(ms); - object x = data; coder.Code(ref x); - return ms.ToArray(); - } + #endregion - /// byte[] -> PointRkdTreeDData - public static PointRkdTreeFData BufferToPointRkdTreeFData(byte[] buffer) - { - //if (buffer == null) return null; - using var ms = new MemoryStream(buffer); - using var coder = new BinaryReadingCoder(ms); - object o = null!; - coder.Code(ref o); - return (PointRkdTreeFData)o; - } + #region PointRkdTreeFData - #endregion + /// PointRkdTreeDData -> byte[] + public static byte[] PointRkdTreeFDataToBuffer(PointRkdTreeFData data) + { + //if (data == null) return null; + var ms = new MemoryStream(); + using var coder = new BinaryWritingCoder(ms); + object x = data; coder.Code(ref x); + return ms.ToArray(); } - /// - /// - public static class StorageExtensions + /// byte[] -> PointRkdTreeDData + public static PointRkdTreeFData BufferToPointRkdTreeFData(byte[] buffer) { - #region Generic + //if (buffer == null) return null; + using var ms = new MemoryStream(buffer); + using var coder = new BinaryReadingCoder(ms); + object o = null!; + coder.Code(ref o); + return (PointRkdTreeFData)o; + } - /// - public static bool TryGetFromCache(this Storage storage, string key, [NotNullWhen(true)] out T? result) + #endregion +} + +/// +/// +public static class StorageExtensions +{ + #region Generic + + /// + public static bool TryGetFromCache(this Storage storage, string key, [NotNullWhen(true)] out T? result) + { + if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) { - if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) - { - result = (T)o; - return true; - } - else - { - result = default; - return false; - } + result = (T)o; + return true; + } + else + { + result = default; + return false; } + } - #endregion + #endregion - #region Stores + #region Stores - /// - public static ImportConfig WithInMemoryStore(this ImportConfig self) - => self.WithStorage(new SimpleMemoryStore().ToPointCloudStore(cache: default)); + /// + public static ImportConfig WithInMemoryStore(this ImportConfig self) + => self.WithStorage(new SimpleMemoryStore().ToPointCloudStore(cache: default)); - /// - /// Wraps Uncodium.ISimpleStore into Storage. - /// - public static Storage ToPointCloudStore(this ISimpleStore x, LruDictionary? cache) { - void add(string name, object value, Func create) => x.Add(name, create()); - return new Storage(add, x.Get, x.GetSlice, x.Remove, x.Dispose, x.Flush, cache); - } + /// + /// Wraps Uncodium.ISimpleStore into Storage. + /// + public static Storage ToPointCloudStore(this ISimpleStore x, LruDictionary? cache) { + void add(string name, object value, Func create) => x.Add(name, create()); + return new Storage(add, x.Get, x.GetSlice, x.Remove, x.Dispose, x.Flush, cache); + } - /// - /// Wraps Uncodium.ISimpleStore into Storage with default 1GB cache. - /// - public static Storage ToPointCloudStore(this ISimpleStore x) - { - void add(string name, object value, Func create) => x.Add(name, create()); - return new Storage(add, x.Get, x.GetSlice, x.Remove, x.Dispose, x.Flush, new LruDictionary(1024 * 1024 * 1024)); - } + /// + /// Wraps Uncodium.ISimpleStore into Storage with default 1GB cache. + /// + public static Storage ToPointCloudStore(this ISimpleStore x) + { + void add(string name, object value, Func create) => x.Add(name, create()); + return new Storage(add, x.Get, x.GetSlice, x.Remove, x.Dispose, x.Flush, new LruDictionary(1024 * 1024 * 1024)); + } - #endregion + #endregion - #region Exists + #region Exists - /// - /// Returns if given key exists in store. - /// - public static bool Exists(this Storage storage, Guid key) => Exists(storage, key.ToString()); + /// + /// Returns if given key exists in store. + /// + public static bool Exists(this Storage storage, Guid key) => Exists(storage, key.ToString()); - /// - /// Returns if given key exists in store. - /// - public static bool Exists(this Storage storage, string key) => storage.f_get(key) != null; + /// + /// Returns if given key exists in store. + /// + public static bool Exists(this Storage storage, string key) => storage.f_get(key) != null; - #endregion + #endregion - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Add(this Storage storage, Guid key, Array data) - => Add(storage, key.ToString(), data); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Add(this Storage storage, Guid key, Array data) + => Add(storage, key.ToString(), data); - public static void Add(this Storage storage, string key, Array data) - { - switch (data) - { - case null : throw new ArgumentNullException(nameof(data)); - case byte[] xs: storage.f_add(key, xs, () => xs); break; - case int[] xs: storage.f_add(key, xs, () => Codec.IntArrayToBuffer(xs)); break; - case short[] xs: storage.f_add(key, xs, () => Codec.Int16ArrayToBuffer(xs)); break; - case V2f[] xs: storage.f_add(key, xs, () => Codec.V2fArrayToBuffer(xs)); break; - case V2d[] xs: storage.f_add(key, xs, () => Codec.V2dArrayToBuffer(xs)); break; - case V3f[] xs: storage.f_add(key, xs, () => Codec.V3fArrayToBuffer(xs)); break; - case V3d[] xs: storage.f_add(key, xs, () => Codec.V3dArrayToBuffer(xs)); break; - case C4b[] xs: storage.f_add(key, xs, () => Codec.C4bArrayToBuffer(xs)); break; - default: throw new Exception($"Type {data.GetType()} not supported."); - } + public static void Add(this Storage storage, string key, Array data) + { + switch (data) + { + case null : throw new ArgumentNullException(nameof(data)); + case byte[] xs: storage.f_add(key, xs, () => xs); break; + case int[] xs: storage.f_add(key, xs, () => Codec.IntArrayToBuffer(xs)); break; + case short[] xs: storage.f_add(key, xs, () => Codec.Int16ArrayToBuffer(xs)); break; + case V2f[] xs: storage.f_add(key, xs, () => Codec.V2fArrayToBuffer(xs)); break; + case V2d[] xs: storage.f_add(key, xs, () => Codec.V2dArrayToBuffer(xs)); break; + case V3f[] xs: storage.f_add(key, xs, () => Codec.V3fArrayToBuffer(xs)); break; + case V3d[] xs: storage.f_add(key, xs, () => Codec.V3dArrayToBuffer(xs)); break; + case C4b[] xs: storage.f_add(key, xs, () => Codec.C4bArrayToBuffer(xs)); break; + default: throw new Exception($"Type {data.GetType()} not supported."); } + } - #region byte[] + #region byte[] - /// - public static void Add(this Storage storage, Guid key, byte[] data) => Add(storage, key.ToString(), data); + /// + public static void Add(this Storage storage, Guid key, byte[] data) => Add(storage, key.ToString(), data); - /// - public static void Add(this Storage storage, string key, byte[] data) => storage.f_add(key, data, () => data); + /// + public static void Add(this Storage storage, string key, byte[] data) => storage.f_add(key, data, () => data); - /// - public static byte[]? GetByteArray(this Storage storage, string key) => storage.f_get(key); + /// + public static byte[]? GetByteArray(this Storage storage, string key) => storage.f_get(key); - /// - public static byte[]? GetByteArray(this Storage storage, Guid key) => storage.f_get(key.ToString()); + /// + public static byte[]? GetByteArray(this Storage storage, Guid key) => storage.f_get(key.ToString()); - /// - public static (bool, byte[]?) TryGetByteArray(this Storage storage, string key) - { - var buffer = storage.f_get(key); - return (buffer != null, buffer); - } + /// + public static (bool, byte[]?) TryGetByteArray(this Storage storage, string key) + { + var buffer = storage.f_get(key); + return (buffer != null, buffer); + } - public static bool TryGetByteArrayFromCache(this Storage storage, string key, [NotNullWhen(true)] out byte[]? result) => TryGetFromCache(storage, key, out result); + public static bool TryGetByteArrayFromCache(this Storage storage, string key, [NotNullWhen(true)] out byte[]? result) => TryGetFromCache(storage, key, out result); - /// - /// Return ungzipped buffer, or original buffer if it is not gzipped. - /// - public static byte[] UnGZip(byte[] buffer, out bool bufferWasGzipped) + /// + /// Return ungzipped buffer, or original buffer if it is not gzipped. + /// + public static byte[] UnGZip(byte[] buffer, out bool bufferWasGzipped) + { + // gzipped buffer? + if (buffer.Length > 10 && buffer[0] == 0x1F && buffer[1] == 0x8B) { - // gzipped buffer? - if (buffer.Length > 10 && buffer[0] == 0x1F && buffer[1] == 0x8B) + // starts with magic gzip bytes: 0x1F 0x8B + // see https://datatracker.ietf.org/doc/html/rfc1952#page-5 + try { - // starts with magic gzip bytes: 0x1F 0x8B - // see https://datatracker.ietf.org/doc/html/rfc1952#page-5 - try - { - using var msgz = new MemoryStream(buffer); - using var stream = new GZipStream(msgz, CompressionMode.Decompress); - - const int size = 4096; - byte[] tmp = new byte[size]; - using var ms = new MemoryStream(); - - int count = 0; - do - { - count = stream.Read(tmp, 0, size); - if (count > 0) ms.Write(tmp, 0, count); - } - while (count > 0); - stream.Close(); - bufferWasGzipped = true; - return ms.ToArray(); - } - catch + using var msgz = new MemoryStream(buffer); + using var stream = new GZipStream(msgz, CompressionMode.Decompress); + + const int size = 4096; + byte[] tmp = new byte[size]; + using var ms = new MemoryStream(); + + int count = 0; + do { - // although buffer starts with gzip magic bytes, it is not gzip - bufferWasGzipped = false; - return buffer; // return original buffer + count = stream.Read(tmp, 0, size); + if (count > 0) ms.Write(tmp, 0, count); } + while (count > 0); + stream.Close(); + bufferWasGzipped = true; + return ms.ToArray(); } - else + catch { - // not gzipped + // although buffer starts with gzip magic bytes, it is not gzip bufferWasGzipped = false; return buffer; // return original buffer } } + else + { + // not gzipped + bufferWasGzipped = false; + return buffer; // return original buffer + } + } - /// - /// Return ungzipped buffer, or original buffer if it is not gzipped. - /// - public static byte[] UnGZip(byte[] buffer) => UnGZip(buffer, out _); + /// + /// Return ungzipped buffer, or original buffer if it is not gzipped. + /// + public static byte[] UnGZip(byte[] buffer) => UnGZip(buffer, out _); - #endregion + #endregion - #region string/json + #region string/json - /// - public static void Add(this Storage storage, Guid key, string s) - => Add(storage, key, Encoding.UTF8.GetBytes(s)); + /// + public static void Add(this Storage storage, Guid key, string s) + => Add(storage, key, Encoding.UTF8.GetBytes(s)); - /// - public static void Add(this Storage storage, string key, string s) - => Add(storage, key, Encoding.UTF8.GetBytes(s)); + /// + public static void Add(this Storage storage, string key, string s) + => Add(storage, key, Encoding.UTF8.GetBytes(s)); - ///// - //public static void Add(this Storage storage, Guid key, JObject json) - // => Add(storage, key, Encoding.UTF8.GetBytes(json.ToString(Formatting.Indented))); + ///// + //public static void Add(this Storage storage, Guid key, JObject json) + // => Add(storage, key, Encoding.UTF8.GetBytes(json.ToString(Formatting.Indented))); - ///// - //public static void Add(this Storage storage, string key, JObject json) - // => Add(storage, key, Encoding.UTF8.GetBytes(json.ToString(Formatting.Indented))); + ///// + //public static void Add(this Storage storage, string key, JObject json) + // => Add(storage, key, Encoding.UTF8.GetBytes(json.ToString(Formatting.Indented))); - #endregion + #endregion - #region V3f[] + #region V3f[] - /// - public static void Add(this Storage storage, Guid key, V3f[] data) => Add(storage, key.ToString(), data); - - /// - public static void Add(this Storage storage, Guid key, IList data) => Add(storage, key.ToString(), data); + /// + public static void Add(this Storage storage, Guid key, V3f[] data) => Add(storage, key.ToString(), data); + + /// + public static void Add(this Storage storage, Guid key, IList data) => Add(storage, key.ToString(), data); - /// - public static void Add(this Storage storage, string key, V3f[] data) - => storage.f_add(key, data, () => Codec.V3fArrayToBuffer(data)); + /// + public static void Add(this Storage storage, string key, V3f[] data) + => storage.f_add(key, data, () => Codec.V3fArrayToBuffer(data)); + + /// + public static void Add(this Storage storage, string key, IList data) + => storage.f_add(key, data, () => Codec.V3fArrayToBuffer(data)); + + /// + public static V3f[]? GetV3fArray(this Storage storage, string key) + { + if (storage.HasCache && storage.Cache.TryGetValue(key, out var o)) return (V3f[])o; - /// - public static void Add(this Storage storage, string key, IList data) - => storage.f_add(key, data, () => Codec.V3fArrayToBuffer(data)); - - /// - public static V3f[]? GetV3fArray(this Storage storage, string key) - { - if (storage.HasCache && storage.Cache.TryGetValue(key, out var o)) return (V3f[])o; - - var buffer = storage.f_get(key); - if (buffer == null) return null; - var data = Codec.BufferToV3fArray(buffer); - - if (storage.HasCache) - storage.Cache.Add(key, data, buffer.Length, onRemove: default); - - return data; - } + var buffer = storage.f_get(key); + if (buffer == null) return null; + var data = Codec.BufferToV3fArray(buffer); + + if (storage.HasCache) + storage.Cache.Add(key, data, buffer.Length, onRemove: default); - /// - public static bool TryGetV3fArrayFromCache(this Storage storage, string key, [NotNullWhen(true)] out V3f[]? result) => TryGetFromCache(storage, key, out result); + return data; + } - #endregion + /// + public static bool TryGetV3fArrayFromCache(this Storage storage, string key, [NotNullWhen(true)] out V3f[]? result) => TryGetFromCache(storage, key, out result); - #region int[] + #endregion - /// - public static void Add(this Storage storage, Guid key, int[] data) => Add(storage, key.ToString(), data); + #region int[] - /// - public static void Add(this Storage storage, string key, int[] data) - => storage.f_add(key, data, () => Codec.IntArrayToBuffer(data)); + /// + public static void Add(this Storage storage, Guid key, int[] data) => Add(storage, key.ToString(), data); - /// - public static int[]? GetIntArray(this Storage storage, Guid key) - => GetIntArray(storage, key.ToString()); + /// + public static void Add(this Storage storage, string key, int[] data) + => storage.f_add(key, data, () => Codec.IntArrayToBuffer(data)); - /// - public static int[]? GetIntArray(this Storage storage, string key) - { - if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) return (int[])o; + /// + public static int[]? GetIntArray(this Storage storage, Guid key) + => GetIntArray(storage, key.ToString()); - var buffer = storage.f_get(key); - if (buffer == null) return null; - var data = Codec.BufferToIntArray(buffer); + /// + public static int[]? GetIntArray(this Storage storage, string key) + { + if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) return (int[])o; - if (storage.HasCache) - storage.Cache.Add(key, data, buffer.Length, onRemove: default); + var buffer = storage.f_get(key); + if (buffer == null) return null; + var data = Codec.BufferToIntArray(buffer); - return data; - } + if (storage.HasCache) + storage.Cache.Add(key, data, buffer.Length, onRemove: default); - /// - public static (bool, int[]?) TryGetIntArray(this Storage storage, string key) + return data; + } + + /// + public static (bool, int[]?) TryGetIntArray(this Storage storage, string key) + { + if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) { - if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) - { - return (true, (int[])o); - } - else - { - return (false, default); - } + return (true, (int[])o); + } + else + { + return (false, default); } + } - public static bool TryGetIntArrayFromCache(this Storage storage, string key, [NotNullWhen(true)] out int[]? result) => TryGetFromCache(storage, key, out result); + public static bool TryGetIntArrayFromCache(this Storage storage, string key, [NotNullWhen(true)] out int[]? result) => TryGetFromCache(storage, key, out result); - #endregion + #endregion - #region int16[] + #region int16[] - /// - public static void Add(this Storage storage, Guid key, short[] data) => Add(storage, key.ToString(), data); + /// + public static void Add(this Storage storage, Guid key, short[] data) => Add(storage, key.ToString(), data); - /// - public static void Add(this Storage storage, string key, short[] data) - => storage.f_add(key, data, () => Codec.Int16ArrayToBuffer(data)); + /// + public static void Add(this Storage storage, string key, short[] data) + => storage.f_add(key, data, () => Codec.Int16ArrayToBuffer(data)); - /// - public static short[]? GetInt16Array(this Storage storage, Guid key) - => GetInt16Array(storage, key.ToString()); + /// + public static short[]? GetInt16Array(this Storage storage, Guid key) + => GetInt16Array(storage, key.ToString()); - /// - public static short[]? GetInt16Array(this Storage storage, string key) - { - if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) return (short[])o; + /// + public static short[]? GetInt16Array(this Storage storage, string key) + { + if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) return (short[])o; - var buffer = storage.f_get(key); - if (buffer == null) return null; - var data = Codec.BufferToInt16Array(buffer); + var buffer = storage.f_get(key); + if (buffer == null) return null; + var data = Codec.BufferToInt16Array(buffer); - if (storage.HasCache) - storage.Cache.Add(key, data, buffer.Length, onRemove: default); + if (storage.HasCache) + storage.Cache.Add(key, data, buffer.Length, onRemove: default); - return data; - } + return data; + } - /// - public static (bool, short[]?) TryGetInt16Array(this Storage storage, string key) + /// + public static (bool, short[]?) TryGetInt16Array(this Storage storage, string key) + { + if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) { - if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) - { - return (true, (short[])o); - } - else - { - return (false, default); - } + return (true, (short[])o); + } + else + { + return (false, default); } + } - public static bool TryGetInt16ArrayFromCache(this Storage storage, string key, [NotNullWhen(true)] out short[]? result) => TryGetFromCache(storage, key, out result); + public static bool TryGetInt16ArrayFromCache(this Storage storage, string key, [NotNullWhen(true)] out short[]? result) => TryGetFromCache(storage, key, out result); - #endregion + #endregion - #region C4b[] + #region C4b[] - /// - public static void Add(this Storage storage, Guid key, C4b[] data) => Add(storage, key.ToString(), data); + /// + public static void Add(this Storage storage, Guid key, C4b[] data) => Add(storage, key.ToString(), data); - /// - public static void Add(this Storage storage, string key, C4b[] data) - => storage.f_add(key, data, () => Codec.C4bArrayToBuffer(data)); + /// + public static void Add(this Storage storage, string key, C4b[] data) + => storage.f_add(key, data, () => Codec.C4bArrayToBuffer(data)); - /// - public static C4b[]? GetC4bArray(this Storage storage, string key) - { - if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) return (C4b[])o; + /// + public static C4b[]? GetC4bArray(this Storage storage, string key) + { + if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) return (C4b[])o; - var buffer = storage.f_get(key); - if (buffer == null) return null; - var data = Codec.BufferToC4bArray(buffer); + var buffer = storage.f_get(key); + if (buffer == null) return null; + var data = Codec.BufferToC4bArray(buffer); - if (storage.HasCache) - storage.Cache.Add(key, data, buffer.Length, onRemove: default); + if (storage.HasCache) + storage.Cache.Add(key, data, buffer.Length, onRemove: default); - return data; - } + return data; + } - /// - public static (bool, C4b[]?) TryGetC4bArray(this Storage storage, string key) + /// + public static (bool, C4b[]?) TryGetC4bArray(this Storage storage, string key) + { + if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) { - if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) - { - return (true, (C4b[])o); - } - else - { - return (false, default); - } + return (true, (C4b[])o); } + else + { + return (false, default); + } + } - public static bool TryGetC4bArrayFromCache(this Storage storage, string key, [NotNullWhen(true)] out C4b[]? result) => TryGetFromCache(storage, key, out result); + public static bool TryGetC4bArrayFromCache(this Storage storage, string key, [NotNullWhen(true)] out C4b[]? result) => TryGetFromCache(storage, key, out result); - #endregion + #endregion - #region PointRkdTreeDData + #region PointRkdTreeDData - ///// - //public static void Add(this Storage storage, Guid key, PointRkdTreeDData data) => Add(storage, key.ToString(), data); + ///// + //public static void Add(this Storage storage, Guid key, PointRkdTreeDData data) => Add(storage, key.ToString(), data); - ///// - //public static void Add(this Storage storage, string key, PointRkdTreeDData data) - // => storage.f_add(key, data, () => Codec.PointRkdTreeDDataToBuffer(data)); + ///// + //public static void Add(this Storage storage, string key, PointRkdTreeDData data) + // => storage.f_add(key, data, () => Codec.PointRkdTreeDDataToBuffer(data)); + + /// + public static PointRkdTreeDData? GetPointRkdTreeDData(this Storage storage, string key) + { + if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) return (PointRkdTreeDData)o; + + var buffer = storage.f_get(key); + if (buffer == null) return null; + var data = Codec.BufferToPointRkdTreeDData(buffer); + if (storage.HasCache) storage.Cache.Add(key, data, buffer.Length, onRemove: default); + return data; + } - /// - public static PointRkdTreeDData? GetPointRkdTreeDData(this Storage storage, string key) + /// + public static (bool, PointRkdTreeDData?) TryGetPointRkdTreeDData(this Storage storage, string key) + { + if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) { - if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) return (PointRkdTreeDData)o; - - var buffer = storage.f_get(key); - if (buffer == null) return null; - var data = Codec.BufferToPointRkdTreeDData(buffer); - if (storage.HasCache) storage.Cache.Add(key, data, buffer.Length, onRemove: default); - return data; + return (true, (PointRkdTreeDData)o); } - - /// - public static (bool, PointRkdTreeDData?) TryGetPointRkdTreeDData(this Storage storage, string key) + else { - if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) - { - return (true, (PointRkdTreeDData)o); - } - else - { - return (false, default); - } + return (false, default); } + } - /// - /// - public static PointRkdTreeD GetKdTree(this Storage storage, string key, V3f[] positions) - => new( - 3, positions.Length, positions, - (xs, i) => xs[(int)i], (v, i) => (float)v[i], - (a, b) => Vec.Distance(a, b), (i, a, b) => b - a, - (a, b, c) => Vec.DistanceToLine(a, b, c), (t,a,b) => Fun.Lerp((float)t,a,b), 1e-9, - storage.GetPointRkdTreeDData(key) - ); + /// + /// + public static PointRkdTreeD GetKdTree(this Storage storage, string key, V3f[] positions) + => new( + 3, positions.Length, positions, + (xs, i) => xs[(int)i], (v, i) => (float)v[i], + (a, b) => Vec.Distance(a, b), (i, a, b) => b - a, + (a, b, c) => Vec.DistanceToLine(a, b, c), (t,a,b) => Fun.Lerp((float)t,a,b), 1e-9, + storage.GetPointRkdTreeDData(key) + ); - public static bool TryGetPointRkdTreeDDataFromCache(this Storage storage, string key, [NotNullWhen(true)] out C4b[]? result) => TryGetFromCache(storage, key, out result); + public static bool TryGetPointRkdTreeDDataFromCache(this Storage storage, string key, [NotNullWhen(true)] out C4b[]? result) => TryGetFromCache(storage, key, out result); - #endregion + #endregion - #region PointRkdTreeFData + #region PointRkdTreeFData - /// - public static void Add(this Storage storage, Guid key, PointRkdTreeFData data) => Add(storage, key.ToString(), data); + /// + public static void Add(this Storage storage, Guid key, PointRkdTreeFData data) => Add(storage, key.ToString(), data); - /// - public static void Add(this Storage storage, string key, PointRkdTreeFData data) - => storage.f_add(key, data, () => Codec.PointRkdTreeFDataToBuffer(data)); + /// + public static void Add(this Storage storage, string key, PointRkdTreeFData data) + => storage.f_add(key, data, () => Codec.PointRkdTreeFDataToBuffer(data)); - /// - public static PointRkdTreeFData? GetPointRkdTreeFData(this Storage storage, string key) - { - if (storage == null) return null; - if (storage.HasCache == true && storage.Cache.TryGetValue(key, out object o)) return (PointRkdTreeFData)o; + /// + public static PointRkdTreeFData? GetPointRkdTreeFData(this Storage storage, string key) + { + if (storage == null) return null; + if (storage.HasCache == true && storage.Cache.TryGetValue(key, out object o)) return (PointRkdTreeFData)o; + + var buffer = storage.f_get(key); + if (buffer == null) return null; + var data = Codec.BufferToPointRkdTreeFData(buffer); + if (storage.HasCache) storage.Cache.Add(key, data, buffer.Length, onRemove: default); + return data; + } - var buffer = storage.f_get(key); - if (buffer == null) return null; - var data = Codec.BufferToPointRkdTreeFData(buffer); - if (storage.HasCache) storage.Cache.Add(key, data, buffer.Length, onRemove: default); - return data; - } + /// + public static PointRkdTreeFData? GetPointRkdTreeFDataFromD(this Storage storage, string key) + { + if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) return (PointRkdTreeFData)o; + + var buffer = storage.f_get(key); + if (buffer == null) return default; + var data0 = Codec.BufferToPointRkdTreeDData(buffer); + var data = new PointRkdTreeFData + { + AxisArray = data0.AxisArray, + PermArray = data0.PermArray, + RadiusArray = data0.RadiusArray.Map(x => (float)x) + }; + if (storage.HasCache) storage.Cache.Add(key, data, buffer.Length, onRemove: default); + return data; + } - /// - public static PointRkdTreeFData? GetPointRkdTreeFDataFromD(this Storage storage, string key) + /// + public static (bool, PointRkdTreeFData?) TryGetPointRkdTreeFData(this Storage storage, string key) + { + if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) { - if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) return (PointRkdTreeFData)o; - - var buffer = storage.f_get(key); - if (buffer == null) return default; - var data0 = Codec.BufferToPointRkdTreeDData(buffer); - var data = new PointRkdTreeFData - { - AxisArray = data0.AxisArray, - PermArray = data0.PermArray, - RadiusArray = data0.RadiusArray.Map(x => (float)x) - }; - if (storage.HasCache) storage.Cache.Add(key, data, buffer.Length, onRemove: default); - return data; + return (true, (PointRkdTreeFData)o); } - - /// - public static (bool, PointRkdTreeFData?) TryGetPointRkdTreeFData(this Storage storage, string key) + else { - if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) - { - return (true, (PointRkdTreeFData)o); - } - else - { - return (false, default); - } + return (false, default); } + } - public static bool TryGetPointRkdTreeFDataFromCache(this Storage storage, string key, [NotNullWhen(true)] out C4b[]? result) => TryGetFromCache(storage, key, out result); + public static bool TryGetPointRkdTreeFDataFromCache(this Storage storage, string key, [NotNullWhen(true)] out C4b[]? result) => TryGetFromCache(storage, key, out result); - /// - public static (bool, PointRkdTreeFData?) TryGetPointRkdTreeFDataFromD(this Storage storage, string key) + /// + public static (bool, PointRkdTreeFData?) TryGetPointRkdTreeFDataFromD(this Storage storage, string key) + { + if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) { - if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) - { - return (true, (PointRkdTreeFData)o); - } - else - { - return (false, default); - } + return (true, (PointRkdTreeFData)o); + } + else + { + return (false, default); } + } - public static bool TryGetPointRkdTreeFDataFromDFromCache(this Storage storage, string key, [NotNullWhen(true)] out C4b[]? result) => TryGetFromCache(storage, key, out result); + public static bool TryGetPointRkdTreeFDataFromDFromCache(this Storage storage, string key, [NotNullWhen(true)] out C4b[]? result) => TryGetFromCache(storage, key, out result); - #endregion + #endregion - #region PointSet + #region PointSet - public static byte[] Encode(this PointSet self) - { - var json = self.ToJson().ToString(); - var buffer = Encoding.UTF8.GetBytes(json); - return buffer; - } + public static byte[] Encode(this PointSet self) + { + var json = self.ToJson().ToString(); + var buffer = Encoding.UTF8.GetBytes(json); + return buffer; + } + + /// + public static void Add(this Storage storage, string key, PointSet data) + { + storage.f_add(key, data, () => data.Encode()); + } + /// + public static void Add(this Storage storage, Guid key, PointSet data) + => Add(storage, key.ToString(), data); + + /// + public static PointSet? GetPointSet(this Storage storage, string key) + { + if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) return (PointSet)o; + + var buffer = storage.f_get(key); + if (buffer == null) return null; + var jsonUTF8 = Encoding.UTF8.GetString(buffer); + var json = JsonNode.Parse(jsonUTF8, new JsonNodeOptions() { PropertyNameCaseInsensitive = true }) ?? throw new Exception($"Failed to parse Json. Error 7949aa24-075c-4cb4-8787-69d2de06f892.\n{jsonUTF8}"); + var data = PointSet.Parse(json, storage); + + if (storage.HasCache) storage.Cache.Add( + key, data, buffer.Length, onRemove: default + ); + return data; + } + public static PointSet? GetPointSet(this Storage storage, Guid key) + => GetPointSet(storage, key.ToString()); - /// - public static void Add(this Storage storage, string key, PointSet data) + #endregion + + #region PointSetNode + + /// + public static void Add(this Storage storage, string key, PointSetNode data) + { + storage.f_add(key, data, () => { - storage.f_add(key, data, () => data.Encode()); - } - /// - public static void Add(this Storage storage, Guid key, PointSet data) - => Add(storage, key.ToString(), data); + if (key != data.Id.ToString()) throw new InvalidOperationException("Invariant b5b13ca6-0182-4e00-a7fe-41ccd9362beb."); + return data.Encode(); + }); + } - /// - public static PointSet? GetPointSet(this Storage storage, string key) + /// + public static void Add(this Storage storage, string key, IPointCloudNode data) + { + storage.f_add(key, data, () => { - if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) return (PointSet)o; + if (key != data.Id.ToString()) throw new InvalidOperationException("Invariant a1e63dbc-6996-481e-996f-afdac047be0b."); + return data.Encode(); + }); + } - var buffer = storage.f_get(key); - if (buffer == null) return null; - var jsonUTF8 = Encoding.UTF8.GetString(buffer); - var json = JsonNode.Parse(jsonUTF8, new JsonNodeOptions() { PropertyNameCaseInsensitive = true }) ?? throw new Exception($"Failed to parse Json. Error 7949aa24-075c-4cb4-8787-69d2de06f892.\n{jsonUTF8}"); - var data = PointSet.Parse(json, storage); + /// + /// + public static IPointCloudNode GetPointCloudNode(this Storage storage, Guid key) + => GetPointCloudNode(storage, key.ToString()); - if (storage.HasCache) storage.Cache.Add( - key, data, buffer.Length, onRemove: default + /// + /// + public static IPointCloudNode GetPointCloudNode(this Storage storage, string key) + { + if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) + { + if (o is not IPointCloudNode r) throw new InvalidOperationException( + $"Invariant d1cb769c-36b6-4374-8248-b8c1ca31d495. " + + $"[GetPointCloudNode] Store key {key} is not IPointCloudNode. " + + $"Error 7aebf7f1-3783-42fb-b405-47384e08f716." ); - return data; + return r; } - public static PointSet? GetPointSet(this Storage storage, Guid key) - => GetPointSet(storage, key.ToString()); - - #endregion - #region PointSetNode + var buffer = storage.f_get(key) ?? throw new Exception( + $"PointCloudNode not found (id={key}). " + + $"Error b2ef55c1-1470-465d-80ea-034464c53638." + ); - /// - public static void Add(this Storage storage, string key, PointSetNode data) + try { - storage.f_add(key, data, () => - { - if (key != data.Id.ToString()) throw new InvalidOperationException("Invariant b5b13ca6-0182-4e00-a7fe-41ccd9362beb."); - return data.Encode(); - }); + return TryDecodeBuffer(buffer); } - - /// - public static void Add(this Storage storage, string key, IPointCloudNode data) + catch { - storage.f_add(key, data, () => + try { - if (key != data.Id.ToString()) throw new InvalidOperationException("Invariant a1e63dbc-6996-481e-996f-afdac047be0b."); - return data.Encode(); - }); + return TryDecodeBuffer(UnGzipBuffer(buffer)); + } + catch //(Exception e) + { + //Report.Warn("Failed to decode PointSetNode. Unknown format."); + //Report.Warn($"{e}"); + return ObsoleteNodeParser.Parse(storage, buffer); + } } - /// - /// - public static IPointCloudNode GetPointCloudNode(this Storage storage, Guid key) - => GetPointCloudNode(storage, key.ToString()); - - /// - /// - public static IPointCloudNode GetPointCloudNode(this Storage storage, string key) + IPointCloudNode TryDecodeBuffer(byte[] buffer) { - if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) + var guid = new Guid(buffer.TakeToArray(16)); + if (guid == Durable.Octree.Node.Id) { - if (o is not IPointCloudNode r) throw new InvalidOperationException( - $"Invariant d1cb769c-36b6-4374-8248-b8c1ca31d495. " + - $"[GetPointCloudNode] Store key {key} is not IPointCloudNode. " + - $"Error 7aebf7f1-3783-42fb-b405-47384e08f716." + var data = PointSetNode.Decode(storage, buffer); + if (key != data.Id.ToString()) throw new InvalidOperationException("Invariant 32554e4b-1e53-4e30-8b3c-c218c5b63c46."); + + if (storage.HasCache) storage.Cache.Add( + key, data, buffer.Length, onRemove: default ); - return r; + return data; } + else if (guid == FilteredNode.Defs.FilteredNode.Id) + { + var fn = FilteredNode.Decode(storage, buffer); + if (key != fn.Id.ToString()) throw new InvalidOperationException("Invariant 14511080-b605-4f6b-ac49-2495899ccdec."); - var buffer = storage.f_get(key) ?? throw new Exception( - $"PointCloudNode not found (id={key}). " + - $"Error b2ef55c1-1470-465d-80ea-034464c53638." - ); - try - { - return TryDecodeBuffer(buffer); + if (storage.HasCache) storage.Cache.Add( + key, fn, buffer.Length, onRemove: default // what to add here? + ); + + return fn; } - catch + //else if (guid == Durable.Octree.MultiNodeIndex.Id) + //{ + // var index = MultiNodeIndex.Decode(buffer); + // var (offset, size) = index.GetOffsetAndSize(index.RootNodeId); + // var bufferRootNode = UnGZip(storage.f_getSlice(index.TreeBlobId, offset, size)); + // //var rootNode = DurableCodec.Deserialize(bufferRootNode); + // var rootNode = PointSetNode.Decode(storage, bufferRootNode); + // var multiNode = new MultiNode(index, rootNode); + // return multiNode; + //} + else { - try - { - return TryDecodeBuffer(UnGzipBuffer(buffer)); - } - catch //(Exception e) - { - //Report.Warn("Failed to decode PointSetNode. Unknown format."); - //Report.Warn($"{e}"); - return ObsoleteNodeParser.Parse(storage, buffer); - } + // if all fails, may be obsolete node format ... + return ObsoleteNodeParser.Parse(storage, buffer); } + } - IPointCloudNode TryDecodeBuffer(byte[] buffer) + byte[] UnGzipBuffer(byte[] buffer) + { + using var stream = new GZipStream(new MemoryStream(buffer), CompressionMode.Decompress); + var size = 4096 * 4; + var tmp = new byte[size]; + using var memory = new MemoryStream(); + var count = -1; + while (count != 0) { - var guid = new Guid(buffer.TakeToArray(16)); - if (guid == Durable.Octree.Node.Id) - { - var data = PointSetNode.Decode(storage, buffer); - if (key != data.Id.ToString()) throw new InvalidOperationException("Invariant 32554e4b-1e53-4e30-8b3c-c218c5b63c46."); - - if (storage.HasCache) storage.Cache.Add( - key, data, buffer.Length, onRemove: default - ); - return data; - } - else if (guid == FilteredNode.Defs.FilteredNode.Id) - { - var fn = FilteredNode.Decode(storage, buffer); - if (key != fn.Id.ToString()) throw new InvalidOperationException("Invariant 14511080-b605-4f6b-ac49-2495899ccdec."); - - - if (storage.HasCache) storage.Cache.Add( - key, fn, buffer.Length, onRemove: default // what to add here? - ); - - return fn; - } - //else if (guid == Durable.Octree.MultiNodeIndex.Id) - //{ - // var index = MultiNodeIndex.Decode(buffer); - // var (offset, size) = index.GetOffsetAndSize(index.RootNodeId); - // var bufferRootNode = UnGZip(storage.f_getSlice(index.TreeBlobId, offset, size)); - // //var rootNode = DurableCodec.Deserialize(bufferRootNode); - // var rootNode = PointSetNode.Decode(storage, bufferRootNode); - // var multiNode = new MultiNode(index, rootNode); - // return multiNode; - //} - else - { - // if all fails, may be obsolete node format ... - return ObsoleteNodeParser.Parse(storage, buffer); - } + count = stream.Read(tmp, 0, size); + if (count > 0) memory.Write(tmp, 0, count); } - byte[] UnGzipBuffer(byte[] buffer) - { - using var stream = new GZipStream(new MemoryStream(buffer), CompressionMode.Decompress); - var size = 4096 * 4; - var tmp = new byte[size]; - using var memory = new MemoryStream(); - var count = -1; - while (count != 0) - { - count = stream.Read(tmp, 0, size); - if (count > 0) memory.Write(tmp, 0, count); - } - - var result = memory.ToArray(); - return result; - } + var result = memory.ToArray(); + return result; } + } - /// - /// - public static bool TryGetPointCloudNode(this Storage storage, string key, [NotNullWhen(true)]out IPointCloudNode? result) + /// + /// + public static bool TryGetPointCloudNode(this Storage storage, string key, [NotNullWhen(true)]out IPointCloudNode? result) + { + if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) { - if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) + result = (PointSetNode)o; + return true; + } + else + { + try { - result = (PointSetNode)o; + result = storage.GetPointCloudNode(key); return true; } - else + catch { - try - { - result = storage.GetPointCloudNode(key); - return true; - } - catch - { - result = null; - return false; - } + result = null; + return false; } } + } - /// - /// + /// + /// - [Obsolete("Use TryGetPointCloudNode with out parameter instead.")] - public static (bool, IPointCloudNode?) TryGetPointCloudNode(this Storage storage, string key) + [Obsolete("Use TryGetPointCloudNode with out parameter instead.")] + public static (bool, IPointCloudNode?) TryGetPointCloudNode(this Storage storage, string key) + { + if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) + { + return (true, (PointSetNode)o); + } + else { - if (storage.HasCache && storage.Cache.TryGetValue(key, out object o)) + try { - return (true, (PointSetNode)o); + return (true, storage.GetPointCloudNode(key)); } - else + catch { - try - { - return (true, storage.GetPointCloudNode(key)); - } - catch - { - return (false, default); - } + return (false, default); } } + } - #endregion + #endregion - #region Safe octree load + #region Safe octree load - /// - /// Try to load PointSet or PointCloudNode with given key. - /// Returns octree root node when found. - /// - public static bool TryGetOctree(this Storage storage, string key, [NotNullWhen(true)]out IPointCloudNode? root) + /// + /// Try to load PointSet or PointCloudNode with given key. + /// Returns octree root node when found. + /// + public static bool TryGetOctree(this Storage storage, string key, [NotNullWhen(true)]out IPointCloudNode? root) + { + var bs = storage.f_get.Invoke(key); + if (bs != null) { - var bs = storage.f_get.Invoke(key); - if (bs != null) + try + { + var ps = storage.GetPointSet(key); + if (ps == null) { root = null; return false; } + root = ps.Root.Value!; + return true; + } + catch { try { - var ps = storage.GetPointSet(key); - if (ps == null) { root = null; return false; } - root = ps.Root.Value!; + root = storage.GetPointCloudNode(key); + if (root == null) return false; return true; } - catch + catch (Exception en) { - try - { - root = storage.GetPointCloudNode(key); - if (root == null) return false; - return true; - } - catch (Exception en) - { - Report.Warn($"Invariant 70012c8d-b994-4ddf-adb6-a5481434561a. {en}."); - root = null; - return false; - } + Report.Warn($"Invariant 70012c8d-b994-4ddf-adb6-a5481434561a. {en}."); + root = null; + return false; } } - else - { - Report.Warn($"Key {key} does not exist in store. Invariant af97e19a-63e8-46ad-a752-fe1200828ced."); - root = null; - return false; - } } + else + { + Report.Warn($"Key {key} does not exist in store. Invariant af97e19a-63e8-46ad-a752-fe1200828ced."); + root = null; + return false; + } + } - #endregion - - #region Durable + #endregion - /// - /// Binary encodes (and optionally gzips) a durable map with given definition from a sequence of key/value pairs. - /// Entries are encoded in the same order as given by the data sequence. - /// - public static byte[] DurableEncode(this IEnumerable> data, Durable.Def def, bool gzipped) - { - if (data == null) throw new Exception("Invariant c61b7125-b523-4f19-8f51-5e8be5d06dde."); - if (def == null) throw new Exception("Invariant a126aff5-8352-4e7c-9864-5459bbf0a5d6."); + #region Durable - using var ms = new MemoryStream(); - using var zs = gzipped ? new GZipStream(ms, CompressionLevel.Optimal) : (Stream)ms; - using var bw = new BinaryWriter(zs); - Data.Codec.Serialize(bw, def, data); - bw.Close(); // must close, otherwise GZipStream will not write to completion in net48 (ignores flush!!) - - var buffer = ms.ToArray(); - return buffer; - } + /// + /// Binary encodes (and optionally gzips) a durable map with given definition from a sequence of key/value pairs. + /// Entries are encoded in the same order as given by the data sequence. + /// + public static byte[] DurableEncode(this IEnumerable> data, Durable.Def def, bool gzipped) + { + if (data == null) throw new Exception("Invariant c61b7125-b523-4f19-8f51-5e8be5d06dde."); + if (def == null) throw new Exception("Invariant a126aff5-8352-4e7c-9864-5459bbf0a5d6."); + + using var ms = new MemoryStream(); + using var zs = gzipped ? new GZipStream(ms, CompressionLevel.Optimal) : (Stream)ms; + using var bw = new BinaryWriter(zs); + Data.Codec.Serialize(bw, def, data); + bw.Close(); // must close, otherwise GZipStream will not write to completion in net48 (ignores flush!!) + + var buffer = ms.ToArray(); + return buffer; + } - public static byte[] Add(this Storage storage, string key, Durable.Def def, IEnumerable> data, bool gzipped) - { - if (key == null) throw new Exception("Invariant 40e0143a-af01-4e77-b278-abb1a0c182f2."); - if (def == null) throw new Exception("Invariant a7a6516e-e019-46ea-b7db-69b559a2aad4."); - if (data == null) throw new Exception("Invariant ec5b1c03-d92c-4b2d-9b5c-a30f935369e5."); + public static byte[] Add(this Storage storage, string key, Durable.Def def, IEnumerable> data, bool gzipped) + { + if (key == null) throw new Exception("Invariant 40e0143a-af01-4e77-b278-abb1a0c182f2."); + if (def == null) throw new Exception("Invariant a7a6516e-e019-46ea-b7db-69b559a2aad4."); + if (data == null) throw new Exception("Invariant ec5b1c03-d92c-4b2d-9b5c-a30f935369e5."); + + using var ms = new MemoryStream(); + using var zs = gzipped ? new GZipStream(ms, CompressionLevel.Optimal) : (Stream)ms; + using var bw = new BinaryWriter(zs); + Data.Codec.Serialize(bw, def, data); + bw.Close(); // must close, otherwise GZipStream will not write to completion in net48 (ignores flush!!) + + var buffer = ms.ToArray(); + storage.Add(key, buffer); - using var ms = new MemoryStream(); - using var zs = gzipped ? new GZipStream(ms, CompressionLevel.Optimal) : (Stream)ms; - using var bw = new BinaryWriter(zs); - Data.Codec.Serialize(bw, def, data); - bw.Close(); // must close, otherwise GZipStream will not write to completion in net48 (ignores flush!!) - - var buffer = ms.ToArray(); - storage.Add(key, buffer); + return buffer; + } + public static byte[] Add(this Storage storage, Guid key, Durable.Def def, ImmutableDictionary data, bool gzipped) + => Add(storage, key.ToString(), def, data, gzipped); + public static byte[] Add(this Storage storage, Guid key, Durable.Def def, IEnumerable> data, bool gzipped) + => Add(storage, key.ToString(), def, data, gzipped); - return buffer; - } - public static byte[] Add(this Storage storage, Guid key, Durable.Def def, ImmutableDictionary data, bool gzipped) - => Add(storage, key.ToString(), def, data, gzipped); - public static byte[] Add(this Storage storage, Guid key, Durable.Def def, IEnumerable> data, bool gzipped) - => Add(storage, key.ToString(), def, data, gzipped); - - public static (Durable.Def, object) DurableDecode(this byte[] buffer) - => Data.Codec.Deserialize(buffer); - public static T DurableDecode(this byte[] buffer) - => (T)Data.Codec.Deserialize(buffer).Item2; - - public static (Durable.Def?, object?) GetDurable(this Storage storage, string key) - { - if (key == null) return (null, null); - var buffer = storage.f_get(key); - if (buffer == null) return (null, null); - return Data.Codec.Deserialize(buffer); - } - public static (Durable.Def?, object?) GetDurable(this Storage storage, Guid key) - => GetDurable(storage, key.ToString()); + public static (Durable.Def, object) DurableDecode(this byte[] buffer) + => Data.Codec.Deserialize(buffer); + public static T DurableDecode(this byte[] buffer) + => (T)Data.Codec.Deserialize(buffer).Item2; - #endregion + public static (Durable.Def?, object?) GetDurable(this Storage storage, string key) + { + if (key == null) return (null, null); + var buffer = storage.f_get(key); + if (buffer == null) return (null, null); + return Data.Codec.Deserialize(buffer); } + public static (Durable.Def?, object?) GetDurable(this Storage storage, Guid key) + => GetDurable(storage, key.ToString()); + + #endregion } diff --git a/src/Aardvark.Geometry.PointSet/Utils/Timing.cs b/src/Aardvark.Geometry.PointSet/Utils/Timing.cs index 3f1aa544..9215b850 100644 --- a/src/Aardvark.Geometry.PointSet/Utils/Timing.cs +++ b/src/Aardvark.Geometry.PointSet/Utils/Timing.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2023. Aardvark Platform Team. http://github.com/aardvark-platform. + Copyright (C) 2006-2024. Aardvark Platform Team. http://github.com/aardvark-platform. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -14,20 +14,19 @@ You should have received a copy of the GNU Affero General Public License using System; using System.Diagnostics; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +internal static class Timing { - internal static class Timing + public static IDisposable Do(string s) { - public static IDisposable Do(string s) + Console.WriteLine($"{s} ..."); + var sw = new Stopwatch(); + sw.Start(); + return new Disposable(() => { - Console.WriteLine($"{s} ..."); - var sw = new Stopwatch(); - sw.Start(); - return new Disposable(() => - { - Console.WriteLine($"{s} ... {sw.Elapsed}"); - sw = null; - }); - } + Console.WriteLine($"{s} ... {sw.Elapsed}"); + sw = null; + }); } } diff --git a/src/Aardvark.Geometry.PointSet/Views/FilteredNode.cs b/src/Aardvark.Geometry.PointSet/Views/FilteredNode.cs index 5835a170..a12317c4 100644 --- a/src/Aardvark.Geometry.PointSet/Views/FilteredNode.cs +++ b/src/Aardvark.Geometry.PointSet/Views/FilteredNode.cs @@ -23,709 +23,708 @@ You should have received a copy of the GNU Affero General Public License using System.Text.Json.Serialization; using static Aardvark.Data.Durable; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + + +/// +/// A filtered view onto a point cloud. +/// +public class FilteredNode : IPointCloudNode { + #region Construction /// - /// A filtered view onto a point cloud. + /// Creates a permanent FilteredNode, which is written to the store. /// - public class FilteredNode : IPointCloudNode + public static IPointCloudNode Create(Guid id, IPointCloudNode node, IFilter filter) { - #region Construction - - /// - /// Creates a permanent FilteredNode, which is written to the store. - /// - public static IPointCloudNode Create(Guid id, IPointCloudNode node, IFilter filter) - { - if (node.IsTemporaryImportNode) throw new InvalidOperationException( - "FilteredNode cannot be created from temporary import node. Invariant b9c2dca3-1510-4ea7-959f-6a0737c707fa." - ); - return new FilteredNode(id, writeToStore: true, node, filter); - } + if (node.IsTemporaryImportNode) throw new InvalidOperationException( + "FilteredNode cannot be created from temporary import node. Invariant b9c2dca3-1510-4ea7-959f-6a0737c707fa." + ); + return new FilteredNode(id, writeToStore: true, node, filter); + } - /// - /// Creates a permanent FilteredNode, which is written to the store. - /// - public static IPointCloudNode Create(IPointCloudNode node, IFilter filter) - => Create(Guid.NewGuid(), node, filter); + /// + /// Creates a permanent FilteredNode, which is written to the store. + /// + public static IPointCloudNode Create(IPointCloudNode node, IFilter filter) + => Create(Guid.NewGuid(), node, filter); - /// - /// Creates an in-memory FilteredNode, which is not written to the store. - /// - public static IPointCloudNode CreateTransient(Guid id, IPointCloudNode node, IFilter filter) - { - if (node.IsTemporaryImportNode) throw new InvalidOperationException( - "FilteredNode cannot be created from temporary import node. Invariant e2b1d90c-38b8-4e05-a832-97ca4936ed3c." - ); - return new FilteredNode(id, writeToStore: false, node, filter); - } + /// + /// Creates an in-memory FilteredNode, which is not written to the store. + /// + public static IPointCloudNode CreateTransient(Guid id, IPointCloudNode node, IFilter filter) + { + if (node.IsTemporaryImportNode) throw new InvalidOperationException( + "FilteredNode cannot be created from temporary import node. Invariant e2b1d90c-38b8-4e05-a832-97ca4936ed3c." + ); + return new FilteredNode(id, writeToStore: false, node, filter); + } - /// - /// Creates an in-memory FilteredNode, which is not written to the store. - /// - public static IPointCloudNode CreateTransient(IPointCloudNode node, IFilter filter) - => CreateTransient(Guid.NewGuid(), node, filter); + /// + /// Creates an in-memory FilteredNode, which is not written to the store. + /// + public static IPointCloudNode CreateTransient(IPointCloudNode node, IFilter filter) + => CreateTransient(Guid.NewGuid(), node, filter); - /// - /// - private FilteredNode(Guid id, bool writeToStore, IPointCloudNode node, IFilter filter) - { - Id = id; - Node = node ?? throw new ArgumentNullException(nameof(node)); - Filter = filter ?? throw new ArgumentNullException(nameof(filter)); + /// + /// + private FilteredNode(Guid id, bool writeToStore, IPointCloudNode node, IFilter filter) + { + Id = id; + Node = node ?? throw new ArgumentNullException(nameof(node)); + Filter = filter ?? throw new ArgumentNullException(nameof(filter)); - if (filter.IsFullyInside(node)) m_activePoints = null; - else if (filter.IsFullyOutside(node)) m_activePoints = new HashSet(); - else m_activePoints = Filter.FilterPoints(node, m_activePoints); + if (filter.IsFullyInside(node)) m_activePoints = null; + else if (filter.IsFullyOutside(node)) m_activePoints = []; + else m_activePoints = Filter.FilterPoints(node, m_activePoints); - if (writeToStore) WriteToStore(); - } + if (writeToStore) WriteToStore(); + } - #endregion + #endregion - #region Properties + #region Properties - private PersistentRef?[]? m_subnodes_cache; + private PersistentRef?[]? m_subnodes_cache; - private readonly HashSet? m_activePoints; + private readonly HashSet? m_activePoints; - /// - public Guid Id { get; } + /// + public Guid Id { get; } - /// - public bool IsTemporaryImportNode => false; + /// + public bool IsTemporaryImportNode => false; - /// - public IPointCloudNode Node { get; } + /// + public IPointCloudNode Node { get; } - /// - public IFilter Filter { get; } + /// + public IFilter Filter { get; } - /// - public Storage Storage => Node.Storage; + /// + public Storage Storage => Node.Storage; - /// - public bool IsMaterialized => false; + /// + public bool IsMaterialized => false; - /// - public bool IsEmpty => Node.IsEmpty; + /// + public bool IsEmpty => Node.IsEmpty; - /// - public IPointCloudNode Materialize() + /// + public IPointCloudNode Materialize() + { + var newId = Guid.NewGuid(); + var data = ImmutableDictionary.Empty + .Add(Octree.NodeId, newId) + .Add(Octree.Cell, Cell) + .Add(Octree.BoundingBoxExactLocal, BoundingBoxExactLocal) + ; + + if (IsLeaf) + { + data = data.Add(Octree.BoundingBoxExactGlobal, BoundingBoxExactGlobal); + } + else { - var newId = Guid.NewGuid(); - var data = ImmutableDictionary.Empty - .Add(Octree.NodeId, newId) - .Add(Octree.Cell, Cell) - .Add(Octree.BoundingBoxExactLocal, BoundingBoxExactLocal) + var subnodes = Subnodes.Map(x => x?.Value.Materialize()); + var subnodeIds = subnodes.Map(x => x?.Id ?? Guid.Empty); + var bbExactGlobal = new Box3d(subnodes.Where(x => x != null).Select(x => x!.BoundingBoxExactGlobal)); + data = data + .Add(Octree.SubnodesGuids, subnodeIds) + .Add(Octree.BoundingBoxExactGlobal, bbExactGlobal) ; + } - if (IsLeaf) - { - data = data.Add(Octree.BoundingBoxExactGlobal, BoundingBoxExactGlobal); - } - else - { - var subnodes = Subnodes.Map(x => x?.Value.Materialize()); - var subnodeIds = subnodes.Map(x => x?.Id ?? Guid.Empty); - var bbExactGlobal = new Box3d(subnodes.Where(x => x != null).Select(x => x!.BoundingBoxExactGlobal)); - data = data - .Add(Octree.SubnodesGuids, subnodeIds) - .Add(Octree.BoundingBoxExactGlobal, bbExactGlobal) - ; - } - - if (HasPositions) - { - var id = Guid.NewGuid(); - Storage.Add(id, Positions.Value); - data = data.Add(Octree.PositionsLocal3fReference, id); - } - - if (HasKdTree) - { - var id = Guid.NewGuid(); - Storage.Add(id, KdTree.Value.Data); - data = data.Add(Octree.PointRkdTreeFDataReference, id); - } + if (HasPositions) + { + var id = Guid.NewGuid(); + Storage.Add(id, Positions.Value); + data = data.Add(Octree.PositionsLocal3fReference, id); + } - if (HasColors) - { - var id = Guid.NewGuid(); - Storage.Add(id, Colors.Value); - data = data.Add(Octree.Colors4bReference, id); - } + if (HasKdTree) + { + var id = Guid.NewGuid(); + Storage.Add(id, KdTree.Value.Data); + data = data.Add(Octree.PointRkdTreeFDataReference, id); + } - if (HasNormals) - { - var id = Guid.NewGuid(); - Storage.Add(id, Normals.Value); - data = data.Add(Octree.Normals3fReference, id); - } + if (HasColors) + { + var id = Guid.NewGuid(); + Storage.Add(id, Colors.Value); + data = data.Add(Octree.Colors4bReference, id); + } - if (HasClassifications) - { - var id = Guid.NewGuid(); - Storage.Add(id, Classifications.Value); - data = data.Add(Octree.Classifications1bReference, id); - } + if (HasNormals) + { + var id = Guid.NewGuid(); + Storage.Add(id, Normals.Value); + data = data.Add(Octree.Normals3fReference, id); + } - if (HasIntensities) - { - var id = Guid.NewGuid(); - Storage.Add(id, Intensities.Value); - data = data.Add(Octree.Intensities1iReference, id); - } + if (HasClassifications) + { + var id = Guid.NewGuid(); + Storage.Add(id, Classifications.Value); + data = data.Add(Octree.Classifications1bReference, id); + } - var result = new PointSetNode(data, Storage, writeToStore: true); - return result; + if (HasIntensities) + { + var id = Guid.NewGuid(); + Storage.Add(id, Intensities.Value); + data = data.Add(Octree.Intensities1iReference, id); } - /// - public Cell Cell => Node.Cell; + var result = new PointSetNode(data, Storage, writeToStore: true); + return result; + } - /// - public V3d Center => Node.Center; + /// + public Cell Cell => Node.Cell; - /// - public long PointCountTree + /// + public V3d Center => Node.Center; + + /// + public long PointCountTree + { + get { - get + if(m_activePoints == null) { - if(m_activePoints == null) - { - return Node.PointCountTree; - } - else if(m_activePoints.Count == 0L) - { - return 0L; - } - else - { - var ratio = (double)m_activePoints.Count / (double)Node.Positions.Value.Length; - return (long)((double)Node.PointCountTree * ratio); - } + return Node.PointCountTree; + } + else if(m_activePoints.Count == 0L) + { + return 0L; + } + else + { + var ratio = (double)m_activePoints.Count / (double)Node.Positions.Value.Length; + return (long)((double)Node.PointCountTree * ratio); } } + } - /// - public bool Has(Def what) => Node.Has(what); + /// + public bool Has(Def what) => Node.Has(what); - /// - public bool TryGetValue(Def what, [NotNullWhen(true)]out object? o) => Node.TryGetValue(what, out o); + /// + public bool TryGetValue(Def what, [NotNullWhen(true)]out object? o) => Node.TryGetValue(what, out o); - /// - public IReadOnlyDictionary Properties => Node.Properties; + /// + public IReadOnlyDictionary Properties => Node.Properties; - /// - public PersistentRef[]? Subnodes + /// + public PersistentRef[]? Subnodes + { + get { - get - { - if (Node.Subnodes == null) return null; + if (Node.Subnodes == null) return null; - if (m_subnodes_cache == null) + if (m_subnodes_cache == null) + { + m_subnodes_cache = new PersistentRef[8]; + for (var i = 0; i < 8; i++) { - m_subnodes_cache = new PersistentRef[8]; - for (var i = 0; i < 8; i++) - { - var subCell = Cell.GetOctant(i); + var subCell = Cell.GetOctant(i); - var spatial = Filter as ISpatialFilter; + var spatial = Filter as ISpatialFilter; - if (spatial != null && spatial.IsFullyInside(subCell.BoundingBox)) - { - m_subnodes_cache[i] = Node.Subnodes[i]; - } - else if (spatial != null && spatial.IsFullyOutside(subCell.BoundingBox)) - { - m_subnodes_cache[i] = null; - } - else + if (spatial != null && spatial.IsFullyInside(subCell.BoundingBox)) + { + m_subnodes_cache[i] = Node.Subnodes[i]; + } + else if (spatial != null && spatial.IsFullyOutside(subCell.BoundingBox)) + { + m_subnodes_cache[i] = null; + } + else + { + var id = (Id + "." + i).ToGuid(); + var n0 = Node.Subnodes[i]?.Value; + if (n0 != null) { - var id = (Id + "." + i).ToGuid(); - var n0 = Node.Subnodes[i]?.Value; - if (n0 != null) + if (Filter.IsFullyInside(n0)) { - if (Filter.IsFullyInside(n0)) - { - m_subnodes_cache[i] = Node.Subnodes[i]; - } - else if (Filter.IsFullyOutside(n0)) - { - m_subnodes_cache[i] = null; - } - else if (n0 != null) - { - var n = new FilteredNode(id, false, n0, Filter); - m_subnodes_cache[i] = new PersistentRef(id, n); - } + m_subnodes_cache[i] = Node.Subnodes[i]; } - else + else if (Filter.IsFullyOutside(n0)) { m_subnodes_cache[i] = null; } + else if (n0 != null) + { + var n = new FilteredNode(id, false, n0, Filter); + m_subnodes_cache[i] = new PersistentRef(id, n); + } + } + else + { + m_subnodes_cache[i] = null; } + } - } } - return m_subnodes_cache!; } + return m_subnodes_cache!; } + } - /// - public int PointCountCell => m_activePoints == null ? Node.PointCountCell : m_activePoints.Count; + /// + public int PointCountCell => m_activePoints == null ? Node.PointCountCell : m_activePoints.Count; - /// - public bool IsLeaf => Node.IsLeaf; + /// + public bool IsLeaf => Node.IsLeaf; - #region Positions + #region Positions - /// - public bool HasPositions => Node.HasPositions; + /// + public bool HasPositions => Node.HasPositions; - /// - public PersistentRef Positions + /// + public PersistentRef Positions + { + get { - get - { - EnsurePositionsAndDerived(); - return (PersistentRef)m_cache[Octree.PositionsLocal3f.Id]; - } + EnsurePositionsAndDerived(); + return (PersistentRef)m_cache[Octree.PositionsLocal3f.Id]; } + } - /// - public V3d[] PositionsAbsolute + /// + public V3d[] PositionsAbsolute + { + get { - get - { - EnsurePositionsAndDerived(); - return (V3d[])m_cache[Octree.PositionsGlobal3d.Id]; - } + EnsurePositionsAndDerived(); + return (V3d[])m_cache[Octree.PositionsGlobal3d.Id]; } + } - private bool m_ensuredPositionsAndDerived = false; - private void EnsurePositionsAndDerived() - { - if (m_ensuredPositionsAndDerived) return; + private bool m_ensuredPositionsAndDerived = false; + private void EnsurePositionsAndDerived() + { + if (m_ensuredPositionsAndDerived) return; - var result = GetSubArray(Octree.PositionsLocal3f, Node.Positions)!; - m_cache[Octree.PositionsLocal3f.Id] = result; - var psLocal = result.Value; + var result = GetSubArray(Octree.PositionsLocal3f, Node.Positions)!; + m_cache[Octree.PositionsLocal3f.Id] = result; + var psLocal = result.Value; - var c = Center; - var psGlobal = psLocal.Map(p => (V3d)p + c); - m_cache[Octree.PositionsGlobal3d.Id] = psGlobal; + var c = Center; + var psGlobal = psLocal.Map(p => (V3d)p + c); + m_cache[Octree.PositionsGlobal3d.Id] = psGlobal; - var bboxLocal = psLocal.Length > 0 ? new Box3f(psLocal) : Box3f.Invalid; - m_cache[Octree.BoundingBoxExactLocal.Id] = bboxLocal; + var bboxLocal = psLocal.Length > 0 ? new Box3f(psLocal) : Box3f.Invalid; + m_cache[Octree.BoundingBoxExactLocal.Id] = bboxLocal; - var kd = psLocal.BuildKdTree(); - var pRefKd = new PersistentRef>(Guid.NewGuid(), kd); - m_cache[Octree.PointRkdTreeFData.Id] = pRefKd; + var kd = psLocal.BuildKdTree(); + var pRefKd = new PersistentRef>(Guid.NewGuid(), kd); + m_cache[Octree.PointRkdTreeFData.Id] = pRefKd; - m_ensuredPositionsAndDerived = true; - } + m_ensuredPositionsAndDerived = true; + } - #endregion + #endregion - #region BoundingBoxExactLocal + #region BoundingBoxExactLocal - /// - public bool HasBoundingBoxExactLocal => Node.HasBoundingBoxExactLocal; + /// + public bool HasBoundingBoxExactLocal => Node.HasBoundingBoxExactLocal; - /// - public Box3f BoundingBoxExactLocal + /// + public Box3f BoundingBoxExactLocal + { + get { - get - { - EnsurePositionsAndDerived(); - return (Box3f)m_cache[Octree.BoundingBoxExactLocal.Id]; - } + EnsurePositionsAndDerived(); + return (Box3f)m_cache[Octree.BoundingBoxExactLocal.Id]; } + } - #endregion + #endregion - #region BoundingBoxExactGlobal + #region BoundingBoxExactGlobal - /// - public bool HasBoundingBoxExactGlobal => Node.HasBoundingBoxExactGlobal; + /// + public bool HasBoundingBoxExactGlobal => Node.HasBoundingBoxExactGlobal; - /// - public Box3d BoundingBoxExactGlobal + /// + public Box3d BoundingBoxExactGlobal + { + get { - get - { - if (Filter is ISpatialFilter sf) return sf.Clip(Node.BoundingBoxExactGlobal); - else return Node.BoundingBoxExactGlobal; - } + if (Filter is ISpatialFilter sf) return sf.Clip(Node.BoundingBoxExactGlobal); + else return Node.BoundingBoxExactGlobal; } + } - public Box3d BoundingBoxApproximate + public Box3d BoundingBoxApproximate + { + get { - get - { - if (Filter is ISpatialFilter sf) return sf.Clip(Node.BoundingBoxApproximate); - else return Node.BoundingBoxApproximate; - } + if (Filter is ISpatialFilter sf) return sf.Clip(Node.BoundingBoxApproximate); + else return Node.BoundingBoxApproximate; } - #endregion + } + #endregion - #region KdTree + #region KdTree - /// + /// - [MemberNotNullWhen(true, nameof(KdTree))] - public bool HasKdTree => Node.HasKdTree; + [MemberNotNullWhen(true, nameof(KdTree))] + public bool HasKdTree => Node.HasKdTree; - /// - public PersistentRef> KdTree + /// + public PersistentRef> KdTree + { + get { - get - { - EnsurePositionsAndDerived(); - return (PersistentRef>)m_cache[Octree.PointRkdTreeFData.Id]; - } + EnsurePositionsAndDerived(); + return (PersistentRef>)m_cache[Octree.PointRkdTreeFData.Id]; } + } - #endregion + #endregion - #region Colors + #region Colors - /// + /// - [MemberNotNullWhen(true, nameof(Colors))] - public bool HasColors => Node.HasColors; + [MemberNotNullWhen(true, nameof(Colors))] + public bool HasColors => Node.HasColors; - /// - public PersistentRef? Colors => GetSubArray(Octree.Colors4b, Node.Colors); + /// + public PersistentRef? Colors => GetSubArray(Octree.Colors4b, Node.Colors); - #endregion + #endregion - #region Normals + #region Normals - /// + /// - [MemberNotNullWhen(true, nameof(Normals))] - public bool HasNormals => Node.HasNormals; + [MemberNotNullWhen(true, nameof(Normals))] + public bool HasNormals => Node.HasNormals; - /// - public PersistentRef? Normals => GetSubArray(Octree.Normals3f, Node.Normals); + /// + public PersistentRef? Normals => GetSubArray(Octree.Normals3f, Node.Normals); - #endregion + #endregion - #region Intensities + #region Intensities - /// + /// - [MemberNotNullWhen(true, nameof(Intensities))] - public bool HasIntensities => Node.HasIntensities; + [MemberNotNullWhen(true, nameof(Intensities))] + public bool HasIntensities => Node.HasIntensities; - /// - public PersistentRef? Intensities => GetSubArray(Octree.Intensities1i, Node.Intensities); + /// + public PersistentRef? Intensities => GetSubArray(Octree.Intensities1i, Node.Intensities); - #endregion + #endregion - #region Classifications + #region Classifications - /// + /// - [MemberNotNullWhen(true, nameof(Classifications))] - public bool HasClassifications => Node.HasClassifications; + [MemberNotNullWhen(true, nameof(Classifications))] + public bool HasClassifications => Node.HasClassifications; - /// - public PersistentRef? Classifications => GetSubArray(Octree.Classifications1b, Node.Classifications); + /// + public PersistentRef? Classifications => GetSubArray(Octree.Classifications1b, Node.Classifications); - #endregion + #endregion - #region PartIndices + #region PartIndices - /// - /// True if this node has a PartIndexRange. - /// - [JsonIgnore] - [MemberNotNullWhen(true, nameof(PartIndexRange))] - public bool HasPartIndexRange => Node.HasPartIndexRange; + /// + /// True if this node has a PartIndexRange. + /// + [JsonIgnore] + [MemberNotNullWhen(true, nameof(PartIndexRange))] + public bool HasPartIndexRange => Node.HasPartIndexRange; - /// - /// Octree. Min and max part index in octree. - /// - public Range1i? PartIndexRange + /// + /// Octree. Min and max part index in octree. + /// + public Range1i? PartIndexRange + { + get { - get + if (HasPartIndices) { - if (HasPartIndices) + if (SubsetIndexArray == null) + { + return Node.PartIndexRange; + } + else { - if (SubsetIndexArray == null) + if (m_cache.TryGetValue(Octree.PartIndexRange.Id, out var _range)) { - return Node.PartIndexRange; + return (Range1i)_range; } else { - if (m_cache.TryGetValue(Octree.PartIndexRange.Id, out var _range)) + if (TryGetPartIndices(out var qs)) { - return (Range1i)_range; + var range = new Range1i(qs); + m_cache[Octree.PartIndexRange.Id] = range; + return range; } else { - if (TryGetPartIndices(out var qs)) - { - var range = new Range1i(qs); - m_cache[Octree.PartIndexRange.Id] = range; - return range; - } - else - { - throw new Exception($"Expected part indices exist. Error 8d191c4a-042c-4179-ac30-8a10aab16436."); - } + throw new Exception($"Expected part indices exist. Error 8d191c4a-042c-4179-ac30-8a10aab16436."); } } } - else - { - return null; - } + } + else + { + return null; } } + } - /// - /// True if this node has part indices. - /// - [MemberNotNullWhen(true, nameof(PartIndices))] - public bool HasPartIndices => Node.HasPartIndices; - - /// - /// Octree. Per-point or per-cell part indices. - /// - public object? PartIndices => SubsetIndexArray != null ? PartIndexUtils.Subset(Node.PartIndices, SubsetIndexArray) : Node.PartIndices; - - /// - /// Get per-point part indices as an int array (regardless of internal representation). - /// Returns false if node has no part indices. - /// - public bool TryGetPartIndices([NotNullWhen(true)] out int[]? result) - { - var qs = PartIndices; + /// + /// True if this node has part indices. + /// + [MemberNotNullWhen(true, nameof(PartIndices))] + public bool HasPartIndices => Node.HasPartIndices; + + /// + /// Octree. Per-point or per-cell part indices. + /// + public object? PartIndices => SubsetIndexArray != null ? PartIndexUtils.Subset(Node.PartIndices, SubsetIndexArray) : Node.PartIndices; + + /// + /// Get per-point part indices as an int array (regardless of internal representation). + /// Returns false if node has no part indices. + /// + public bool TryGetPartIndices([NotNullWhen(true)] out int[]? result) + { + var qs = PartIndices; - if (m_cache.TryGetValue(Octree.PerPointPartIndex1i.Id, out var _result)) + if (m_cache.TryGetValue(Octree.PerPointPartIndex1i.Id, out var _result)) + { + result = (int[])_result; + return true; + } + else + { + switch (qs) { - result = (int[])_result; - return true; + case null: result = null; return false; + case int x: result = new int[PointCountCell].Set(x); break; + case uint x: checked { result = new int[PointCountCell].Set((int)x); break; } + case byte[] xs: result = xs.Map(x => (int)x); break; + case short[] xs: result = xs.Map(x => (int)x); break; + case int[] xs: result = xs; break; + default: + throw new Exception( + $"Unexpected type {qs.GetType().FullName}. " + + $"Error ccc0b898-fe4f-4373-ac15-42da763fe5ab." + ); } - else - { - switch (qs) - { - case null: result = null; return false; - case int x: result = new int[PointCountCell].Set(x); break; - case uint x: checked { result = new int[PointCountCell].Set((int)x); break; } - case byte[] xs: result = xs.Map(x => (int)x); break; - case short[] xs: result = xs.Map(x => (int)x); break; - case int[] xs: result = xs; break; - default: - throw new Exception( - $"Unexpected type {qs.GetType().FullName}. " + - $"Error ccc0b898-fe4f-4373-ac15-42da763fe5ab." - ); - } - m_cache[Octree.PerPointPartIndex1i.Id] = result; + m_cache[Octree.PerPointPartIndex1i.Id] = result; - return true; - } + return true; } + } - #endregion + #endregion - #region Velocities + #region Velocities - /// - /// Deprecated. Always returns false. Use custom attributes instead. - /// - [Obsolete("Use custom attributes instead.")] + /// + /// Deprecated. Always returns false. Use custom attributes instead. + /// + [Obsolete("Use custom attributes instead.")] #pragma warning disable CS0618 // Type or member is obsolete - [MemberNotNullWhen(true, nameof(Velocities))] + [MemberNotNullWhen(true, nameof(Velocities))] #pragma warning restore CS0618 // Type or member is obsolete - public bool HasVelocities => false; + public bool HasVelocities => false; - /// - /// Deprecated. Always returns null. Use custom attributes instead. - /// - [Obsolete("Use custom attributes instead.")] - public PersistentRef? Velocities => null; + /// + /// Deprecated. Always returns null. Use custom attributes instead. + /// + [Obsolete("Use custom attributes instead.")] + public PersistentRef? Velocities => null; - #endregion + #endregion - #region CentroidLocal + #region CentroidLocal - /// - public bool HasCentroidLocal => Node.HasCentroidLocal; + /// + public bool HasCentroidLocal => Node.HasCentroidLocal; - /// - public V3f CentroidLocal => Node.CentroidLocal; + /// + public V3f CentroidLocal => Node.CentroidLocal; - /// - public bool HasCentroidLocalStdDev => Node.HasCentroidLocalStdDev; + /// + public bool HasCentroidLocalStdDev => Node.HasCentroidLocalStdDev; - /// - public float CentroidLocalStdDev => Node.CentroidLocalStdDev; + /// + public float CentroidLocalStdDev => Node.CentroidLocalStdDev; - #endregion + #endregion - #region TreeDepth + #region TreeDepth - /// - public bool HasMinTreeDepth => Node.HasMinTreeDepth; + /// + public bool HasMinTreeDepth => Node.HasMinTreeDepth; - /// - public int MinTreeDepth => Node.MinTreeDepth; + /// + public int MinTreeDepth => Node.MinTreeDepth; - /// - public bool HasMaxTreeDepth => Node.HasMaxTreeDepth; + /// + public bool HasMaxTreeDepth => Node.HasMaxTreeDepth; - /// - public int MaxTreeDepth => Node.MaxTreeDepth; + /// + public int MaxTreeDepth => Node.MaxTreeDepth; - #endregion + #endregion - #region PointDistance + #region PointDistance - /// - public bool HasPointDistanceAverage => Node.HasPointDistanceAverage; + /// + public bool HasPointDistanceAverage => Node.HasPointDistanceAverage; - /// - /// Average distance of points in this cell. - /// - public float PointDistanceAverage => Node.PointDistanceAverage; + /// + /// Average distance of points in this cell. + /// + public float PointDistanceAverage => Node.PointDistanceAverage; - /// - public bool HasPointDistanceStandardDeviation => Node.HasPointDistanceStandardDeviation; + /// + public bool HasPointDistanceStandardDeviation => Node.HasPointDistanceStandardDeviation; - /// - /// Standard deviation of distance of points in this cell. - /// - public float PointDistanceStandardDeviation => Node.PointDistanceStandardDeviation; + /// + /// Standard deviation of distance of points in this cell. + /// + public float PointDistanceStandardDeviation => Node.PointDistanceStandardDeviation; - #endregion + #endregion - private readonly Dictionary m_cache = new(); + private readonly Dictionary m_cache = []; - private PersistentRef? GetSubArray(Def def, PersistentRef? originalValue) - { - if (m_cache.TryGetValue(def.Id, out var o) && o is PersistentRef x) return x; + private PersistentRef? GetSubArray(Def def, PersistentRef? originalValue) + { + if (m_cache.TryGetValue(def.Id, out var o) && o is PersistentRef x) return x; - if (originalValue == null) return null; - // should be empty not null, right? - if (m_activePoints == null) return originalValue; + if (originalValue == null) return null; + // should be empty not null, right? + if (m_activePoints == null) return originalValue; - var key = (Id + originalValue.Id).ToGuid().ToString(); - var xs = originalValue.Value.Where((_, i) => m_activePoints.Contains(i)).ToArray(); - var result = new PersistentRef(key, xs); - m_cache[def.Id] = result; - return result; - } + var key = (Id + originalValue.Id).ToGuid().ToString(); + var xs = originalValue.Value.Where((_, i) => m_activePoints.Contains(i)).ToArray(); + var result = new PersistentRef(key, xs); + m_cache[def.Id] = result; + return result; + } - private int[]? _subsetIndexArray = null; - private int[]? SubsetIndexArray + private int[]? _subsetIndexArray = null; + private int[]? SubsetIndexArray + { + get { - get - { - if (_subsetIndexArray != null) return _subsetIndexArray; - if (m_activePoints == null) return null; + if (_subsetIndexArray != null) return _subsetIndexArray; + if (m_activePoints == null) return null; - var xs = m_activePoints.ToArray(); - xs.QuickSortAscending(); - return _subsetIndexArray = xs; - } + var xs = m_activePoints.ToArray(); + xs.QuickSortAscending(); + return _subsetIndexArray = xs; } + } - #endregion + #endregion - #region Not supported ... + #region Not supported ... - /// - /// FilteredNode does not support With. - /// - public IPointCloudNode With(IReadOnlyDictionary replacements) - => throw new InvalidOperationException("Invariant 3de7dad1-668d-4104-838b-552eae03f7a8."); + /// + /// FilteredNode does not support With. + /// + public IPointCloudNode With(IReadOnlyDictionary replacements) + => throw new InvalidOperationException("Invariant 3de7dad1-668d-4104-838b-552eae03f7a8."); - /// - /// FilteredNode does not support WithSubNodes. - /// - public IPointCloudNode WithSubNodes(IPointCloudNode?[] subnodes) - => throw new InvalidOperationException("Invariant 62e6dab8-133a-452d-8d8c-f0b0eb5f286c."); + /// + /// FilteredNode does not support WithSubNodes. + /// + public IPointCloudNode WithSubNodes(IPointCloudNode?[] subnodes) + => throw new InvalidOperationException("Invariant 62e6dab8-133a-452d-8d8c-f0b0eb5f286c."); - #endregion + #endregion - #region Durable codec + #region Durable codec + /// + public static class Defs + { /// - public static class Defs - { - /// - public static readonly Def FilteredNode = new( - new Guid("a5dd1687-ea0b-4735-9be1-b74b969e0673"), - "Octree.FilteredNode", "Octree.FilteredNode. A filtered octree node.", - Primitives.DurableMap.Id, false - ); - - /// - public static readonly Def FilteredNodeRootId = new( - new Guid("f9a7c994-35b3-4d50-b5b0-80af05896987"), - "Octree.FilteredNode.RootId", "Octree.FilteredNode. Node id of the node to be filtered.", - Primitives.GuidDef.Id, false - ); - - /// - public static readonly Def FilteredNodeFilter = new( - new Guid("1d2298b6-df47-4170-8fc2-4bd899ea6153"), - "Octree.FilteredNode.Filter", "Octree.FilteredNode. Filter definition as UTF8-encoded JSON string.", - Primitives.StringUTF8.Id, false - ); - } + public static readonly Def FilteredNode = new( + new Guid("a5dd1687-ea0b-4735-9be1-b74b969e0673"), + "Octree.FilteredNode", "Octree.FilteredNode. A filtered octree node.", + Primitives.DurableMap.Id, false + ); /// - public IPointCloudNode WriteToStore() - { - this.CheckDerivedAttributes(); - var buffer = Encode(); - Storage.Add(Id, buffer); - return this; - } + public static readonly Def FilteredNodeRootId = new( + new Guid("f9a7c994-35b3-4d50-b5b0-80af05896987"), + "Octree.FilteredNode.RootId", "Octree.FilteredNode. Node id of the node to be filtered.", + Primitives.GuidDef.Id, false + ); - /// - /// - public byte[] Encode() - { - var filter = Filter.Serialize().ToString(); + /// + public static readonly Def FilteredNodeFilter = new( + new Guid("1d2298b6-df47-4170-8fc2-4bd899ea6153"), + "Octree.FilteredNode.Filter", "Octree.FilteredNode. Filter definition as UTF8-encoded JSON string.", + Primitives.StringUTF8.Id, false + ); + } - var x = ImmutableDictionary.Empty - .Add(Octree.NodeId, Id) - .Add(Defs.FilteredNodeRootId, Node.Id) - .Add(Defs.FilteredNodeFilter, filter) - ; + /// + public IPointCloudNode WriteToStore() + { + this.CheckDerivedAttributes(); + var buffer = Encode(); + Storage.Add(Id, buffer); + return this; + } - return Data.Codec.Serialize(Defs.FilteredNode, x); - } + /// + /// + public byte[] Encode() + { + var filter = Filter.Serialize().ToString(); - /// - /// - public static FilteredNode Decode(Storage storage, byte[] buffer) - { - var r = Data.Codec.Deserialize(buffer); - if (r.Item1 != Defs.FilteredNode) throw new InvalidOperationException("Invariant c03cfd90-a083-44f2-a00f-cb36b1735f37."); - var data = (ImmutableDictionary)r.Item2; - var id = (Guid)data.Get(Octree.NodeId); - var filterString = (string)data.Get(Defs.FilteredNodeFilter); - var filter = Points.Filter.Deserialize(filterString); - var rootId = (Guid)data.Get(Defs.FilteredNodeRootId); - var root = storage.GetPointCloudNode(rootId) ?? throw new InvalidOperationException("Invariant 7fd48e32-9741-4241-b68f-6a0a0d261ec8."); - return new FilteredNode(id, false, root, filter); - } + var x = ImmutableDictionary.Empty + .Add(Octree.NodeId, Id) + .Add(Defs.FilteredNodeRootId, Node.Id) + .Add(Defs.FilteredNodeFilter, filter) + ; - #endregion + return Data.Codec.Serialize(Defs.FilteredNode, x); } + + /// + /// + public static FilteredNode Decode(Storage storage, byte[] buffer) + { + var r = Data.Codec.Deserialize(buffer); + if (r.Item1 != Defs.FilteredNode) throw new InvalidOperationException("Invariant c03cfd90-a083-44f2-a00f-cb36b1735f37."); + var data = (ImmutableDictionary)r.Item2; + var id = (Guid)data.Get(Octree.NodeId); + var filterString = (string)data.Get(Defs.FilteredNodeFilter); + var filter = Points.Filter.Deserialize(filterString); + var rootId = (Guid)data.Get(Defs.FilteredNodeRootId); + var root = storage.GetPointCloudNode(rootId) ?? throw new InvalidOperationException("Invariant 7fd48e32-9741-4241-b68f-6a0a0d261ec8."); + return new FilteredNode(id, false, root, filter); + } + + #endregion } diff --git a/src/Aardvark.Geometry.PointSet/Views/Filters/Filter.cs b/src/Aardvark.Geometry.PointSet/Views/Filters/Filter.cs index f6950ab0..1eda8036 100644 --- a/src/Aardvark.Geometry.PointSet/Views/Filters/Filter.cs +++ b/src/Aardvark.Geometry.PointSet/Views/Filters/Filter.cs @@ -1,6 +1,8 @@ using System; using System.Text.Json.Nodes; +#pragma warning disable IDE0130 + namespace System.Runtime.CompilerServices { internal class IsExternalInit { } diff --git a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterBoolean.cs b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterBoolean.cs index f461c492..f8f0090f 100644 --- a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterBoolean.cs +++ b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterBoolean.cs @@ -3,111 +3,98 @@ using System.Text.Json; using System.Text.Json.Nodes; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// NOT IMPLEMENTED +/// +/// +public class FilterOr(IFilter left, IFilter right) : IFilter { - /// - /// NOT IMPLEMENTED - /// - public class FilterOr : IFilter + /// + public const string Type = "FilterOr"; + + /// + public IFilter Left { get; } = left ?? throw new ArgumentNullException(nameof(left)); + + /// + public IFilter Right { get; } = right ?? throw new ArgumentNullException(nameof(right)); + + /// + public bool IsFullyInside(IPointCloudNode node) => Left.IsFullyInside(node) || Right.IsFullyInside(node); + + /// + public bool IsFullyOutside(IPointCloudNode node) => Left.IsFullyOutside(node) || Right.IsFullyOutside(node); + + /// + public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) { - /// - public const string Type = "FilterOr"; - - /// - public IFilter Left { get; } - - /// - public IFilter Right { get; } - - /// - public FilterOr(IFilter left, IFilter right) - { - Left = left ?? throw new ArgumentNullException(nameof(left)); - Right = right ?? throw new ArgumentNullException(nameof(right)); - } - - /// - public bool IsFullyInside(IPointCloudNode node) => Left.IsFullyInside(node) || Right.IsFullyInside(node); - - /// - public bool IsFullyOutside(IPointCloudNode node) => Left.IsFullyOutside(node) || Right.IsFullyOutside(node); - - /// - public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) - { - var a = Left.FilterPoints(node, selected); - if (selected != null && a.Count == selected.Count) return a; - var b = Right.FilterPoints(node, selected); - if (selected != null && b.Count == selected.Count) return b; - a.UnionWith(b); - return a; - } - - /// - public JsonNode Serialize() => JsonSerializer.SerializeToNode(new - { - Type, - Left = Left.Serialize(), - Right = Right.Serialize() - })!; - - /// - public static FilterOr Deserialize(JsonNode json) - => new(Filter.Deserialize(json["Left"]!), Filter.Deserialize(json["Right"]!)); - - public bool Equals(IFilter other) - => other is FilterOr x && Left.Equals(x.Left) && Right.Equals(x.Right); + var a = Left.FilterPoints(node, selected); + if (selected != null && a.Count == selected.Count) return a; + var b = Right.FilterPoints(node, selected); + if (selected != null && b.Count == selected.Count) return b; + a.UnionWith(b); + return a; } - /// - /// - public class FilterAnd : IFilter + /// + public JsonNode Serialize() => JsonSerializer.SerializeToNode(new { - /// - public const string Type = "FilterAnd"; - - /// - public IFilter Left { get; } - - /// - public IFilter Right { get; } - - /// - public FilterAnd(IFilter left, IFilter right) - { - Left = left ?? throw new ArgumentNullException(nameof(left)); - Right = right ?? throw new ArgumentNullException(nameof(right)); - } - - /// - public bool IsFullyInside(IPointCloudNode node) => Left.IsFullyInside(node) && Right.IsFullyInside(node); - - /// - public bool IsFullyOutside(IPointCloudNode node) => Left.IsFullyOutside(node) && Right.IsFullyOutside(node); - - /// - public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) - { - var a = Left.FilterPoints(node, selected); - var b = Right.FilterPoints(node, selected); - if (selected != null && a.Count == selected.Count && b.Count == selected.Count) return selected; - a.IntersectWith(b); - return a; - } - - /// - public JsonNode Serialize() => JsonSerializer.SerializeToNode(new - { - Type, - Left = Left.Serialize(), - Right = Right.Serialize() - })!; - - /// - public static FilterAnd Deserialize(JsonNode json) - => new(Filter.Deserialize(json["Left"]!), Filter.Deserialize(json["Right"]!)); - - public bool Equals(IFilter other) - => other is FilterAnd x && Left.Equals(x.Left) && Right.Equals(x.Right); + Type, + Left = Left.Serialize(), + Right = Right.Serialize() + })!; + + /// + public static FilterOr Deserialize(JsonNode json) + => new(Filter.Deserialize(json["Left"]!), Filter.Deserialize(json["Right"]!)); + + public bool Equals(IFilter other) + => other is FilterOr x && Left.Equals(x.Left) && Right.Equals(x.Right); +} + +/// +/// +/// +public class FilterAnd(IFilter left, IFilter right) : IFilter +{ + /// + public const string Type = "FilterAnd"; + + /// + public IFilter Left { get; } = left ?? throw new ArgumentNullException(nameof(left)); + + /// + public IFilter Right { get; } = right ?? throw new ArgumentNullException(nameof(right)); + + /// + public bool IsFullyInside(IPointCloudNode node) => Left.IsFullyInside(node) && Right.IsFullyInside(node); + + /// + public bool IsFullyOutside(IPointCloudNode node) => Left.IsFullyOutside(node) && Right.IsFullyOutside(node); + + /// + public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) + { + var a = Left.FilterPoints(node, selected); + var b = Right.FilterPoints(node, selected); + if (selected != null && a.Count == selected.Count && b.Count == selected.Count) return selected; + a.IntersectWith(b); + return a; } + + /// + public JsonNode Serialize() => JsonSerializer.SerializeToNode(new + { + Type, + Left = Left.Serialize(), + Right = Right.Serialize() + })!; + + /// + public static FilterAnd Deserialize(JsonNode json) + => new(Filter.Deserialize(json["Left"]!), Filter.Deserialize(json["Right"]!)); + + public bool Equals(IFilter other) + => other is FilterAnd x && Left.Equals(x.Left) && Right.Equals(x.Right); } diff --git a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterBox3d.cs b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterBox3d.cs index 22cdbdbd..2e562b8f 100644 --- a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterBox3d.cs +++ b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterBox3d.cs @@ -4,137 +4,132 @@ using System.Text.Json; using System.Text.Json.Nodes; -namespace Aardvark.Geometry.Points -{ - /// - /// - public class FilterInsideBox3d : ISpatialFilter - { - /// - public const string Type = "FilterInsideBox3d"; +namespace Aardvark.Geometry.Points; - /// - public Box3d Box { get; } +/// +/// +/// +public class FilterInsideBox3d(Box3d filter) : ISpatialFilter +{ + /// + public const string Type = "FilterInsideBox3d"; - /// - public FilterInsideBox3d(Box3d filter) { Box = filter; } + /// + public Box3d Box { get; } = filter; - /// - public bool IsFullyInside(Box3d box) => Box.Contains(box); + /// + public bool IsFullyInside(Box3d box) => Box.Contains(box); - /// - public bool IsFullyOutside(Box3d box) => !Box.Intersects(box); + /// + public bool IsFullyOutside(Box3d box) => !Box.Intersects(box); - /// - public bool IsFullyInside(IPointCloudNode node) => IsFullyInside(node.BoundingBoxExactGlobal); + /// + public bool IsFullyInside(IPointCloudNode node) => IsFullyInside(node.BoundingBoxExactGlobal); - /// - public bool IsFullyOutside(IPointCloudNode node) => IsFullyOutside(node.BoundingBoxExactGlobal); - /// - public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) + /// + public bool IsFullyOutside(IPointCloudNode node) => IsFullyOutside(node.BoundingBoxExactGlobal); + /// + public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) + { + if (selected != null) { - if (selected != null) - { - var c = node.Center; - var ps = node.Positions.Value; - return new HashSet(selected.Where(i => Box.Contains(c + (V3d)ps[i]))); - } - else + var c = node.Center; + var ps = node.Positions.Value; + return new HashSet(selected.Where(i => Box.Contains(c + (V3d)ps[i]))); + } + else + { + var c = node.Center; + var ps = node.Positions.Value; + var result = new HashSet(); + for (var i = 0; i < ps.Length; i++) { - var c = node.Center; - var ps = node.Positions.Value; - var result = new HashSet(); - for (var i = 0; i < ps.Length; i++) - { - if (Box.Contains(c + (V3d)ps[i])) result.Add(i); - } - return result; + if (Box.Contains(c + (V3d)ps[i])) result.Add(i); } + return result; } + } - /// - public JsonNode Serialize() => JsonSerializer.SerializeToNode(new - { - Type, - Box = Box.ToString() - })!; + /// + public JsonNode Serialize() => JsonSerializer.SerializeToNode(new + { + Type, + Box = Box.ToString() + })!; - /// - public static FilterInsideBox3d Deserialize(JsonNode json) => new(Box3d.Parse((string)json["Box"]!)); + /// + public static FilterInsideBox3d Deserialize(JsonNode json) => new(Box3d.Parse((string)json["Box"]!)); - public Box3d Clip(Box3d box) - { - return box.Intersection(Box); - } - public bool Contains(V3d pt) => Box.Contains(pt); - - public bool Equals(IFilter other) - => other is FilterInsideBox3d x && Box == x.Box; + public Box3d Clip(Box3d box) + { + return box.Intersection(Box); } + public bool Contains(V3d pt) => Box.Contains(pt); - /// - /// - public class FilterOutsideBox3d : ISpatialFilter - { - /// - public const string Type = "FilterOutsideBox3d"; + public bool Equals(IFilter other) + => other is FilterInsideBox3d x && Box == x.Box; +} - /// - public Box3d Box { get; } +/// +/// +/// +public class FilterOutsideBox3d(Box3d filter) : ISpatialFilter +{ + /// + public const string Type = "FilterOutsideBox3d"; - /// - public FilterOutsideBox3d(Box3d filter) { Box = filter; } + /// + public Box3d Box { get; } = filter; - /// - public bool IsFullyInside(Box3d box) => !Box.Intersects(box); + /// + public bool IsFullyInside(Box3d box) => !Box.Intersects(box); - /// - public bool IsFullyOutside(Box3d box) => Box.Contains(box); + /// + public bool IsFullyOutside(Box3d box) => Box.Contains(box); - /// - public bool IsFullyInside(IPointCloudNode node) => IsFullyInside(node.BoundingBoxExactGlobal); + /// + public bool IsFullyInside(IPointCloudNode node) => IsFullyInside(node.BoundingBoxExactGlobal); - /// - public bool IsFullyOutside(IPointCloudNode node) => IsFullyOutside(node.BoundingBoxExactGlobal); + /// + public bool IsFullyOutside(IPointCloudNode node) => IsFullyOutside(node.BoundingBoxExactGlobal); - /// - public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) - { - var c = node.Center; - var xs = node.Positions.Value; + /// + public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) + { + var c = node.Center; + var xs = node.Positions.Value; - if (selected != null) - { - return new HashSet(selected.Where(i => Box.Contains(c + (V3d)xs[i]))); - } - else + if (selected != null) + { + return new HashSet(selected.Where(i => Box.Contains(c + (V3d)xs[i]))); + } + else + { + var result = new HashSet(); + for (var i = 0; i < xs.Length; i++) { - var result = new HashSet(); - for (var i = 0; i < xs.Length; i++) - { - if (Box.Contains(c + (V3d)xs[i])) result.Add(i); - } - return result; + if (Box.Contains(c + (V3d)xs[i])) result.Add(i); } + return result; } + } - /// - public JsonNode Serialize() => JsonSerializer.SerializeToNode(new - { - Type, - Box = Box.ToString() - })!; + /// + public JsonNode Serialize() => JsonSerializer.SerializeToNode(new + { + Type, + Box = Box.ToString() + })!; - /// - public static FilterOutsideBox3d Deserialize(JsonNode json) => new(Box3d.Parse((string)json["Box"]!)); - public Box3d Clip(Box3d box) - { - return box; - } + /// + public static FilterOutsideBox3d Deserialize(JsonNode json) => new(Box3d.Parse((string)json["Box"]!)); + public Box3d Clip(Box3d box) + { + return box; + } - public bool Contains(V3d pt) => !Box.Contains(pt); + public bool Contains(V3d pt) => !Box.Contains(pt); - public bool Equals(IFilter other) - => other is FilterOutsideBox3d x && Box == x.Box; - } + public bool Equals(IFilter other) + => other is FilterOutsideBox3d x && Box == x.Box; } diff --git a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterClassification.cs b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterClassification.cs index c3c56b16..5cd40bcd 100644 --- a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterClassification.cs +++ b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterClassification.cs @@ -4,103 +4,99 @@ using System.Text.Json; using System.Text.Json.Nodes; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +/// +public class FilterClassification(HashSet filter) : IFilter { - /// - /// - public class FilterClassification : IFilter + /// + public const string Type = "FilterClassification"; + + /// + public HashSet Filter { get; } = filter; + /// + public static FilterClassification AllExcept(params byte[] xs) { - /// - public const string Type = "FilterClassification"; - - /// - public HashSet Filter { get; } - - /// - public static FilterClassification AllExcept(params byte[] xs) - { - var hs = new HashSet(Enumerable.Range(0, 256).Select(x => (byte)x)); - foreach (var x in xs) hs.Remove(x); - return new FilterClassification(hs); - } + var hs = new HashSet(Enumerable.Range(0, 256).Select(x => (byte)x)); + foreach (var x in xs) hs.Remove(x); + return new FilterClassification(hs); + } - /// - public static FilterClassification AllExcept(params Range1b[] xs) - { - var hs = new HashSet(Enumerable.Range(0, 256).Select(x => (byte)x)); - foreach (var x in xs) for (var i = x.Min; i <= x.Max; i++) hs.Remove(i); - return new FilterClassification(hs); - } + /// + public static FilterClassification AllExcept(params Range1b[] xs) + { + var hs = new HashSet(Enumerable.Range(0, 256).Select(x => (byte)x)); + foreach (var x in xs) for (var i = x.Min; i <= x.Max; i++) hs.Remove(i); + return new FilterClassification(hs); + } - /// - public FilterClassification(params byte[] filter) : this(new HashSet(filter)) { } + /// + public FilterClassification(params byte[] filter) : this(new HashSet(filter)) { } - /// - public FilterClassification(HashSet filter) { Filter = filter; } + private byte[]? GetValues(IPointCloudNode node) => node.HasClassifications ? node.Classifications.Value : null; - private byte[]? GetValues(IPointCloudNode node) => node.HasClassifications ? node.Classifications.Value : null; + /// + public bool IsFullyInside(IPointCloudNode node) + { + var xs = GetValues(node); + if (xs == null) return true; - /// - public bool IsFullyInside(IPointCloudNode node) + for (var i = 0; i < xs.Length; i++) { - var xs = GetValues(node); - if (xs == null) return true; + if (!Filter.Contains(xs[i])) return false; + } - for (var i = 0; i < xs.Length; i++) - { - if (!Filter.Contains(xs[i])) return false; - } + return true; + } - return true; - } + /// + public bool IsFullyOutside(IPointCloudNode node) + { + var xs = GetValues(node); + if (xs == null) return false; - /// - public bool IsFullyOutside(IPointCloudNode node) + for (var i = 0; i < xs.Length; i++) { - var xs = GetValues(node); - if (xs == null) return false; + if (Filter.Contains(xs[i])) return false; + } - for (var i = 0; i < xs.Length; i++) - { - if (Filter.Contains(xs[i])) return false; - } + return true; + } - return true; - } + /// + public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) + { + var xs = GetValues(node); + if (xs == null) return []; - /// - public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) + if (selected != null) { - var xs = GetValues(node); - if (xs == null) return new(); - - if (selected != null) - { - return new HashSet(selected.Where(i => Filter.Contains(xs[i]))); - } - else + return new HashSet(selected.Where(i => Filter.Contains(xs[i]))); + } + else + { + var result = new HashSet(); + for (var i = 0; i < xs.Length; i++) { - var result = new HashSet(); - for (var i = 0; i < xs.Length; i++) - { - if (Filter.Contains(xs[i])) result.Add(i); - } - return result; + if (Filter.Contains(xs[i])) result.Add(i); } + return result; } + } - /// - public JsonNode Serialize() => JsonSerializer.SerializeToNode(new - { - Type, - Filter = Filter.Select(x => (int)x).ToArray() - })!; + /// + public JsonNode Serialize() => JsonSerializer.SerializeToNode(new + { + Type, + Filter = Filter.Select(x => (int)x).ToArray() + })!; - /// - public static FilterClassification Deserialize(JsonNode json) - => new(new HashSet(json["Filter"].Deserialize().Select(x => (byte)x))); + /// + public static FilterClassification Deserialize(JsonNode json) + => new(new HashSet(json["Filter"].Deserialize().Select(x => (byte)x))); - public bool Equals(IFilter other) - => other is FilterClassification x && Filter.SetEquals(x.Filter); - } + public bool Equals(IFilter other) + => other is FilterClassification x && Filter.SetEquals(x.Filter); } diff --git a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterHull3d.cs b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterHull3d.cs index d203b7a9..05f252b8 100644 --- a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterHull3d.cs +++ b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterHull3d.cs @@ -5,75 +5,72 @@ using System.Text.Json; using System.Text.Json.Nodes; -namespace Aardvark.Geometry.Points -{ - /// - /// - public class FilterInsideConvexHull3d : ISpatialFilter - { - /// - public const string Type = "FilterInsideConvexHull3d"; +namespace Aardvark.Geometry.Points; - /// - public Hull3d Hull { get; } +/// +/// +/// +public class FilterInsideConvexHull3d(Hull3d filter) : ISpatialFilter +{ + /// + public const string Type = "FilterInsideConvexHull3d"; - /// - public FilterInsideConvexHull3d(Hull3d filter) { Hull = filter; } + /// + public Hull3d Hull { get; } = filter; - /// - public bool IsFullyInside(Box3d box) => Hull.Contains(box); + /// + public bool IsFullyInside(Box3d box) => Hull.Contains(box); - /// - public bool IsFullyOutside(Box3d box) => !Hull.Intersects(box); + /// + public bool IsFullyOutside(Box3d box) => !Hull.Intersects(box); - /// - public bool IsFullyInside(IPointCloudNode node) => IsFullyInside(node.BoundingBoxExactGlobal); + /// + public bool IsFullyInside(IPointCloudNode node) => IsFullyInside(node.BoundingBoxExactGlobal); - /// - public bool IsFullyOutside(IPointCloudNode node) => IsFullyOutside(node.BoundingBoxExactGlobal); + /// + public bool IsFullyOutside(IPointCloudNode node) => IsFullyOutside(node.BoundingBoxExactGlobal); - /// - public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) + /// + public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) + { + if (selected != null) { - if (selected != null) - { - var c = node.Center; - var ps = node.Positions.Value; - return new HashSet(selected.Where(i => Hull.Contains(c + (V3d)ps[i]))); - } - else + var c = node.Center; + var ps = node.Positions.Value; + return new HashSet(selected.Where(i => Hull.Contains(c + (V3d)ps[i]))); + } + else + { + var c = node.Center; + var ps = node.Positions.Value; + + var result = new HashSet(); + for (var i = 0; i < ps.Length; i++) { - var c = node.Center; - var ps = node.Positions.Value; - - var result = new HashSet(); - for (var i = 0; i < ps.Length; i++) - { - if (Hull.Contains(c + (V3d)ps[i])) result.Add(i); - } - return result; + if (Hull.Contains(c + (V3d)ps[i])) result.Add(i); } + return result; } + } - /// - public JsonNode Serialize() => JsonSerializer.SerializeToNode(new - { - Type, - Array = Hull.PlaneArray.Map(p => new { Point = p.Point.ToString(), Normal = p.Normal.ToString() }) - })!; + /// + public JsonNode Serialize() => JsonSerializer.SerializeToNode(new + { + Type, + Array = Hull.PlaneArray.Map(p => new { Point = p.Point.ToString(), Normal = p.Normal.ToString() }) + })!; - public static FilterInsideConvexHull3d Deserialize(JsonNode json) - { - var arr = (JsonArray)json["Array"]!; - var planes = arr.Map(jt => new Plane3d(V3d.Parse((string)jt!["Normal"]!), V3d.Parse((string)jt!["Point"]!))); - var hull = new Hull3d(planes); - return new FilterInsideConvexHull3d(hull); - } + public static FilterInsideConvexHull3d Deserialize(JsonNode json) + { + var arr = (JsonArray)json["Array"]!; + var planes = arr.Map(jt => new Plane3d(V3d.Parse((string)jt!["Normal"]!), V3d.Parse((string)jt!["Point"]!))); + var hull = new Hull3d(planes); + return new FilterInsideConvexHull3d(hull); + } - public Box3d Clip(Box3d box) => Hull.IntersectionBounds(box); - public bool Contains(V3d pt) => Hull.Contains(pt); + public Box3d Clip(Box3d box) => Hull.IntersectionBounds(box); + public bool Contains(V3d pt) => Hull.Contains(pt); - public bool Equals(IFilter other) - => other is FilterInsideConvexHull3d x && Hull.PlaneCount == x.Hull.PlaneCount && Hull.PlaneArray.ZipPairs(x.Hull.PlaneArray).All(p => p.Item1 == p.Item2); - } + public bool Equals(IFilter other) + => other is FilterInsideConvexHull3d x && Hull.PlaneCount == x.Hull.PlaneCount && Hull.PlaneArray.ZipPairs(x.Hull.PlaneArray).All(p => p.Item1 == p.Item2); } diff --git a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterHulls3d.cs b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterHulls3d.cs index 95ade72b..2affafa3 100644 --- a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterHulls3d.cs +++ b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterHulls3d.cs @@ -5,129 +5,128 @@ using System.Text.Json; using System.Text.Json.Nodes; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public class FilterInsideConvexHulls3d : ISpatialFilter { - /// - /// - public class FilterInsideConvexHulls3d : ISpatialFilter - { - /// - public const string Type = "FilterInsideConvexHulls3d"; + /// + public const string Type = "FilterInsideConvexHulls3d"; - /// - public Hull3d[] Hulls { get; } + /// + public Hull3d[] Hulls { get; } - /// - public FilterInsideConvexHulls3d(params Hull3d[] filter) { Hulls = filter; } + /// + public FilterInsideConvexHulls3d(params Hull3d[] filter) { Hulls = filter; } - /// - public FilterInsideConvexHulls3d(IEnumerable filter) { Hulls = filter.ToArray(); } + /// + public FilterInsideConvexHulls3d(IEnumerable filter) { Hulls = filter.ToArray(); } - public FilterInsideConvexHulls3d(Polygon2d footprint, Range1d zRange, Trafo3d trafo) - { - var basePoly = footprint; - if (basePoly.PointCount < 3) { throw new ArgumentException("Footprint must contain at least 3 points."); } - var inter = basePoly.HasSelfIntersections(1E-8); - if (inter == 0) { throw new ArgumentException("Footprint can not contain two identical points."); } - else if (inter == -1) { throw new ArgumentException("Footprint can not have self-intersections."); } - if(!basePoly.IsCcw()) { basePoly.Reverse(); } - Hulls = basePoly.ComputeNonConcaveSubPolygons(1E-8).ToArray().Map(arr => { - var poly = new Polygon2d(arr.Map(i => basePoly[i])); - if (!poly.IsCcw()) poly.Reverse(); - var planes = - poly.GetEdgeLineArray().Map(l => { - var dir = (l.P1 - l.P0).Normalized; - var n = new V3d(dir.Y, -dir.X, 0); - return new Plane3d(n, new V3d(l.P0, 0)).Transformed(trafo); - }).Append(new[] { - new Plane3d(V3d.OON,zRange.Min).Transformed(trafo), - new Plane3d(V3d.OOI,zRange.Max).Transformed(trafo) - }); - return new Hull3d(planes); - }); - } + public FilterInsideConvexHulls3d(Polygon2d footprint, Range1d zRange, Trafo3d trafo) + { + var basePoly = footprint; + if (basePoly.PointCount < 3) { throw new ArgumentException("Footprint must contain at least 3 points."); } + var inter = basePoly.HasSelfIntersections(1E-8); + if (inter == 0) { throw new ArgumentException("Footprint can not contain two identical points."); } + else if (inter == -1) { throw new ArgumentException("Footprint can not have self-intersections."); } + if(!basePoly.IsCcw()) { basePoly.Reverse(); } + Hulls = basePoly.ComputeNonConcaveSubPolygons(1E-8).ToArray().Map(arr => { + var poly = new Polygon2d(arr.Map(i => basePoly[i])); + if (!poly.IsCcw()) poly.Reverse(); + var planes = + poly.GetEdgeLineArray().Map(l => { + var dir = (l.P1 - l.P0).Normalized; + var n = new V3d(dir.Y, -dir.X, 0); + return new Plane3d(n, new V3d(l.P0, 0)).Transformed(trafo); + }).Append([ + new Plane3d(V3d.OON,zRange.Min).Transformed(trafo), + new Plane3d(V3d.OOI,zRange.Max).Transformed(trafo) + ]); + return new Hull3d(planes); + }); + } - /// - public bool IsFullyInside(Box3d box) - { - return Hulls.Any(h => h.Contains(box)); - //var boxhull = new Hull3d(box); - //for(int i = 0; i - // { - // for (int j = 0; j < Hulls.Length; j++) - // { - // if (j != i && Hulls[j].Contains(c)) return true; - // } - // return false; - // }); - // if (!interior) return false; - //} - - //return Contains(box.Min); + /// + public bool IsFullyInside(Box3d box) + { + return Hulls.Any(h => h.Contains(box)); + //var boxhull = new Hull3d(box); + //for(int i = 0; i + // { + // for (int j = 0; j < Hulls.Length; j++) + // { + // if (j != i && Hulls[j].Contains(c)) return true; + // } + // return false; + // }); + // if (!interior) return false; + //} + + //return Contains(box.Min); - } + } - /// - public bool IsFullyOutside(Box3d box) => Hulls.All(h => !h.Intersects(box)); + /// + public bool IsFullyOutside(Box3d box) => Hulls.All(h => !h.Intersects(box)); - /// - public bool IsFullyInside(IPointCloudNode node) => IsFullyInside(node.BoundingBoxExactGlobal); + /// + public bool IsFullyInside(IPointCloudNode node) => IsFullyInside(node.BoundingBoxExactGlobal); - /// - public bool IsFullyOutside(IPointCloudNode node) => IsFullyOutside(node.BoundingBoxExactGlobal); + /// + public bool IsFullyOutside(IPointCloudNode node) => IsFullyOutside(node.BoundingBoxExactGlobal); - /// - public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) + /// + public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) + { + if (selected != null) { - if (selected != null) - { - var c = node.Center; - var ps = node.Positions.Value; - return new HashSet(selected.Where(i => Contains(c + (V3d)ps[i]))); - } - else + var c = node.Center; + var ps = node.Positions.Value; + return new HashSet(selected.Where(i => Contains(c + (V3d)ps[i]))); + } + else + { + var c = node.Center; + var ps = node.Positions.Value; + + var result = new HashSet(); + for (var i = 0; i < ps.Length; i++) { - var c = node.Center; - var ps = node.Positions.Value; - - var result = new HashSet(); - for (var i = 0; i < ps.Length; i++) - { - if (Contains(c + (V3d)ps[i])) result.Add(i); - } - return result; + if (Contains(c + (V3d)ps[i])) result.Add(i); } + return result; } + } - /// - public JsonNode Serialize() => JsonSerializer.SerializeToNode(new - { - Type, - Array = Hulls.Map(h => h.PlaneArray.Map(p => new { Point = p.Point.ToString(), Normal = p.Normal.ToString() })) - })!; + /// + public JsonNode Serialize() => JsonSerializer.SerializeToNode(new + { + Type, + Array = Hulls.Map(h => h.PlaneArray.Map(p => new { Point = p.Point.ToString(), Normal = p.Normal.ToString() })) + })!; - public static FilterInsideConvexHulls3d Deserialize(JsonNode json) - { - var arr = (JsonArray)json["Array"]!; - var hulls = arr.Map(jts => new Hull3d(((JsonArray)jts!).Map(jt => new Plane3d(V3d.Parse((string)jt!["Normal"]!), V3d.Parse((string)jt["Point"]!))))); - return new FilterInsideConvexHulls3d(hulls); - } + public static FilterInsideConvexHulls3d Deserialize(JsonNode json) + { + var arr = (JsonArray)json["Array"]!; + var hulls = arr.Map(jts => new Hull3d(((JsonArray)jts!).Map(jt => new Plane3d(V3d.Parse((string)jt!["Normal"]!), V3d.Parse((string)jt["Point"]!))))); + return new FilterInsideConvexHulls3d(hulls); + } - public Box3d Clip(Box3d box) => new(Hulls.Map(h => h.IntersectionBounds(box))); + public Box3d Clip(Box3d box) => new(Hulls.Map(h => h.IntersectionBounds(box))); - public bool Contains(V3d point) => Hulls.Any(h =>h.Contains(point)); + public bool Contains(V3d point) => Hulls.Any(h =>h.Contains(point)); - public bool Equals(IFilter other) - => other is FilterInsideConvexHulls3d x && - x.Hulls.Length == Hulls.Length && - Hulls.ZipPairs(x.Hulls).All(tup => tup.Item1.PlaneArray.ZipPairs(tup.Item2.PlaneArray).All(p => p.Item1.ApproximateEquals(p.Item2, 1e-9))); - } + public bool Equals(IFilter other) + => other is FilterInsideConvexHulls3d x && + x.Hulls.Length == Hulls.Length && + Hulls.ZipPairs(x.Hulls).All(tup => tup.Item1.PlaneArray.ZipPairs(tup.Item2.PlaneArray).All(p => p.Item1.ApproximateEquals(p.Item2, 1e-9))); } diff --git a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterIntensity.cs b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterIntensity.cs index 168d2592..81fb2e01 100644 --- a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterIntensity.cs +++ b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterIntensity.cs @@ -4,57 +4,54 @@ using System.Text.Json; using System.Text.Json.Nodes; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +/// +public class FilterIntensity(Range1i range) : IFilter { - /// - /// - public class FilterIntensity : IFilter - { - /// - public const string Type = "FilterIntensity"; + /// + public const string Type = "FilterIntensity"; - /// - public Range1i Range { get; } + /// + public Range1i Range { get; } = range; - /// - public FilterIntensity(Range1i range) { Range = range; } + private int[]? GetValues(IPointCloudNode node) => node.HasIntensities ? node.Intensities.Value : null; - private int[]? GetValues(IPointCloudNode node) => node.HasIntensities ? node.Intensities.Value : null; + /// + public bool IsFullyInside(IPointCloudNode node) => false; - /// - public bool IsFullyInside(IPointCloudNode node) => false; + /// + public bool IsFullyOutside(IPointCloudNode node) => false; - /// - public bool IsFullyOutside(IPointCloudNode node) => false; + /// + public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) + { + var xs = GetValues(node); + if (xs == null) return []; - /// - public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) + if (selected != null) { - var xs = GetValues(node); - if (xs == null) return new(); - - if (selected != null) - { - return new HashSet(selected.Where(i => Range.Contains(xs[i]))); - } - else + return new HashSet(selected.Where(i => Range.Contains(xs[i]))); + } + else + { + var result = new HashSet(); + for (var i = 0; i < xs.Length; i++) { - var result = new HashSet(); - for (var i = 0; i < xs.Length; i++) - { - if (Range.Contains(xs[i])) result.Add(i); - } - return result; + if (Range.Contains(xs[i])) result.Add(i); } + return result; } + } - /// - public JsonNode Serialize() => JsonSerializer.SerializeToNode(new { Type, Range = Range.ToString() })!; + /// + public JsonNode Serialize() => JsonSerializer.SerializeToNode(new { Type, Range = Range.ToString() })!; - /// - public static FilterIntensity Deserialize(JsonNode json) => new(Range1i.Parse((string)json["Range"]!)); + /// + public static FilterIntensity Deserialize(JsonNode json) => new(Range1i.Parse((string)json["Range"]!)); - public bool Equals(IFilter other) - => other is FilterIntensity x && Range == x.Range; - } + public bool Equals(IFilter other) + => other is FilterIntensity x && Range == x.Range; } diff --git a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterNormals.cs b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterNormals.cs index 2b813a90..c9773596 100644 --- a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterNormals.cs +++ b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterNormals.cs @@ -5,73 +5,72 @@ using System.Text.Json; using System.Text.Json.Nodes; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public class FilterNormalDirection : IFilter { - /// - /// - public class FilterNormalDirection : IFilter - { - /// - public const string Type = "FilterNormalDirection"; + /// + public const string Type = "FilterNormalDirection"; - /// - public V3f Direction { get; } - - /// - public float EpsInDegrees { get; } + /// + public V3f Direction { get; } + + /// + public float EpsInDegrees { get; } - /// - public FilterNormalDirection(V3f direction, float epsInDegrees) - { - var e = (float)Math.Sin(Conversion.RadiansFromDegrees(Fun.Clamp(epsInDegrees, 0.0, 90.0))); - Direction = direction.Normalized; - EpsInDegrees = epsInDegrees; - m_eps = e * e; - } + /// + public FilterNormalDirection(V3f direction, float epsInDegrees) + { + var e = (float)Math.Sin(Conversion.RadiansFromDegrees(Fun.Clamp(epsInDegrees, 0.0, 90.0))); + Direction = direction.Normalized; + EpsInDegrees = epsInDegrees; + m_eps = e * e; + } - private readonly float m_eps; + private readonly float m_eps; - private V3f[]? GetValues(IPointCloudNode node) => node.HasNormals ? node.Normals.Value : null; + private V3f[]? GetValues(IPointCloudNode node) => node.HasNormals ? node.Normals.Value : null; - /// - public bool IsFullyInside(IPointCloudNode node) => false; + /// + public bool IsFullyInside(IPointCloudNode node) => false; - /// - public bool IsFullyOutside(IPointCloudNode node) => false; + /// + public bool IsFullyOutside(IPointCloudNode node) => false; - /// - public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) - { - var xs = GetValues(node); - if (xs == null) return new(); + /// + public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) + { + var xs = GetValues(node); + if (xs == null) return []; - if (selected != null) - { - return new HashSet(selected.Where(i => Vec.Cross(Direction, xs[i].Normalized).LengthSquared <= m_eps)); - } - else + if (selected != null) + { + return new HashSet(selected.Where(i => Vec.Cross(Direction, xs[i].Normalized).LengthSquared <= m_eps)); + } + else + { + var result = new HashSet(); + for (var i = 0; i < xs.Length; i++) { - var result = new HashSet(); - for (var i = 0; i < xs.Length; i++) - { - if (Vec.Cross(Direction, xs[i].Normalized).LengthSquared <= m_eps) result.Add(i); - } - return result; + if (Vec.Cross(Direction, xs[i].Normalized).LengthSquared <= m_eps) result.Add(i); } + return result; } + } - /// - public JsonNode Serialize() => JsonSerializer.SerializeToNode( - new { Type, Direction = Direction.ToString(), EpsInDegrees } - )!; + /// + public JsonNode Serialize() => JsonSerializer.SerializeToNode( + new { Type, Direction = Direction.ToString(), EpsInDegrees } + )!; - /// - public static FilterNormalDirection Deserialize(JsonNode json) => new( - V3f.Parse((string)json["Direction"]!), - (float)json["EpsInDegrees"]! - ); + /// + public static FilterNormalDirection Deserialize(JsonNode json) => new( + V3f.Parse((string)json["Direction"]!), + (float)json["EpsInDegrees"]! + ); - public bool Equals(IFilter other) - => other is FilterNormalDirection x && Direction == x.Direction && EpsInDegrees == x.EpsInDegrees; - } + public bool Equals(IFilter other) + => other is FilterNormalDirection x && Direction == x.Direction && EpsInDegrees == x.EpsInDegrees; } diff --git a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterPrism3d.cs b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterPrism3d.cs index a9a59464..03a407c6 100644 --- a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterPrism3d.cs +++ b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterPrism3d.cs @@ -5,95 +5,94 @@ using System.Text.Json; using System.Text.Json.Nodes; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +public class FilterInsidePrismXY : ISpatialFilter { - public class FilterInsidePrismXY : ISpatialFilter - { - public const string Type = "FilterInsidePrismXY"; + public const string Type = "FilterInsidePrismXY"; - public PolyRegion Shape { get; } - - public Range1d ZRange { get; } + public PolyRegion Shape { get; } + + public Range1d ZRange { get; } - public FilterInsidePrismXY(PolyRegion shape, Range1d zRange) { Shape = shape; ZRange = zRange; } - - public FilterInsidePrismXY(Polygon2d shape, Range1d zRange) { Shape = new(shape); ZRange = zRange; } + public FilterInsidePrismXY(PolyRegion shape, Range1d zRange) { Shape = shape; ZRange = zRange; } + + public FilterInsidePrismXY(Polygon2d shape, Range1d zRange) { Shape = new(shape); ZRange = zRange; } - public bool IsFullyInside(Box3d box) - => box.Min.Z >= ZRange.Min && box.Max.Z <= ZRange.Max && Shape.Contains(new Box2d(box.Min.XY, box.Max.XY)); + public bool IsFullyInside(Box3d box) + => box.Min.Z >= ZRange.Min && box.Max.Z <= ZRange.Max && Shape.Contains(new Box2d(box.Min.XY, box.Max.XY)); - public bool IsFullyOutside(Box3d box) - => box.Max.Z < ZRange.Min || box.Min.Z > ZRange.Max || !Shape.Overlaps(PolyRegionModule.ofBox(new Box2d(box.Min.XY, box.Max.XY))); + public bool IsFullyOutside(Box3d box) + => box.Max.Z < ZRange.Min || box.Min.Z > ZRange.Max || !Shape.Overlaps(PolyRegionModule.ofBox(new Box2d(box.Min.XY, box.Max.XY))); - public bool IsFullyInside(IPointCloudNode node) => IsFullyInside(node.BoundingBoxExactGlobal); + public bool IsFullyInside(IPointCloudNode node) => IsFullyInside(node.BoundingBoxExactGlobal); - public bool IsFullyOutside(IPointCloudNode node) => IsFullyOutside(node.BoundingBoxExactGlobal); + public bool IsFullyOutside(IPointCloudNode node) => IsFullyOutside(node.BoundingBoxExactGlobal); - public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) + public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) + { + if (selected != null) { - if (selected != null) + var c = node.Center; + var ps = node.Positions.Value; + return new HashSet(selected.Where(i => { - var c = node.Center; - var ps = node.Positions.Value; - return new HashSet(selected.Where(i => - { - var p = c + (V3d)ps[i]; - return p.Z >= ZRange.Min && p.Z <= ZRange.Max && Shape.Contains(p.XY); - })); - } - else + var p = c + (V3d)ps[i]; + return p.Z >= ZRange.Min && p.Z <= ZRange.Max && Shape.Contains(p.XY); + })); + } + else + { + var c = node.Center; + var ps = node.Positions.Value; + + var result = new HashSet(); + for (var i = 0; i < ps.Length; i++) { - var c = node.Center; - var ps = node.Positions.Value; - - var result = new HashSet(); - for (var i = 0; i < ps.Length; i++) - { - var p = c + (V3d)ps[i]; - if (p.Z >= ZRange.Min && p.Z <= ZRange.Max && Shape.Contains(p.XY)) result.Add(i); - } - return result; + var p = c + (V3d)ps[i]; + if (p.Z >= ZRange.Min && p.Z <= ZRange.Max && Shape.Contains(p.XY)) result.Add(i); } + return result; } + } - public Box3d Clip(Box3d box) - { - var bound2d = PolyRegion.Intersection(Shape, PolyRegionModule.ofBox(new Box2d(box.Min.XY, box.Max.XY))).BoundingBox; - var bound3d = new Box3d( - new V3d(bound2d.Min, Math.Max(ZRange.Min, box.Min.Z)), - new V3d(bound2d.Max, Math.Min(ZRange.Max, box.Max.Z)) - ); - return bound3d; - } + public Box3d Clip(Box3d box) + { + var bound2d = PolyRegion.Intersection(Shape, PolyRegionModule.ofBox(new Box2d(box.Min.XY, box.Max.XY))).BoundingBox; + var bound3d = new Box3d( + new V3d(bound2d.Min, Math.Max(ZRange.Min, box.Min.Z)), + new V3d(bound2d.Max, Math.Min(ZRange.Max, box.Max.Z)) + ); + return bound3d; + } - public bool Contains(V3d pt) => pt.Z >= ZRange.Min && pt.Z <= ZRange.Max && Shape.Contains(pt.XY); + public bool Contains(V3d pt) => pt.Z >= ZRange.Min && pt.Z <= ZRange.Max && Shape.Contains(pt.XY); - public bool Equals(IFilter other) - => other is FilterInsidePrismXY x && Shape.Polygons.ZipPairs(x.Shape.Polygons).All(p => p.Item1 == p.Item2) && x.ZRange == ZRange; + public bool Equals(IFilter other) + => other is FilterInsidePrismXY x && Shape.Polygons.ZipPairs(x.Shape.Polygons).All(p => p.Item1 == p.Item2) && x.ZRange == ZRange; - #region Serialization + #region Serialization - private record Dto(string Type, V2d[][] Shape, double[] Range) - { - public Dto() : this(FilterInsidePrismXY.Type, Array.Empty(), Array.Empty()) { } - public Dto(FilterInsidePrismXY x) : this( - FilterInsidePrismXY.Type, - x.Shape.Polygons.Select(x => x.GetPointArray()).ToArray(), - new[] { x.ZRange.Min, x.ZRange.Max } - ) - { } - } - private Dto ToDto() => new(this); - private static FilterInsidePrismXY FromDto(Dto dto) => new( - new PolyRegion(new Polygon2d(dto.Shape[0].Map(p => new V2d(p[0], p[1])))), - new Range1d(dto.Range[0], dto.Range[1]) - ); + private record Dto(string Type, V2d[][] Shape, double[] Range) + { + public Dto() : this(FilterInsidePrismXY.Type, [], []) { } + public Dto(FilterInsidePrismXY x) : this( + FilterInsidePrismXY.Type, + x.Shape.Polygons.Select(x => x.GetPointArray()).ToArray(), + [x.ZRange.Min, x.ZRange.Max] + ) + { } + } + private Dto ToDto() => new(this); + private static FilterInsidePrismXY FromDto(Dto dto) => new( + new PolyRegion(new Polygon2d(dto.Shape[0].Map(p => new V2d(p[0], p[1])))), + new Range1d(dto.Range[0], dto.Range[1]) + ); - public JsonNode Serialize() => JsonSerializer.SerializeToNode(ToDto())!; + public JsonNode Serialize() => JsonSerializer.SerializeToNode(ToDto())!; - public static FilterInsidePrismXY Deserialize(JsonNode json) - => FromDto(JsonSerializer.Deserialize(json)!); + public static FilterInsidePrismXY Deserialize(JsonNode json) + => FromDto(JsonSerializer.Deserialize(json)!); - #endregion - } + #endregion } diff --git a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterSphere3d.cs b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterSphere3d.cs index 4d3e8d51..4228b869 100644 --- a/src/Aardvark.Geometry.PointSet/Views/Filters/FilterSphere3d.cs +++ b/src/Aardvark.Geometry.PointSet/Views/Filters/FilterSphere3d.cs @@ -4,100 +4,93 @@ using System.Text.Json; using System.Text.Json.Nodes; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +public static class BoxHullThings { - public static class BoxHullThings + public static Box3d IntersectionBounds(this Hull3d hull, Box3d box) { - public static Box3d IntersectionBounds(this Hull3d hull, Box3d box) - { - if (box.IsInvalid) return box; - - var bh = new Hull3d(box); - var pp = new Plane3d[6 + hull.PlaneCount]; - bh.PlaneArray.CopyTo(pp, 0); - hull.PlaneArray.CopyTo(pp, 6); - var h = new Hull3d(pp); - return new Box3d(h.ComputeCorners()); - } - + if (box.IsInvalid) return box; + + var bh = new Hull3d(box); + var pp = new Plane3d[6 + hull.PlaneCount]; + bh.PlaneArray.CopyTo(pp, 0); + hull.PlaneArray.CopyTo(pp, 6); + var h = new Hull3d(pp); + return new Box3d(h.ComputeCorners()); } +} + +/// +/// +public class FilterInsideSphere3d(Sphere3d sphere) : ISpatialFilter +{ /// - public class FilterInsideSphere3d : ISpatialFilter - { - /// - public const string Type = "FilterInsideSphere3d"; + public const string Type = "FilterInsideSphere3d"; - public Sphere3d Sphere { get; } + public Sphere3d Sphere { get; } = sphere; - private readonly double m_radiusSquared; + private readonly double m_radiusSquared = sphere.RadiusSquared; - public bool Contains(V3d pt) - { - return Vec.DistanceSquared(Sphere.Center, pt) <= m_radiusSquared; - } + public bool Contains(V3d pt) + { + return Vec.DistanceSquared(Sphere.Center, pt) <= m_radiusSquared; + } - /// - public FilterInsideSphere3d(Sphere3d sphere) + /// + public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) + { + if (selected != null) { - Sphere = sphere; - m_radiusSquared = sphere.RadiusSquared; + var c = node.Center; + var ps = node.Positions.Value; + return new HashSet(selected.Where(i => Contains(c + (V3d)ps[i]))); } - - /// - public HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null) + else { - if (selected != null) - { - var c = node.Center; - var ps = node.Positions.Value; - return new HashSet(selected.Where(i => Contains(c + (V3d)ps[i]))); - } - else + var c = node.Center; + var ps = node.Positions.Value; + var result = new HashSet(); + for (var i = 0; i < ps.Length; i++) { - var c = node.Center; - var ps = node.Positions.Value; - var result = new HashSet(); - for (var i = 0; i < ps.Length; i++) - { - if (Contains(c + (V3d)ps[i])) result.Add(i); - } - return result; + if (Contains(c + (V3d)ps[i])) result.Add(i); } + return result; } + } - /// - public bool IsFullyInside(Box3d box) - { - return box.ComputeCorners().TrueForAll(Contains); - } - /// - public bool IsFullyInside(IPointCloudNode node) - { - return IsFullyInside(node.BoundingBoxExactGlobal); - } + /// + public bool IsFullyInside(Box3d box) + { + return box.ComputeCorners().TrueForAll(Contains); + } + /// + public bool IsFullyInside(IPointCloudNode node) + { + return IsFullyInside(node.BoundingBoxExactGlobal); + } - /// - public bool IsFullyOutside(Box3d box) - { - return !box.Intersects(Sphere); - } + /// + public bool IsFullyOutside(Box3d box) + { + return !box.Intersects(Sphere); + } - /// - public bool IsFullyOutside(IPointCloudNode node) - { - return IsFullyOutside(node.BoundingBoxExactGlobal); - } + /// + public bool IsFullyOutside(IPointCloudNode node) + { + return IsFullyOutside(node.BoundingBoxExactGlobal); + } - /// - public JsonNode Serialize() => JsonSerializer.SerializeToNode(new { Type, Sphere = Sphere.ToString() })!; + /// + public JsonNode Serialize() => JsonSerializer.SerializeToNode(new { Type, Sphere = Sphere.ToString() })!; - /// - public static FilterInsideSphere3d Deserialize(JsonNode json) => new(Sphere3d.Parse((string)json["Sphere"]!)); + /// + public static FilterInsideSphere3d Deserialize(JsonNode json) => new(Sphere3d.Parse((string)json["Sphere"]!)); - public Box3d Clip(Box3d box) => Sphere.BoundingBox3d.Intersection(box); + public Box3d Clip(Box3d box) => Sphere.BoundingBox3d.Intersection(box); - public bool Equals(IFilter other) - => other is FilterInsideSphere3d x && Sphere == x.Sphere; - } + public bool Equals(IFilter other) + => other is FilterInsideSphere3d x && Sphere == x.Sphere; } diff --git a/src/Aardvark.Geometry.PointSet/Views/Filters/IFilter.cs b/src/Aardvark.Geometry.PointSet/Views/Filters/IFilter.cs index ef25af3e..6b754b46 100644 --- a/src/Aardvark.Geometry.PointSet/Views/Filters/IFilter.cs +++ b/src/Aardvark.Geometry.PointSet/Views/Filters/IFilter.cs @@ -16,38 +16,37 @@ You should have received a copy of the GNU Affero General Public License using System.Collections.Generic; using System.Text.Json.Nodes; -namespace Aardvark.Geometry.Points +namespace Aardvark.Geometry.Points; + +/// +/// +public interface IFilter : IEquatable { - /// - /// - public interface IFilter : IEquatable - { - /// - bool IsFullyInside(IPointCloudNode node); + /// + bool IsFullyInside(IPointCloudNode node); - /// - bool IsFullyOutside(IPointCloudNode node); + /// + bool IsFullyOutside(IPointCloudNode node); - /// - /// Computes indices of selected/visible points, starting from already selected points. - /// If 'selected' is null, then ALL points are selected to begin with. - /// - HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null); + /// + /// Computes indices of selected/visible points, starting from already selected points. + /// If 'selected' is null, then ALL points are selected to begin with. + /// + HashSet FilterPoints(IPointCloudNode node, HashSet? selected = null); - /// - JsonNode Serialize(); - } + /// + JsonNode Serialize(); +} - public interface ISpatialFilter : IFilter - { - /// - bool IsFullyInside(Box3d box); +public interface ISpatialFilter : IFilter +{ + /// + bool IsFullyInside(Box3d box); - /// - bool IsFullyOutside(Box3d box); + /// + bool IsFullyOutside(Box3d box); - bool Contains(V3d pt); + bool Contains(V3d pt); - Box3d Clip(Box3d box); - } + Box3d Clip(Box3d box); } diff --git a/src/Aardvark.Rendering.PointSet/LodTreeInstance.fs b/src/Aardvark.Rendering.PointSet/LodTreeInstance.fs index a935902e..71e34822 100644 --- a/src/Aardvark.Rendering.PointSet/LodTreeInstance.fs +++ b/src/Aardvark.Rendering.PointSet/LodTreeInstance.fs @@ -24,7 +24,7 @@ module LodTreeInstance = open Aardvark.Data.Points.Import let private tokens = - LookupTable.lookupTable [ + LookupTable.lookup [ "x", Ascii.Token.PositionX "y", Ascii.Token.PositionY "z", Ascii.Token.PositionZ @@ -729,7 +729,7 @@ module LodTreeInstance = member x.Build() = let cnt = ref 0 x.Build(cnt) - !cnt + cnt.Value interface ILodTreeNode with