/*
* 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;
}
}