From 7fb98c93e6fe23b3ce1c01889f71c35ef4cec8e5 Mon Sep 17 00:00:00 2001 From: Stefan Maierhofer Date: Tue, 10 Oct 2023 09:59:02 +0200 Subject: [PATCH] add PartIndexRange to octree nodes (range is no longer stored in PointSet, but just forwarded from root node) --- src/Aardvark.Algodat.Tests/ImportTests.cs | 11 +- src/Aardvark.Algodat.Tests/LodTests.cs | 33 ++++- .../PartIndicesTests.cs | 4 +- src/Aardvark.Algodat.Tests/PointSetTests.cs | 70 +++++----- .../PartIndexUtils.cs | 26 +++- .../Import/ImportChunks.cs | 12 +- .../Import/MapReduce.cs | 4 +- .../Octrees/IPointCloudNode.cs | 20 +++ .../Octrees/InMemoryPointSet.cs | 121 +++++++++--------- src/Aardvark.Geometry.PointSet/Octrees/Lod.cs | 22 ++-- .../Octrees/PointSet.cs | 60 +++------ .../Octrees/PointSetNode.cs | 19 ++- src/Aardvark.Geometry.PointSet/Utils/Codec.cs | 12 ++ .../Utils/StorageExtensions.cs | 3 +- .../Views/FilteredNode.cs | 89 +++++++++++-- 15 files changed, 320 insertions(+), 186 deletions(-) diff --git a/src/Aardvark.Algodat.Tests/ImportTests.cs b/src/Aardvark.Algodat.Tests/ImportTests.cs index 048d09d3..690730b2 100644 --- a/src/Aardvark.Algodat.Tests/ImportTests.cs +++ b/src/Aardvark.Algodat.Tests/ImportTests.cs @@ -253,13 +253,18 @@ public void CanImport_Empty() var pointcloud = PointCloud.Chunks(Array.Empty(), config); + Assert.IsTrue(pointcloud.Root != null); + Assert.IsTrue(pointcloud.Root.Id == Guid.Empty.ToString()); + Assert.IsTrue(pointcloud.Root.Value.IsEmpty); Assert.IsTrue(pointcloud.Id == "test"); Assert.IsTrue(pointcloud.PointCount == 0); var reloaded = config.Storage.GetPointSet("test"); Assert.IsTrue(reloaded.Id == "test"); Assert.IsTrue(reloaded.PointCount == 0); - Assert.IsTrue(reloaded.HasPartIndexRange == false); + Assert.IsFalse(reloaded.HasPartIndices); + Assert.IsFalse(reloaded.HasPartIndexRange); + Assert.IsNull(reloaded.PartIndexRange); } #endregion @@ -410,7 +415,9 @@ public void CanImportPtsFile() var pointset = PointCloud.Import(filename, config); Assert.IsTrue(pointset != null); Assert.IsTrue(pointset.PointCount == 3); - Assert.IsTrue(pointset.PartIndexRange == Range1i.Invalid); + Assert.IsFalse(pointset.HasPartIndices); + Assert.IsFalse(pointset.HasPartIndexRange); + Assert.IsNull(pointset.PartIndexRange); } [Test] diff --git a/src/Aardvark.Algodat.Tests/LodTests.cs b/src/Aardvark.Algodat.Tests/LodTests.cs index 8af2cd78..d9668faf 100644 --- a/src/Aardvark.Algodat.Tests/LodTests.cs +++ b/src/Aardvark.Algodat.Tests/LodTests.cs @@ -35,7 +35,7 @@ public void LodCreationSetsLodPointCountCell() var cs = ps.Map(_ => C4b.White); var pointset = PointSet.Create( - storage, "test", ps.ToList(), cs.ToList(), null, null, null, partIndices: 42u, 5000, + storage, "test", ps.ToList(), cs.ToList(), null, null, null, null, 5000, generateLod: false, isTemporaryImportNode: true, ct: default ); pointset.Root.Value.ForEachNode(true, cell => @@ -54,6 +54,37 @@ public void LodCreationSetsLodPointCountCell() }); } + [Test] + public void LodCreationSetsPartIndices() + { + var r = new Random(); + var storage = PointSetTests.CreateStorage(); + + var ps = new V3d[42000].SetByIndex(_ => new V3d(r.NextDouble(), r.NextDouble(), r.NextDouble())); + var cs = ps.Map(_ => C4b.White); + + var pointset = PointSet.Create( + storage, "test", ps.ToList(), cs.ToList(), null, null, null, partIndices: 42, 5000, + generateLod: false, isTemporaryImportNode: true, ct: default + ); + pointset.Root.Value.ForEachNode(true, cell => + { + Assert.IsTrue(cell.IsNotLeaf() || cell.Positions != null); + }); + + var config = ImportConfig.Default + .WithKey("Test") + .WithOctreeSplitLimit(1) + ; + var lodded = pointset.GenerateLod(config); + lodded.Root.Value.ForEachNode(true, cell => + { + Assert.IsTrue(cell.HasPartIndices); + Assert.IsTrue(cell.HasPartIndexRange); + Assert.IsTrue(cell.PartIndexRange == new Range1i(42, 42)); + }); + } + [Test] public void LodPositions() { diff --git a/src/Aardvark.Algodat.Tests/PartIndicesTests.cs b/src/Aardvark.Algodat.Tests/PartIndicesTests.cs index 8d8769fa..7a2a8c8d 100644 --- a/src/Aardvark.Algodat.Tests/PartIndicesTests.cs +++ b/src/Aardvark.Algodat.Tests/PartIndicesTests.cs @@ -118,8 +118,8 @@ public void ExtendRangeBy() } [Test] - public void ExtendRangeBy_Fail() + public void ExtendRangeBy_null() { - Assert.Catch(() => PartIndexUtils.ExtendRangeBy(new Range1i(7, 11), null!)); + Assert.True(PartIndexUtils.ExtendRangeBy(new Range1i(7, 11), null) == new Range1i(7, 11)); } } diff --git a/src/Aardvark.Algodat.Tests/PointSetTests.cs b/src/Aardvark.Algodat.Tests/PointSetTests.cs index 9b682e59..c3f211f3 100644 --- a/src/Aardvark.Algodat.Tests/PointSetTests.cs +++ b/src/Aardvark.Algodat.Tests/PointSetTests.cs @@ -182,7 +182,7 @@ public void PointSetAttributes_All() var ks = new List { 42 }; var qs = new List { 17 }; var storage = PointCloud.CreateInMemoryStore(cache: default); - var pointset = PointSet.Create(storage, "test", ps, cs, ns, js, ks, qs, 1, generateLod: true, isTemporaryImportNode: false, default); + var pointset = PointSet.Create(storage, "test", ps, cs, ns, js, ks, qs, octreeSplitLimit: 1, generateLod: true, isTemporaryImportNode: false, default); Assert.IsTrue(pointset.HasColors == true); Assert.IsTrue(pointset.HasClassifications == true); Assert.IsTrue(pointset.HasIntensities == true); @@ -203,7 +203,7 @@ public void PointSetAttributes_NoLod() var ks = new List { 42 }; var qs = new List { 17 }; var storage = PointCloud.CreateInMemoryStore(cache: default); - var pointset = PointSet.Create(storage, "test", ps, cs, ns, js, ks, qs, 1, generateLod: false, isTemporaryImportNode: true, default); + var pointset = PointSet.Create(storage, "test", ps, cs, ns, js, ks, qs, octreeSplitLimit: 1, generateLod: false, isTemporaryImportNode: true, default); Assert.IsTrue(pointset.HasColors == true); Assert.IsTrue(pointset.HasClassifications == true); Assert.IsTrue(pointset.HasIntensities == true); @@ -233,45 +233,45 @@ public void PointSet_PartIndexRange_No() { var ps = new List { new(0.5, 0.5, 0.5) }; var storage = PointCloud.CreateInMemoryStore(cache: default); - var pointset = PointSet.Create(storage, "test", ps, null, null, null, null, null, 1, generateLod: false, isTemporaryImportNode: true, default); - Assert.IsTrue(pointset.PartIndexRange == Range1i.Invalid); + var pointset = PointSet.Create(storage, "test", ps, null, null, null, null, partIndices: null, 1, generateLod: false, isTemporaryImportNode: true, default); Assert.IsTrue(pointset.HasPartIndexRange == false); + Assert.IsTrue(pointset.PartIndexRange == null); } - [Test] - public void PointSet_PartIndexRange() - { - var ps = new List { new(0.5, 0.5, 0.5) }; - var storage = PointCloud.CreateInMemoryStore(cache: default); - var pointset = PointSet.Create( - storage, "test", ps, null, null, null, null, null, 1, generateLod: false, isTemporaryImportNode: true - ) - .WithPartIndexRange(new(7, 11)) - ; - Assert.IsTrue(pointset.PartIndexRange == new Range1i(7, 11)); - Assert.IsTrue(pointset.HasPartIndexRange == true); - } + //[Test] + //public void PointSet_PartIndexRange() + //{ + // var ps = new List { new(0.5, 0.5, 0.5) }; + // var storage = PointCloud.CreateInMemoryStore(cache: default); + // var pointset = PointSet.Create( + // storage, "test", ps, null, null, null, null, null, 1, generateLod: false, isTemporaryImportNode: true + // ) + // .WithPartIndexRange(new(7, 11)) + // ; + // Assert.IsTrue(pointset.PartIndexRange == new Range1i(7, 11)); + // Assert.IsTrue(pointset.HasPartIndexRange == true); + //} - [Test] - public void PointSet_PartIndexRange_Serialization() - { - var ps = new List { new(0.5, 0.5, 0.5) }; - var storage = PointCloud.CreateInMemoryStore(cache: default); + //[Test] + //public void PointSet_PartIndexRange_Serialization() + //{ + // var ps = new List { new(0.5, 0.5, 0.5) }; + // var storage = PointCloud.CreateInMemoryStore(cache: default); - var pointset = PointSet.Create( - storage, "test", ps, null, null, null, null, null, 1, generateLod: false, isTemporaryImportNode: true - ) - .WithPartIndexRange(new(7, 11)) - ; + // var pointset = PointSet.Create( + // storage, "test", ps, null, null, null, null, null, 1, generateLod: false, isTemporaryImportNode: true + // ) + // .WithPartIndexRange(new(7, 11)) + // ; - var json = pointset.ToJson(); - var reloaded = PointSet.Parse(json, storage); + // var json = pointset.ToJson(); + // var reloaded = PointSet.Parse(json, storage); - Assert.IsTrue(pointset.Id == reloaded.Id ); - Assert.IsTrue(pointset.SplitLimit == reloaded.SplitLimit ); - Assert.IsTrue(pointset.Root.Value.Id == reloaded.Root.Value.Id ); - Assert.IsTrue(pointset.PartIndexRange == reloaded.PartIndexRange); - } + // Assert.IsTrue(pointset.Id == reloaded.Id); + // Assert.IsTrue(pointset.SplitLimit == reloaded.SplitLimit); + // Assert.IsTrue(pointset.Root.Value.Id == reloaded.Root.Value.Id); + // Assert.IsTrue(pointset.PartIndexRange == reloaded.PartIndexRange); + //} [Test] public void PointSet_PartIndexRange_Serialization_NoRange() @@ -290,7 +290,7 @@ public void PointSet_PartIndexRange_Serialization_NoRange() Assert.IsTrue(pointset.SplitLimit == reloaded.SplitLimit ); Assert.IsTrue(pointset.Root.Value.Id == reloaded.Root.Value.Id ); Assert.IsTrue(pointset.PartIndexRange == reloaded.PartIndexRange); - Assert.IsTrue(reloaded.PartIndexRange.IsInvalid); + Assert.IsTrue(reloaded.PartIndexRange == null); } } } diff --git a/src/Aardvark.Data.Points.Base/PartIndexUtils.cs b/src/Aardvark.Data.Points.Base/PartIndexUtils.cs index 8abf9dd3..1189039d 100644 --- a/src/Aardvark.Data.Points.Base/PartIndexUtils.cs +++ b/src/Aardvark.Data.Points.Base/PartIndexUtils.cs @@ -262,19 +262,31 @@ object createArray4(IReadOnlyList first, IReadOnlyList second) where T return resultIndices; } - public static Range1i ExtendRangeBy(in Range1i range, object partIndices) + public static Range1i? GetRange(object? qs) => qs switch { - if (partIndices == null) throw new Exception("Invariant d781e171-41c3-4272-88a7-261cea302c18."); + null => null, + int x => new Range1i(x), + uint x => new Range1i((int)x), + byte[] xs => new Range1i(xs.Select(x => (int)x)), + short[] xs => new Range1i(xs.Select(x => (int)x)), + int[] xs => new Range1i(xs), + _ => throw new Exception($"Unexpected type {qs.GetType().FullName}. Error 8a975735-bb10-42bf-9f7f-4d570e5223e3.") + }; + + public static Range1i? ExtendRangeBy(in Range1i? range, object? partIndices) + { + if (range == null) return GetRange(partIndices); + if (partIndices == null) return range; checked { return partIndices switch { - int x => range.ExtendedBy(x), - uint x => range.ExtendedBy((int)x), - IList xs => range.ExtendedBy((Range1i)new Range1b(xs)), - IList xs => range.ExtendedBy((Range1i)new Range1s(xs)), - IList xs => range.ExtendedBy(new Range1i(xs)), + int x => range.Value.ExtendedBy(x), + uint x => range.Value.ExtendedBy((int)x), + IList xs => range.Value.ExtendedBy((Range1i)new Range1b(xs)), + IList xs => range.Value.ExtendedBy((Range1i)new Range1s(xs)), + IList xs => range.Value.ExtendedBy(new Range1i(xs)), _ => throw new Exception( $"Unexpected part indices type {partIndices.GetType().FullName}. " + diff --git a/src/Aardvark.Geometry.PointSet/Import/ImportChunks.cs b/src/Aardvark.Geometry.PointSet/Import/ImportChunks.cs index d7826897..64dc2873 100644 --- a/src/Aardvark.Geometry.PointSet/Import/ImportChunks.cs +++ b/src/Aardvark.Geometry.PointSet/Import/ImportChunks.cs @@ -74,7 +74,7 @@ public static PointSet Chunks(IEnumerable chunks, ImportConfig config) { config.ProgressCallback(0.0); - var partIndicesRange = Range1i.Invalid; + var partIndicesRange = (Range1i?)null; var chunkCount = 0; chunks = chunks.Do(chunk => { @@ -183,16 +183,12 @@ Chunk map(Chunk x, CancellationToken ct) // 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."), + storage: config.Storage ?? throw new Exception($"No storage specified. Error 5b4ebfec-d418-4ddc-9c2f-646d270cf78c."), pointSetId: key, - rootCellId: final.Root?.Value?.Id ?? Guid.Empty, + rootCellId: final.Root.Value!.Id, splitLimit: config.OctreeSplitLimit - ) - { - PartIndexRange = partIndicesRange - }; + ); config.Storage.Add(key, final); - return final; } diff --git a/src/Aardvark.Geometry.PointSet/Import/MapReduce.cs b/src/Aardvark.Geometry.PointSet/Import/MapReduce.cs index d564c61f..eecc97dd 100644 --- a/src/Aardvark.Geometry.PointSet/Import/MapReduce.cs +++ b/src/Aardvark.Geometry.PointSet/Import/MapReduce.cs @@ -86,9 +86,7 @@ public static PointSet MapReduce(this IEnumerable chunks, ImportConfig co var totalPointSetsCount = pointsets.Count; if (totalPointSetsCount == 0) { - var empty = new PointSet(config.Storage, key); - config.Storage.Add(key, empty); - return empty; + return PointSet.Empty; } var doneCount = 0; diff --git a/src/Aardvark.Geometry.PointSet/Octrees/IPointCloudNode.cs b/src/Aardvark.Geometry.PointSet/Octrees/IPointCloudNode.cs index 40338c41..ee9570c6 100644 --- a/src/Aardvark.Geometry.PointSet/Octrees/IPointCloudNode.cs +++ b/src/Aardvark.Geometry.PointSet/Octrees/IPointCloudNode.cs @@ -35,6 +35,11 @@ public interface IPointCloudNode /// bool IsMaterialized { get; } + /// + /// True, if this is Node.Empty. + /// + bool IsEmpty { get; } + /// /// Returns materialized version of this node. /// E.g. a non-materialized filtered node is converted into a PointSetNode (which is stored in Storage). @@ -146,6 +151,7 @@ public interface IPointCloudNode /// /// Durable definition aadbb622-1cf6-42e0-86df-be79d28d6757. /// + [MemberNotNullWhen(true, nameof(BoundingBoxExactLocal))] bool HasBoundingBoxExactLocal { get; } /// @@ -161,6 +167,7 @@ public interface IPointCloudNode /// /// Durable definition 7912c862-74b4-4f44-a8cd-d11ea1da9304. /// + [MemberNotNullWhen(true, nameof(BoundingBoxExactGlobal))] bool HasBoundingBoxExactGlobal { get; } /// @@ -244,14 +251,27 @@ public interface IPointCloudNode #region PartIndices + /// + /// True if this node has a PartIndexRange. + /// + [MemberNotNullWhen(true, nameof(PartIndexRange))] + bool HasPartIndexRange { get; } + + /// + /// Octree. Min and max part index in octree. + /// + Range1i? PartIndexRange { get; } + /// /// True if this node has part indices. /// + [MemberNotNullWhen(true, nameof(PartIndices))] bool HasPartIndices { get; } /// /// Octree. Per-point or per-cell part indices. /// + object? PartIndices { get; } /// diff --git a/src/Aardvark.Geometry.PointSet/Octrees/InMemoryPointSet.cs b/src/Aardvark.Geometry.PointSet/Octrees/InMemoryPointSet.cs index 7df6d1bd..1998f045 100644 --- a/src/Aardvark.Geometry.PointSet/Octrees/InMemoryPointSet.cs +++ b/src/Aardvark.Geometry.PointSet/Octrees/InMemoryPointSet.cs @@ -19,6 +19,7 @@ You should have received a copy of the GNU Affero General Public License using System.Collections.Immutable; using System.IO; using System.Linq; +using static Aardvark.Data.Durable; namespace Aardvark.Geometry.Points { @@ -27,16 +28,16 @@ public class InMemoryPointSet /// /// The following attribute arrays will be stored stand-alone and referenced via id. /// - public static Dictionary StoreAsReference { get; } = new Dictionary() + public static Dictionary StoreAsReference { get; } = new Dictionary() { - { Durable.Octree.PositionsLocal3f, Durable.Octree.PositionsLocal3fReference }, - { Durable.Octree.Colors4b, Durable.Octree.Colors4bReference }, - { Durable.Octree.Normals3f, Durable.Octree.Normals3fReference }, - { Durable.Octree.Intensities1i, Durable.Octree.Intensities1iReference }, - { Durable.Octree.Classifications1b, Durable.Octree.Classifications1bReference } + { Octree.PositionsLocal3f, Octree.PositionsLocal3fReference }, + { Octree.Colors4b, Octree.Colors4bReference }, + { Octree.Normals3f, Octree.Normals3fReference }, + { Octree.Intensities1i, Octree.Intensities1iReference }, + { Octree.Classifications1b, Octree.Classifications1bReference } }; - private readonly ImmutableDictionary m_data; + private readonly ImmutableDictionary m_data; private readonly int m_splitLimit; private readonly Node m_root; private readonly IList m_ps; @@ -54,28 +55,28 @@ public static InMemoryPointSet Build(IList ps, IList? cs, IList? { if (ps == null) throw new ArgumentNullException(nameof(ps)); - var data = ImmutableDictionary.Empty - .Add(Durable.Octree.PositionsGlobal3d, ps.ToArray()) + var data = ImmutableDictionary.Empty + .Add(Octree.PositionsGlobal3d, ps.ToArray()) ; - if (cs != null) data = data.Add(Durable.Octree.Colors4b, cs.ToArray()); - if (ns != null) data = data.Add(Durable.Octree.Normals3f, ns.ToArray()); - if (js != null) data = data.Add(Durable.Octree.Intensities1i, js.ToArray()); - if (ks != null) data = data.Add(Durable.Octree.Classifications1b, ks.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(Durable.Octree.PerCellPartIndex1i , x ); break; - case uint x : data = data.Add(Durable.Octree.PerCellPartIndex1i , (int)x ); break; - case byte[] xs: data = data.Add(Durable.Octree.PerPointPartIndex1b, xs ); break; - case IList xs: data = data.Add(Durable.Octree.PerPointPartIndex1b, xs.ToArray()); break; - case IEnumerable xs: data = data.Add(Durable.Octree.PerPointPartIndex1b, xs.ToArray()); break; - case short[] xs: data = data.Add(Durable.Octree.PerPointPartIndex1s, xs ); break; - case IList xs: data = data.Add(Durable.Octree.PerPointPartIndex1s, xs.ToArray()); break; - case IEnumerable xs: data = data.Add(Durable.Octree.PerPointPartIndex1s, xs.ToArray()); break; - case int[] xs: data = data.Add(Durable.Octree.PerPointPartIndex1i, xs ); break; - case IList xs: data = data.Add(Durable.Octree.PerPointPartIndex1i, xs.ToArray()); break; - case IEnumerable xs: data = data.Add(Durable.Octree.PerPointPartIndex1i, xs.ToArray()); 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." @@ -88,50 +89,51 @@ public static InMemoryPointSet Build(IList ps, IList? cs, IList? /// /// Constructor. /// - private InMemoryPointSet(ImmutableDictionary data, Cell cell, int octreeSplitLimit) + private InMemoryPointSet(ImmutableDictionary data, Cell cell, int octreeSplitLimit) { if (data == null) throw new ArgumentNullException(nameof(data)); foreach (var kv in data) { - if (kv.Key == Durable.Octree.PerCellPartIndex1i || - kv.Key == Durable.Octree.PerCellPartIndex1ui + if (kv.Key == Octree.PerCellPartIndex1i || + kv.Key == Octree.PerCellPartIndex1ui || + kv.Key == Octree.PartIndexRange ) continue; if (kv.Value is not Array) throw new ArgumentException($"Entry {kv.Key} must be array."); } - void TryRename(Durable.Def from, Durable.Def to) + void TryRename(Def from, Def to) { if (data.TryGetValue(from, out var obj)) { data = data.Remove(from).Add(to, obj); } } - TryRename(GenericChunk.Defs.Positions3f, Durable.Octree.PositionsGlobal3f); - TryRename(GenericChunk.Defs.Positions3d, Durable.Octree.PositionsGlobal3d); - TryRename(GenericChunk.Defs.Classifications1b, Durable.Octree.Classifications1b); - TryRename(GenericChunk.Defs.Classifications1i, Durable.Octree.Classifications1i); - TryRename(GenericChunk.Defs.Classifications1s, Durable.Octree.Classifications1s); - TryRename(GenericChunk.Defs.Colors3b, Durable.Octree.Colors3b ); - TryRename(GenericChunk.Defs.Colors4b, Durable.Octree.Colors4b ); - TryRename(GenericChunk.Defs.Intensities1i, Durable.Octree.Intensities1i ); - TryRename(GenericChunk.Defs.Intensities1f, Durable.Octree.Intensities1f ); - TryRename(GenericChunk.Defs.Normals3f, Durable.Octree.Normals3f ); + 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(Durable.Octree.PositionsGlobal3d, out var ps3d)) + if (data.TryGetValue(Octree.PositionsGlobal3d, out var ps3d)) { m_ps = (V3d[])ps3d; - data = data.Remove(Durable.Octree.PositionsGlobal3d); + data = data.Remove(Octree.PositionsGlobal3d); } - else if (data.TryGetValue(Durable.Octree.PositionsGlobal3f, out var ps3f)) + else if (data.TryGetValue(Octree.PositionsGlobal3f, out var ps3f)) { m_ps = ((V3f[])ps3f).Map(p => (V3d)p); - data = data.Remove(Durable.Octree.PositionsGlobal3f); + data = data.Remove(Octree.PositionsGlobal3f); } else { - throw new Exception("Could not find positions. Please add one of the following entries to 'data': Durable.Octree.PositionsGlobal3[df], GenericChunk.Defs.Positions3[df]."); + 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; @@ -168,7 +170,7 @@ internal PointSetNode ToPointSetNode(Storage storage, bool isTemporaryImportNode { var center = new V3d(_centerX, _centerY, _centerZ); V3f[]? localPositions = null; - var attributes = ImmutableDictionary.Empty; + var attributes = ImmutableDictionary.Empty; if (_ia != null) { @@ -178,15 +180,16 @@ internal PointSetNode ToPointSetNode(Storage storage, bool isTemporaryImportNode var allPs = _octree.m_ps; localPositions = new V3f[count]; for (var i = 0; i < count; i++) localPositions[i] = (V3f)(allPs[_ia[i]] - center); // relative to center - attributes = attributes.Add(Durable.Octree.PositionsLocal3f, localPositions); + attributes = attributes.Add(Octree.PositionsLocal3f, localPositions); // create all other attributes ... foreach (var kv in _octree.m_data) { - if (kv.Key == Durable.Octree.PositionsGlobal3d) continue; + if (kv.Key == Octree.PositionsGlobal3d) continue; - if (kv.Key == Durable.Octree.PerCellPartIndex1ui || - kv.Key == Durable.Octree.PerCellPartIndex1i + if (kv.Key == Octree.PerCellPartIndex1ui || + kv.Key == Octree.PerCellPartIndex1i || + kv.Key == Octree.PartIndexRange ) { attributes = attributes.Add(kv.Key, kv.Value); @@ -224,10 +227,10 @@ internal PointSetNode ToPointSetNode(Storage storage, bool isTemporaryImportNode : localPositions!.Length ; - var data = ImmutableDictionary.Empty - .Add(Durable.Octree.NodeId, Guid.NewGuid()) - .Add(Durable.Octree.Cell, _cell) - .Add(Durable.Octree.PointCountTreeLeafs, pointCountTreeLeafs) + var data = ImmutableDictionary.Empty + .Add(Octree.NodeId, Guid.NewGuid()) + .Add(Octree.Cell, _cell) + .Add(Octree.PointCountTreeLeafs, pointCountTreeLeafs) ; if (isTemporaryImportNode) @@ -235,21 +238,21 @@ internal PointSetNode ToPointSetNode(Storage storage, bool isTemporaryImportNode data = data.Add(PointSetNode.TemporaryImportNode, 0); } - if (attributes.TryGetValue(Durable.Octree.PositionsLocal3f, out var psObj)) + if (attributes.TryGetValue(Octree.PositionsLocal3f, out var psObj)) { var ps = (V3f[])psObj; var bbExactLocal = new Box3f(ps); data = data - .Add(Durable.Octree.PointCountCell, ps.Length) - .Add(Durable.Octree.BoundingBoxExactLocal, bbExactLocal) - .Add(Durable.Octree.BoundingBoxExactGlobal, (Box3d)bbExactLocal + center) + .Add(Octree.PointCountCell, ps.Length) + .Add(Octree.BoundingBoxExactLocal, bbExactLocal) + .Add(Octree.BoundingBoxExactGlobal, (Box3d)bbExactLocal + center) ; } else { data = data - .Add(Durable.Octree.PointCountCell, 0) + .Add(Octree.PointCountCell, 0) ; } @@ -289,8 +292,8 @@ internal PointSetNode ToPointSetNode(Storage storage, bool isTemporaryImportNode } var bbExactGlobal = new Box3d(subcells.Where(x => x != null).Select(x => x!.BoundingBoxExactGlobal)); data = data - .Add(Durable.Octree.BoundingBoxExactGlobal, bbExactGlobal) - .Add(Durable.Octree.SubnodesGuids, subcellIds.Map(x => x ?? Guid.Empty)) + .Add(Octree.BoundingBoxExactGlobal, bbExactGlobal) + .Add(Octree.SubnodesGuids, subcellIds.Map(x => x ?? Guid.Empty)) ; var result = new PointSetNode(data, storage, writeToStore: true); if (storage.GetPointCloudNode(result.Id) == null) throw new InvalidOperationException("Invariant 7b09eccb-b6a0-4b99-be7a-eeff53b6a98b."); diff --git a/src/Aardvark.Geometry.PointSet/Octrees/Lod.cs b/src/Aardvark.Geometry.PointSet/Octrees/Lod.cs index 63675132..3138515d 100644 --- a/src/Aardvark.Geometry.PointSet/Octrees/Lod.cs +++ b/src/Aardvark.Geometry.PointSet/Octrees/Lod.cs @@ -186,9 +186,10 @@ internal static Array AggregateSubArrays(int[] counts, int splitLimit, object[] /// /// /// - internal static object? AggregateSubPartIndices(int[] counts, int aggregateCount, object?[] xss) + 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]; // special case: all subnodes have identical per-cell index @@ -204,10 +205,10 @@ internal static Array AggregateSubArrays(int[] counts, int splitLimit, object[] }) .ToArray() ; - if (perCellIndices.Length == 0) return null; + 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]; + if (allIdentical) return (perCellIndices[0], PartIndexUtils.GetRange(perCellIndices[0])); } // standard case: @@ -217,6 +218,8 @@ internal static Array AggregateSubArrays(int[] counts, int splitLimit, object[] 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."), @@ -268,7 +271,7 @@ internal static Array AggregateSubArrays(int[] counts, int splitLimit, object[] result = PartIndexUtils.Compact(result); - return result; + return (result, resultRange); } private static async Task GenerateLod(this PointSet self, string? key, Action callback, CancellationToken ct) @@ -300,9 +303,9 @@ private static async Task GenerateLod(this PointSet self, string? key, /// public static PointSet GenerateLod(this PointSet self, ImportConfig config) { - if (self.Root == null) return self; + if (self.Root == null || self.Root.Value.IsEmpty) return self; - var nodeCount = self.Root?.Value?.CountNodes(true) ?? 0; + var nodeCount = self.Root.Value.CountNodes(true); var loddedNodesCount = 0L; if (config.Verbose) Console.WriteLine(); var result = self.GenerateLod(config.Key, () => @@ -473,10 +476,13 @@ void addAttributeByRef(string kind, Durable.Def def, Func x?.PartIndices)); + var (lodQs, lodQsRange) = AggregateSubPartIndices(counts, aggregateCount, subcells.Map(x => x?.PartIndices)); if (lodQs != null) { - upsertData = upsertData.Add(PartIndexUtils.GetDurableDefForPartIndices(lodQs), lodQs); + upsertData = upsertData + .Add(PartIndexUtils.GetDurableDefForPartIndices(lodQs), lodQs) + .Add(Durable.Octree.PartIndexRange, lodQsRange ?? throw new Exception($"Expected part index range to be not null. Error d355eb9f-02b3-4cd7-b1de-041d4d0e7c3c.")) + ; } // ... classifications ... diff --git a/src/Aardvark.Geometry.PointSet/Octrees/PointSet.cs b/src/Aardvark.Geometry.PointSet/Octrees/PointSet.cs index 8334ca36..8ae905cb 100644 --- a/src/Aardvark.Geometry.PointSet/Octrees/PointSet.cs +++ b/src/Aardvark.Geometry.PointSet/Octrees/PointSet.cs @@ -64,26 +64,6 @@ public static PointSet Create(Storage storage, string pointSetId, result = result.GenerateLod(config); } - checked - { - switch (partIndices) - { - case null : break; - case byte x : result = result.WithPartIndexRange(new(x, x)); break; - case short x : result = result.WithPartIndexRange(new(x, x)); break; - case int x : result = result.WithPartIndexRange(new(x, x)); break; - case uint x : result = result.WithPartIndexRange(new((int)x, (int)x)); break; - case IList xs: result = result.WithPartIndexRange(new(xs.Select(x => (int)x))); break; - case IList xs: result = result.WithPartIndexRange(new(xs.Select(x => (int)x))); break; - case IList xs: result = result.WithPartIndexRange(new(xs.Select(x => x))); break; - case IList xs: result = result.WithPartIndexRange(new(xs.Select(x => (int)x))); break; - - default: throw new Exception( - $"Unknown part indices type {partIndices.GetType().FullName}. " + - $"Error 8b6b8202-dc6c-4c2c-89a3-40f24c7eee5c." - ); - } - } return result; } @@ -96,9 +76,10 @@ public PointSet(Storage storage, string pointSetId, Guid rootCellId, int splitLi Id = pointSetId ?? throw new ArgumentNullException(nameof(pointSetId)); SplitLimit = splitLimit; - Root = new PersistentRef(rootCellId.ToString(), storage.GetPointCloudNode, - storage.TryGetPointCloudNode - ); + Root = rootCellId != Guid.Empty + ? new PersistentRef(rootCellId.ToString(), storage.GetPointCloudNode, storage.TryGetPointCloudNode) + : new PersistentRef(Guid.Empty, PointSetNode.Empty) + ; } /// @@ -112,7 +93,10 @@ public PointSet(Storage storage, string key, IPointCloudNode root, int splitLimi Id = key ?? throw new ArgumentNullException(nameof(key)); SplitLimit = splitLimit; - Root = new PersistentRef(root.Id.ToString(), storage.GetPointCloudNode, storage.TryGetPointCloudNode); + Root = root.IsEmpty + ? new PersistentRef(Guid.Empty, PointSetNode.Empty) + : new PersistentRef(root.Id.ToString(), storage.GetPointCloudNode, storage.TryGetPointCloudNode) + ; } /// @@ -143,11 +127,6 @@ public PointSet(Storage storage, string key) /// public PersistentRef Root { get; init; } - /// - /// Range (inclusive) of part indices, or invalid range if no part indices are stored. - /// - public Range1i PartIndexRange { get; init; } = Range1i.Invalid; - #endregion #region Json @@ -159,8 +138,7 @@ public JsonNode ToJson() => JsonSerializer.SerializeToNode(new Id, OctreeId = Root.Id, RootCellId = Root.Id, // backwards compatibility - SplitLimit, - PartIndexRange + SplitLimit })!; /// @@ -185,14 +163,7 @@ public static PointSet Parse(JsonNode json, Storage storage) // backwards compatibility: if split limit is not set, guess as number of points in root cell var splitLimit = o.TryGetPropertyValue("SplitLimit", out var x) ? (int)x! : 8192; - // part index range (JsonArray) - var partIndexRangeArray = (JsonArray?)o["PartIndexRange"]; - var partIndexRange = partIndexRangeArray != null - ? new Range1i((int)partIndexRangeArray[0]!, (int)partIndexRangeArray[1]!) - : Range1i.Invalid - ; - - return new PointSet(storage, id, octree ?? PointSetNode.Empty, splitLimit).WithPartIndexRange(partIndexRange); + return new PointSet(storage, id, octree ?? PointSetNode.Empty, splitLimit); } #endregion @@ -255,9 +226,12 @@ public Box3d BoundingBox /// public bool HasPositions => Root != null && Root.Value!.HasPositions; + public bool HasPartIndices => Root != null && Root.Value.HasPartIndices; - /// - public bool HasPartIndexRange => PartIndexRange.IsValid; + [MemberNotNullWhen(true, nameof(PartIndexRange))] + public bool HasPartIndexRange => Root != null && Root.Value.HasPartIndexRange; + + public Range1i? PartIndexRange => Root!.Value.PartIndexRange; #endregion @@ -283,10 +257,6 @@ public PointSet Merge(PointSet other, Action pointsMergedCallback, ImportC } } - //public PointSet WithPartIndexRange(Range1i x) => new() { Id = Id, PartIndexRange = x, Root = Root, SplitLimit = SplitLimit, Storage = Storage }; - - public PointSet WithPartIndexRange(Range1i x) => new(Storage, Id, Guid.Parse(Root.Id), SplitLimit) { PartIndexRange = x }; - #endregion } } diff --git a/src/Aardvark.Geometry.PointSet/Octrees/PointSetNode.cs b/src/Aardvark.Geometry.PointSet/Octrees/PointSetNode.cs index 07b79436..5a601352 100644 --- a/src/Aardvark.Geometry.PointSet/Octrees/PointSetNode.cs +++ b/src/Aardvark.Geometry.PointSet/Octrees/PointSetNode.cs @@ -671,7 +671,7 @@ public PersistentRef Positions return new PersistentRef(Guid.Empty, ps); } else - return null!; + return new PersistentRef(Guid.Empty, Array.Empty()); } } @@ -679,7 +679,7 @@ public PersistentRef Positions /// Point positions (absolute), or null if no positions. /// [JsonIgnore] - public V3d[] PositionsAbsolute => Positions?.Value.Map(p => new V3d(Center.X + p.X, Center.Y + p.Y, Center.Z + p.Z))!; + public V3d[] PositionsAbsolute => Positions.Value.Map(p => new V3d(Center.X + p.X, Center.Y + p.Y, Center.Z + p.Z))!; #endregion @@ -687,6 +687,7 @@ public PersistentRef Positions /// [JsonIgnore] + [MemberNotNullWhen(true, nameof(BoundingBoxExactLocal))] public bool HasBoundingBoxExactLocal => Data.ContainsKey(Durable.Octree.BoundingBoxExactLocal); /// @@ -699,6 +700,7 @@ public PersistentRef Positions /// [JsonIgnore] + [MemberNotNullWhen(true, nameof(BoundingBoxExactGlobal))] public bool HasBoundingBoxExactGlobal => Data.ContainsKey(Durable.Octree.BoundingBoxExactGlobal); /// @@ -899,6 +901,17 @@ public PersistentRef? Classifications #region PartIndices + /// + /// True if this node has a PartIndexRange. + /// + [JsonIgnore] + [MemberNotNullWhen(true, nameof(PartIndexRange))] + public bool HasPartIndexRange => Data.ContainsKey(Durable.Octree.PartIndexRange); + + /// + /// Octree. Min and max part index in octree. + /// + public Range1i? PartIndexRange => Data.TryGetValue(Durable.Octree.PartIndexRange, out var o) ? (Range1i)o : null; /// /// True if this node has part indices. @@ -1296,6 +1309,8 @@ public static PointSetNode Decode(Storage storage, byte[] buffer) /// public bool IsMaterialized => true; + public bool IsEmpty => Id == Guid.Empty; + /// public IPointCloudNode Materialize() => this; diff --git a/src/Aardvark.Geometry.PointSet/Utils/Codec.cs b/src/Aardvark.Geometry.PointSet/Utils/Codec.cs index e50efe0d..556d2782 100644 --- a/src/Aardvark.Geometry.PointSet/Utils/Codec.cs +++ b/src/Aardvark.Geometry.PointSet/Utils/Codec.cs @@ -73,6 +73,8 @@ static Codec() [Durable.Aardvark.Box3fArray.Id] = EncodeBox3fArray, [Durable.Aardvark.Box3d.Id] = EncodeBox3d, [Durable.Aardvark.Box3dArray.Id] = EncodeBox3dArray, + [Durable.Aardvark.Range1i.Id] = EncodeRange1i, + [Durable.Aardvark.Range1iArray.Id] = EncodeRange1iArray, [Durable.Aardvark.C3b.Id] = EncodeC3b, [Durable.Aardvark.C3bArray.Id] = EncodeC3bArray, @@ -135,6 +137,8 @@ static Codec() [Durable.Aardvark.Box3fArray.Id] = DecodeBox3fArray, [Durable.Aardvark.Box3d.Id] = DecodeBox3d, [Durable.Aardvark.Box3dArray.Id] = DecodeBox3dArray, + [Durable.Aardvark.Range1i.Id] = DecodeRange1i, + [Durable.Aardvark.Range1iArray.Id] = DecodeRange1iArray, [Durable.Aardvark.C3b.Id] = DecodeC3b, [Durable.Aardvark.C3bArray.Id] = DecodeC3bArray, @@ -251,6 +255,11 @@ static Codec() private static readonly Action EncodeBox3dArray = (s, o) => EncodeArray(s, (Box3d[])o); + private static readonly Action EncodeRange1i = + (s, o) => { var x = (Range1i)o; s.Write(x.Min); s.Write(x.Max); }; + private static readonly Action EncodeRange1iArray = + (s, o) => EncodeArray(s, (Range1i[])o); + /// /// Wrong implemention (should be serialized as BGR, not RGB) according to Durable definition. /// For backwards compatibility, this should be fixed with magic, by obsoleting Aardvark.C3b durable definition used by this implemention and creating new Aardvark.C3b durable def. @@ -406,6 +415,9 @@ private static void Encode(BinaryWriter stream, Durable.Def def, object x) private static readonly Func DecodeBox3d = s => new Box3d((V3d)DecodeV3d(s), (V3d)DecodeV3d(s)); private static readonly Func DecodeBox3dArray = s => DecodeArray(s); + private static readonly Func DecodeRange1i = s => new Range1i(s.ReadInt32(), s.ReadInt32()); + private static readonly Func DecodeRange1iArray = s => DecodeArray(s); + /// /// Wrong implemention (should be serialized as BGR, not RGB) according to Durable definition. /// For backwards compatibility, this should be fixed with magic, by obsoleting Aardvark.C3b durable definition used by this implemention and creating new Aardvark.C3b durable def. diff --git a/src/Aardvark.Geometry.PointSet/Utils/StorageExtensions.cs b/src/Aardvark.Geometry.PointSet/Utils/StorageExtensions.cs index c4ea6741..50a90de2 100644 --- a/src/Aardvark.Geometry.PointSet/Utils/StorageExtensions.cs +++ b/src/Aardvark.Geometry.PointSet/Utils/StorageExtensions.cs @@ -718,8 +718,7 @@ public static IPointCloudNode GetPointCloudNode(this Storage storage, string key return r; } - var buffer = storage.f_get(key); - if (buffer == null) throw new Exception( + var buffer = storage.f_get(key) ?? throw new Exception( $"PointCloudNode not found (id={key}). " + $"Error b2ef55c1-1470-465d-80ea-034464c53638." ); diff --git a/src/Aardvark.Geometry.PointSet/Views/FilteredNode.cs b/src/Aardvark.Geometry.PointSet/Views/FilteredNode.cs index e843d357..e7e78d24 100644 --- a/src/Aardvark.Geometry.PointSet/Views/FilteredNode.cs +++ b/src/Aardvark.Geometry.PointSet/Views/FilteredNode.cs @@ -19,6 +19,7 @@ You should have received a copy of the GNU Affero General Public License using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Text.Json.Serialization; using static Aardvark.Data.Durable; namespace Aardvark.Geometry.Points @@ -106,6 +107,9 @@ private FilteredNode(Guid id, bool writeToStore, IPointCloudNode node, IFilter f /// public bool IsMaterialized => false; + /// + public bool IsEmpty => Node.IsEmpty; + /// public IPointCloudNode Materialize() { @@ -438,6 +442,54 @@ public PersistentRef> KdTree #region PartIndices + /// + /// True if this node has a PartIndexRange. + /// + [JsonIgnore] + [MemberNotNullWhen(true, nameof(PartIndexRange))] + public bool HasPartIndexRange => Node.HasPartIndexRange; + + /// + /// Octree. Min and max part index in octree. + /// + public Range1i? PartIndexRange + { + get + { + if (HasPartIndices) + { + if (SubsetIndexArray == null) + { + return Node.PartIndexRange; + } + else + { + if (m_cache.TryGetValue(Octree.PartIndexRange.Id, out var _range)) + { + return (Range1i)_range; + } + else + { + if (TryGetPartIndices(out var qs)) + { + var range = new Range1i(qs); + m_cache[Octree.PartIndexRange.Id] = range; + return range; + } + else + { + throw new Exception($"Expected part indices exist. Error 8d191c4a-042c-4179-ac30-8a10aab16436."); + } + } + } + } + else + { + return null; + } + } + } + /// /// True if this node has part indices. /// @@ -456,19 +508,32 @@ public PersistentRef> KdTree public bool TryGetPartIndices([NotNullWhen(true)] out int[]? result) { var qs = SubsetIndexArray != null ? PartIndexUtils.Subset(PartIndices, SubsetIndexArray) : PartIndices; - switch (qs) + + if (m_cache.TryGetValue(Octree.PerPointPartIndex1i.Id, out var _result)) + { + result = (int[])_result; + return true; + } + else { - case null: result = null; return false; - case int x: result = new int[PointCountCell].Set(x); return true; - case uint x: checked { result = new int[PointCountCell].Set((int)x); return true; } - case byte[] xs: result = xs.Map(x => (int)x); return true; - case short[] xs: result = xs.Map(x => (int)x); return true; - case int[] xs: result = xs; return true; - default: - throw new Exception( - $"Unexpected type {qs.GetType().FullName}. " + - $"Error ccc0b898-fe4f-4373-ac15-42da763fe5ab." - ); + switch (qs) + { + case null: result = null; return false; + case int x: result = new int[PointCountCell].Set(x); break; + case uint x: checked { result = new int[PointCountCell].Set((int)x); break; } + case byte[] xs: result = xs.Map(x => (int)x); break; + case short[] xs: result = xs.Map(x => (int)x); break; + case int[] xs: result = xs; break; + default: + throw new Exception( + $"Unexpected type {qs.GetType().FullName}. " + + $"Error ccc0b898-fe4f-4373-ac15-42da763fe5ab." + ); + } + + m_cache[Octree.PerPointPartIndex1i.Id] = result; + + return true; } }