/* * Copyright 2016 The Cartographer Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using CartographerSharp.Common.Math; using System.Collections; using RobotNet10.Shared.Numbers; namespace CartographerSharp.Mapping.D3D; /// /// Utility functions for HybridGrid indexing. /// internal static class HybridGridUtils { /// /// Converts an 'index' with each dimension from 0 to 2^'bits' - 1 to a flat z-major index. /// public static int ToFlatIndex(Array3i index, int bits) { if (index.X < 0 || index.Y < 0 || index.Z < 0 || index.X >= (1 << bits) || index.Y >= (1 << bits) || index.Z >= (1 << bits)) { throw new ArgumentOutOfRangeException(nameof(index), $"Index {index} is out of range for bits={bits}"); } return (((index.Z << bits) + index.Y) << bits) + index.X; } /// /// Converts a flat z-major 'index' to a 3-dimensional index with each dimension /// from 0 to 2^'bits' - 1. /// public static Array3i To3DIndex(int index, int bits) { if (index < 0 || index >= (1 << (3 * bits))) { throw new ArgumentOutOfRangeException(nameof(index), $"Index {index} is out of range for bits={bits}"); } int mask = (1 << bits) - 1; return new Array3i( index & mask, (index >> bits) & mask, (index >> bits) >> bits); } /// /// Checks if a value is the default value. /// public static bool IsDefaultValue(T value) where T : struct { return EqualityComparer.Default.Equals(value, default); } /// /// Checks if a list is empty (default value for collections). /// public static bool IsDefaultValue(List? value) { return value == null || value.Count == 0; } } /// /// A flat grid of '2^kBits' x '2^kBits' x '2^kBits' voxels storing values of /// type 'TValueType' in contiguous memory. Indices in each dimension are 0-based. /// internal class FlatGrid where TValueType : struct { private const int kBits = 3; // Fixed at 3 bits = 8x8x8 = 512 cells private const int kGridSize = 1 << kBits; // 8 private const int kTotalCells = 1 << (3 * kBits); // 512 private readonly TValueType[] _cells; public FlatGrid() { _cells = new TValueType[kTotalCells]; // Values are already default-initialized } /// /// Returns the number of voxels per dimension. /// public static int GridSize => kGridSize; /// /// Returns the value stored at 'index', each dimension of 'index' being /// between 0 and grid_size() - 1. /// public TValueType GetValue(Array3i index) { return _cells[HybridGridUtils.ToFlatIndex(index, kBits)]; } /// /// Returns a reference to the value at 'index' to allow changing it. /// public ref TValueType GetMutableValue(Array3i index) { return ref _cells[HybridGridUtils.ToFlatIndex(index, kBits)]; } /// /// Iterator for iterating over all values not comparing equal to the default constructed value. /// public class Iterator : IEnumerator<(Array3i Index, TValueType Value)> { private readonly FlatGrid _grid; private int _currentIndex; private (Array3i Index, TValueType Value)? _current; public Iterator(FlatGrid grid) { _grid = grid; _currentIndex = -1; MoveNext(); } public bool MoveNext() { _currentIndex++; while (_currentIndex < _grid._cells.Length) { var value = _grid._cells[_currentIndex]; if (!HybridGridUtils.IsDefaultValue(value)) { var index = HybridGridUtils.To3DIndex(_currentIndex, kBits); _current = (index, value); return true; } _currentIndex++; } _current = null; return false; } public void Reset() { _currentIndex = -1; _current = null; } public (Array3i Index, TValueType Value) Current => _current!.Value; object IEnumerator.Current => Current; public void Dispose() { GC.SuppressFinalize(this); } } /// /// Gets an enumerator for all non-default values. /// public Iterator GetEnumerator() { return new Iterator(this); } } /// /// A grid consisting of '2^kBits' x '2^kBits' x '2^kBits' grids of type 'FlatGrid'. /// Wrapped grids are constructed on first access via 'GetMutableValue()'. /// This is a concrete implementation for the specific case: NestedGrid, 3> /// internal class NestedGrid where TValueType : struct { private const int kBits = 3; // Fixed at 3 bits = 8x8x8 = 512 meta cells private const int kWrappedGridSize = 8; // FlatGrid.GridSize = 8 private readonly FlatGrid?[] _metaCells; public NestedGrid() { _metaCells = new FlatGrid?[1 << (3 * kBits)]; // 512 } public static int GridSize => kWrappedGridSize << kBits; // 8 * 8 = 64 public TValueType GetValue(Array3i index) { var metaIndex = NestedGrid.GetMetaIndex(index); var metaCell = _metaCells[HybridGridUtils.ToFlatIndex(metaIndex, kBits)]; if (metaCell == null) { return default; } var innerIndex = index - metaIndex * kWrappedGridSize; return metaCell.GetValue(innerIndex); } public ref TValueType GetMutableValue(Array3i index) { var metaIndex = NestedGrid.GetMetaIndex(index); var flatIndex = HybridGridUtils.ToFlatIndex(metaIndex, kBits); if (_metaCells[flatIndex] == null) { _metaCells[flatIndex] = new FlatGrid(); } var innerIndex = index - metaIndex * kWrappedGridSize; return ref _metaCells[flatIndex]!.GetMutableValue(innerIndex); } public IEnumerator<(Array3i Index, TValueType Value)> GetEnumerator() { return new Iterator(this); } private static Array3i GetMetaIndex(Array3i index) { if (index.X < 0 || index.Y < 0 || index.Z < 0) { throw new ArgumentOutOfRangeException(nameof(index), $"Index {index} has negative components"); } var metaIndex = index / kWrappedGridSize; if (metaIndex.X >= (1 << kBits) || metaIndex.Y >= (1 << kBits) || metaIndex.Z >= (1 << kBits)) { throw new ArgumentOutOfRangeException(nameof(index), $"Meta index {metaIndex} is out of range"); } return metaIndex; } /// /// Iterator for iterating over all non-default values. /// public class Iterator : IEnumerator<(Array3i Index, TValueType Value)> { private readonly NestedGrid _grid; private int _currentMetaIndex; private FlatGrid.Iterator? _nestedIterator; private (Array3i Index, TValueType Value)? _current; public Iterator(NestedGrid grid) { _grid = grid; _currentMetaIndex = -1; AdvanceToValidNestedIterator(); } private void AdvanceToValidNestedIterator() { while (_currentMetaIndex < _grid._metaCells.Length - 1) { _currentMetaIndex++; if (_currentMetaIndex >= _grid._metaCells.Length) { _nestedIterator = null; _current = null; return; } var metaCell = _grid._metaCells[_currentMetaIndex]; if (metaCell != null) { _nestedIterator = metaCell.GetEnumerator(); if (_nestedIterator.MoveNext()) { var (innerIndex, value) = _nestedIterator.Current; var metaIndex = HybridGridUtils.To3DIndex(_currentMetaIndex, kBits); var fullIndex = metaIndex * kWrappedGridSize + innerIndex; _current = (fullIndex, value); return; } } } _nestedIterator = null; _current = null; } public bool MoveNext() { if (_nestedIterator != null && _nestedIterator.MoveNext()) { var (innerIndex, value) = _nestedIterator.Current; var metaIndex = HybridGridUtils.To3DIndex(_currentMetaIndex, kBits); var fullIndex = metaIndex * kWrappedGridSize + innerIndex; _current = (fullIndex, value); return true; } AdvanceToValidNestedIterator(); return _current.HasValue; } public void Reset() { _currentMetaIndex = -1; _nestedIterator = null; _current = null; AdvanceToValidNestedIterator(); } public (Array3i Index, TValueType Value) Current => _current!.Value; object IEnumerator.Current => Current; public void Dispose() { _nestedIterator?.Dispose(); _current = null; GC.SuppressFinalize(this); } } } /// /// A grid consisting of 2x2x2 grids of type 'NestedGrid' initially. Wrapped grids /// are constructed on first access via 'GetMutableValue()'. If necessary, the grid /// grows to twice the size in each dimension. The range of indices is (almost) /// symmetric around the origin, i.e. negative indices are allowed. /// internal class DynamicGrid where TValueType : struct { private const int kWrappedGridSize = 64; // NestedGrid.GridSize = 64 private int _bits; // Starts at 1 (2x2x2 = 8 meta cells) private NestedGrid?[] _metaCells; public DynamicGrid() { _bits = 1; _metaCells = new NestedGrid?[8]; // 2^3 = 8 } /// /// Returns the current number of voxels per dimension. /// public int GridSize => kWrappedGridSize << _bits; /// /// Returns the value stored at 'index'. /// public TValueType GetValue(Array3i index) { var shiftedIndex = index + new Array3i(GridSize >> 1, GridSize >> 1, GridSize >> 1); // Check bounds using unsigned comparison for performance if (shiftedIndex.X < 0 || shiftedIndex.Y < 0 || shiftedIndex.Z < 0 || shiftedIndex.X >= GridSize || shiftedIndex.Y >= GridSize || shiftedIndex.Z >= GridSize) { return default; } var metaIndex = GetMetaIndex(shiftedIndex); var metaCell = _metaCells[HybridGridUtils.ToFlatIndex(metaIndex, _bits)]; if (metaCell == null) { return default; } var innerIndex = shiftedIndex - metaIndex * kWrappedGridSize; return metaCell.GetValue(innerIndex); } /// /// Returns a reference to the value at 'index' to allow changing it, dynamically /// growing the DynamicGrid and constructing new NestedGrids as needed. /// public ref TValueType GetMutableValue(Array3i index) { var shiftedIndex = index + new Array3i(GridSize >> 1, GridSize >> 1, GridSize >> 1); // Check bounds using unsigned comparison for performance if (shiftedIndex.X < 0 || shiftedIndex.Y < 0 || shiftedIndex.Z < 0 || shiftedIndex.X >= GridSize || shiftedIndex.Y >= GridSize || shiftedIndex.Z >= GridSize) { // SAFEGUARD: Store old bits to detect if Grow() actually increased size var oldBits = _bits; // Grow the grid Grow(); // SAFEGUARD: Check if Grow() actually increased size (prevent infinite recursion) if (_bits == oldBits) { throw new InvalidOperationException( $"Cannot grow grid further. Index {index} is out of bounds even after grow attempt. " + $"Current bits={_bits}, GridSize={GridSize}, shiftedIndex={shiftedIndex}"); } // SAFEGUARD: Recalculate shiftedIndex after grow and check bounds again shiftedIndex = index + new Array3i(GridSize >> 1, GridSize >> 1, GridSize >> 1); // SAFEGUARD: If still out of bounds after grow, throw exception instead of infinite recursion if (shiftedIndex.X < 0 || shiftedIndex.Y < 0 || shiftedIndex.Z < 0 || shiftedIndex.X >= GridSize || shiftedIndex.Y >= GridSize || shiftedIndex.Z >= GridSize) { throw new ArgumentOutOfRangeException(nameof(index), $"Index {index} is out of bounds even after growing grid to maximum size. " + $"GridSize={GridSize}, shiftedIndex={shiftedIndex}, bits={_bits}"); } return ref GetMutableValue(index); // Recursive call after grow (now safe) } var metaIndex = GetMetaIndex(shiftedIndex); var flatIndex = HybridGridUtils.ToFlatIndex(metaIndex, _bits); if (_metaCells[flatIndex] == null) { _metaCells[flatIndex] = new NestedGrid(); } var innerIndex = shiftedIndex - metaIndex * kWrappedGridSize; return ref _metaCells[flatIndex]!.GetMutableValue(innerIndex); } /// /// Iterator for iterating over all values not comparing equal to the default constructed value. /// public class Iterator : IEnumerator<(Array3i Index, TValueType Value)> { private readonly DynamicGrid _grid; private readonly int _bits; private int _currentMetaIndex; private IEnumerator<(Array3i Index, TValueType Value)>? _nestedIterator; private (Array3i Index, TValueType Value)? _current; public Iterator(DynamicGrid grid) { _grid = grid; _bits = grid._bits; _currentMetaIndex = -1; AdvanceToValidNestedIterator(); } private void AdvanceToValidNestedIterator() { while (_currentMetaIndex < _grid._metaCells.Length - 1) { _currentMetaIndex++; if (_currentMetaIndex >= _grid._metaCells.Length) { _nestedIterator = null; _current = null; return; } var metaCell = _grid._metaCells[_currentMetaIndex]; if (metaCell != null) { _nestedIterator = metaCell.GetEnumerator(); if (_nestedIterator.MoveNext()) { var (innerIndex, value) = _nestedIterator.Current; var metaIndex = HybridGridUtils.To3DIndex(_currentMetaIndex, _bits); var shiftedIndex = metaIndex * kWrappedGridSize + innerIndex; var originalIndex = shiftedIndex - new Array3i( (1 << (_bits - 1)) * kWrappedGridSize, (1 << (_bits - 1)) * kWrappedGridSize, (1 << (_bits - 1)) * kWrappedGridSize); _current = (originalIndex, value); return; } } } _nestedIterator = null; _current = null; } public bool MoveNext() { if (_nestedIterator != null && _nestedIterator.MoveNext()) { var (innerIndex, value) = _nestedIterator.Current; var metaIndex = HybridGridUtils.To3DIndex(_currentMetaIndex, _bits); var shiftedIndex = metaIndex * kWrappedGridSize + innerIndex; var originalIndex = shiftedIndex - new Array3i( (1 << (_bits - 1)) * kWrappedGridSize, (1 << (_bits - 1)) * kWrappedGridSize, (1 << (_bits - 1)) * kWrappedGridSize); _current = (originalIndex, value); return true; } AdvanceToValidNestedIterator(); return _current.HasValue; } public void Reset() { _currentMetaIndex = -1; _nestedIterator = null; _current = null; AdvanceToValidNestedIterator(); } public (Array3i Index, TValueType Value) Current => _current!.Value; object IEnumerator.Current => Current; public void Dispose() { _nestedIterator?.Dispose(); _current = null; GC.SuppressFinalize(this); } /// /// Advances iterator to end (for end() implementation). /// public void AdvanceToEnd() { _currentMetaIndex = _grid._metaCells.Length; _nestedIterator = null; _current = null; } } /// /// Gets an enumerator for all non-default values. /// public Iterator GetEnumerator() { return new Iterator(this); } private Array3i GetMetaIndex(Array3i index) { if (index.X < 0 || index.Y < 0 || index.Z < 0) { throw new ArgumentOutOfRangeException(nameof(index), $"Index {index} has negative components"); } var metaIndex = index / kWrappedGridSize; if (metaIndex.X >= (1 << _bits) || metaIndex.Y >= (1 << _bits) || metaIndex.Z >= (1 << _bits)) { throw new ArgumentOutOfRangeException(nameof(index), $"Meta index {metaIndex} is out of range"); } return metaIndex; } /// /// Grows this grid by a factor of 2 in each of the 3 dimensions. /// private void Grow() { var newBits = _bits + 1; if (newBits > 8) { throw new InvalidOperationException($"Cannot grow grid beyond bits=8 (current bits={_bits})"); } var newMetaCells = new NestedGrid?[8 * _metaCells.Length]; for (int z = 0; z < (1 << _bits); z++) { for (int y = 0; y < (1 << _bits); y++) { for (int x = 0; x < (1 << _bits); x++) { var originalMetaIndex = new Array3i(x, y, z); var newMetaIndex = originalMetaIndex + new Array3i(1 << (_bits - 1), 1 << (_bits - 1), 1 << (_bits - 1)); var originalFlatIndex = HybridGridUtils.ToFlatIndex(originalMetaIndex, _bits); var newFlatIndex = HybridGridUtils.ToFlatIndex(newMetaIndex, newBits); newMetaCells[newFlatIndex] = _metaCells[originalFlatIndex]; } } } _metaCells = newMetaCells; _bits = newBits; } } /// /// Represents a 3D grid as a wide, shallow tree. /// This is the base class for HybridGrid and IntensityHybridGrid. /// /// /// Creates a new tree-based grid with voxels having edge length 'resolution' /// around the origin which becomes the center of the cell at index (0, 0, 0). /// public class HybridGridBase(double resolution) where TValueType : struct { private readonly DynamicGrid _grid = new(); /// /// Returns the resolution (edge length of each voxel). /// public double Resolution => resolution; /// /// Returns the value stored at 'index'. /// protected TValueType GetValue(Array3i index) { return _grid.GetValue(index); } /// /// Returns a reference to the value at 'index' to allow changing it. /// protected ref TValueType GetMutableValue(Array3i index) { return ref _grid.GetMutableValue(index); } /// /// Returns the index of the cell containing the 'point'. Indices are integer /// vectors identifying cells, for this the coordinates are rounded to the next /// multiple of the resolution. /// public Array3i GetCellIndex(Vector3 point) { var index = new Vector3(point.X / resolution, point.Y / resolution, point.Z / resolution); return new Array3i( (int)System.Math.Round(index.X), (int)System.Math.Round(index.Y), (int)System.Math.Round(index.Z)); } /// /// Returns one of the octants, (0, 0, 0), (1, 0, 0), ..., (1, 1, 1). /// public static Array3i GetOctant(int i) { if (i < 0 || i >= 8) { throw new ArgumentOutOfRangeException(nameof(i), $"Octant index {i} must be in range [0, 7]"); } return new Array3i( (i & 1) != 0 ? 1 : 0, (i & 2) != 0 ? 1 : 0, (i & 4) != 0 ? 1 : 0); } /// /// Returns the center of the cell at 'index'. /// public Vector3 GetCenterOfCell(Array3i index) { return new Vector3( index.X * resolution, index.Y * resolution, index.Z * resolution); } /// /// Gets an enumerator for all non-default values. /// public IEnumerator<(Array3i Index, TValueType Value)> GetEnumerator() { return _grid.GetEnumerator(); } } /// /// A grid containing probability values stored using 15 bits, and an update /// marker per voxel. /// Points are expected to be close to the origin. Points far from the origin /// require the grid to grow dynamically. For centimeter resolution, points /// can only be tens of meters from the origin. /// The hard limit of cell indexes is +/- 8192 around the origin. /// public class HybridGrid : HybridGridBase { private const ushort kUpdateMarker = (ushort)(1u << 15); private readonly List _updateIndices; /// /// Creates a new HybridGrid with the specified resolution. /// public HybridGrid(double resolution) : base(resolution) { _updateIndices = []; } /// /// Creates a HybridGrid from a proto. /// public HybridGrid(Models.Mapping.HybridGrid proto) : base(proto.Resolution) { _updateIndices = []; if (proto.XIndices == null || proto.YIndices == null || proto.ZIndices == null || proto.Values == null) { throw new ArgumentException("Proto must have valid indices and values", nameof(proto)); } if (proto.XIndices.Count != proto.Values.Count || proto.YIndices.Count != proto.Values.Count || proto.ZIndices.Count != proto.Values.Count) { throw new ArgumentException( $"Proto indices and values count mismatch: X={proto.XIndices.Count}, Y={proto.YIndices.Count}, Z={proto.ZIndices.Count}, Values={proto.Values.Count}", nameof(proto)); } for (int i = 0; i < proto.Values.Count; i++) { var index = new Array3i(proto.XIndices[i], proto.YIndices[i], proto.ZIndices[i]); var probability = ProbabilityValues.ValueToProbability((ushort)proto.Values[i]); SetProbability(index, probability); } } /// /// Sets the probability of the cell at 'index' to the given 'probability'. /// public void SetProbability(Array3i index, double probability) { var clampedProbability = ProbabilityValues.ClampProbability(probability); var value = ProbabilityValues.ProbabilityToValue(clampedProbability); GetMutableValue(index) = value; } /// /// Finishes the update sequence by removing update markers from all updated cells. /// public void FinishUpdate() { foreach (var index in _updateIndices) { ref var cell = ref GetMutableValue(index); if (cell >= kUpdateMarker) { cell = (ushort)(cell - kUpdateMarker); } } _updateIndices.Clear(); } /// /// Applies the 'table' (lookup table from ComputeLookupTableToApplyOdds) to the /// probability of the cell at 'index' if the cell has not already been updated. /// Multiple updates of the same cell will be ignored until FinishUpdate() is called. /// Returns true if the cell was updated. /// /// If this is the first call to ApplyLookupTable() for the specified cell, its value /// will be set to probability corresponding to the table entry. /// public bool ApplyLookupTable(Array3i index, List table) { if (table == null || table.Count != kUpdateMarker) { throw new ArgumentException($"Table must have size {kUpdateMarker}", nameof(table)); } ref var cell = ref GetMutableValue(index); if (cell >= kUpdateMarker) { return false; // Already updated } _updateIndices.Add(index); cell = table[cell]; return true; } /// /// Returns the probability of the cell with 'index'. /// public double GetProbability(Array3i index) { return ProbabilityValues.ValueToProbability(GetValue(index)); } /// /// Returns true if the probability at the specified 'index' is known. /// public bool IsKnown(Array3i index) { return GetValue(index) != 0; } /// /// Converts this HybridGrid to a proto. /// public Models.Mapping.HybridGrid ToProto() { if (_updateIndices.Count > 0) { throw new InvalidOperationException( "Serializing a grid during an update is not supported. Finish the update first."); } var result = new Models.Mapping.HybridGrid { Resolution = Resolution, XIndices = [], YIndices = [], ZIndices = [], Values = [] }; foreach (var (index, value) in this) { result.XIndices.Add(index.X); result.YIndices.Add(index.Y); result.ZIndices.Add(index.Z); result.Values.Add(value); } return result; } } /// /// Average intensity data structure for IntensityHybridGrid. /// public struct AverageIntensityData { public double Sum { get; set; } public int Count { get; set; } } /// /// Hybrid grid for storing intensity data (average intensity per voxel). /// /// /// Creates a new IntensityHybridGrid with the specified resolution. /// public class IntensityHybridGrid(double resolution) : HybridGridBase(resolution) { /// /// Adds intensity value to the cell at 'index'. /// public void AddIntensity(Array3i index, double intensity) { ref var cell = ref GetMutableValue(index); cell.Count += 1; cell.Sum += intensity; } /// /// Returns the average intensity of the cell at 'index'. /// public double GetIntensity(Array3i index) { var cell = GetValue(index); if (cell.Count == 0) { return 0.0; } return cell.Sum / cell.Count; } }