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