Files
BQP/srcs/RobotNet10/RobotApp/Communication/CartographerSharp/Mapping/3D/HybridGrid.cs
2026-07-13 09:25:40 +07:00

849 lines
29 KiB
C#

/*
* 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;
/// <summary>
/// Utility functions for HybridGrid indexing.
/// </summary>
internal static class HybridGridUtils
{
/// <summary>
/// Converts an 'index' with each dimension from 0 to 2^'bits' - 1 to a flat z-major index.
/// </summary>
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;
}
/// <summary>
/// Converts a flat z-major 'index' to a 3-dimensional index with each dimension
/// from 0 to 2^'bits' - 1.
/// </summary>
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);
}
/// <summary>
/// Checks if a value is the default value.
/// </summary>
public static bool IsDefaultValue<T>(T value) where T : struct
{
return EqualityComparer<T>.Default.Equals(value, default);
}
/// <summary>
/// Checks if a list is empty (default value for collections).
/// </summary>
public static bool IsDefaultValue<T>(List<T>? value)
{
return value == null || value.Count == 0;
}
}
/// <summary>
/// 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.
/// </summary>
internal class FlatGrid<TValueType> 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
}
/// <summary>
/// Returns the number of voxels per dimension.
/// </summary>
public static int GridSize => kGridSize;
/// <summary>
/// Returns the value stored at 'index', each dimension of 'index' being
/// between 0 and grid_size() - 1.
/// </summary>
public TValueType GetValue(Array3i index)
{
return _cells[HybridGridUtils.ToFlatIndex(index, kBits)];
}
/// <summary>
/// Returns a reference to the value at 'index' to allow changing it.
/// </summary>
public ref TValueType GetMutableValue(Array3i index)
{
return ref _cells[HybridGridUtils.ToFlatIndex(index, kBits)];
}
/// <summary>
/// Iterator for iterating over all values not comparing equal to the default constructed value.
/// </summary>
public class Iterator : IEnumerator<(Array3i Index, TValueType Value)>
{
private readonly FlatGrid<TValueType> _grid;
private int _currentIndex;
private (Array3i Index, TValueType Value)? _current;
public Iterator(FlatGrid<TValueType> 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);
}
}
/// <summary>
/// Gets an enumerator for all non-default values.
/// </summary>
public Iterator GetEnumerator()
{
return new Iterator(this);
}
}
/// <summary>
/// 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<FlatGrid<TValueType>, 3>
/// </summary>
internal class NestedGrid<TValueType> where TValueType : struct
{
private const int kBits = 3; // Fixed at 3 bits = 8x8x8 = 512 meta cells
private const int kWrappedGridSize = 8; // FlatGrid<TValueType>.GridSize = 8
private readonly FlatGrid<TValueType>?[] _metaCells;
public NestedGrid()
{
_metaCells = new FlatGrid<TValueType>?[1 << (3 * kBits)]; // 512
}
public static int GridSize => kWrappedGridSize << kBits; // 8 * 8 = 64
public TValueType GetValue(Array3i index)
{
var metaIndex = NestedGrid<TValueType>.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<TValueType>.GetMetaIndex(index);
var flatIndex = HybridGridUtils.ToFlatIndex(metaIndex, kBits);
if (_metaCells[flatIndex] == null)
{
_metaCells[flatIndex] = new FlatGrid<TValueType>();
}
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;
}
/// <summary>
/// Iterator for iterating over all non-default values.
/// </summary>
public class Iterator : IEnumerator<(Array3i Index, TValueType Value)>
{
private readonly NestedGrid<TValueType> _grid;
private int _currentMetaIndex;
private FlatGrid<TValueType>.Iterator? _nestedIterator;
private (Array3i Index, TValueType Value)? _current;
public Iterator(NestedGrid<TValueType> 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);
}
}
}
/// <summary>
/// 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.
/// </summary>
internal class DynamicGrid<TValueType> where TValueType : struct
{
private const int kWrappedGridSize = 64; // NestedGrid<TValueType>.GridSize = 64
private int _bits; // Starts at 1 (2x2x2 = 8 meta cells)
private NestedGrid<TValueType>?[] _metaCells;
public DynamicGrid()
{
_bits = 1;
_metaCells = new NestedGrid<TValueType>?[8]; // 2^3 = 8
}
/// <summary>
/// Returns the current number of voxels per dimension.
/// </summary>
public int GridSize => kWrappedGridSize << _bits;
/// <summary>
/// Returns the value stored at 'index'.
/// </summary>
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);
}
/// <summary>
/// Returns a reference to the value at 'index' to allow changing it, dynamically
/// growing the DynamicGrid and constructing new NestedGrids as needed.
/// </summary>
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<TValueType>();
}
var innerIndex = shiftedIndex - metaIndex * kWrappedGridSize;
return ref _metaCells[flatIndex]!.GetMutableValue(innerIndex);
}
/// <summary>
/// Iterator for iterating over all values not comparing equal to the default constructed value.
/// </summary>
public class Iterator : IEnumerator<(Array3i Index, TValueType Value)>
{
private readonly DynamicGrid<TValueType> _grid;
private readonly int _bits;
private int _currentMetaIndex;
private IEnumerator<(Array3i Index, TValueType Value)>? _nestedIterator;
private (Array3i Index, TValueType Value)? _current;
public Iterator(DynamicGrid<TValueType> 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);
}
/// <summary>
/// Advances iterator to end (for end() implementation).
/// </summary>
public void AdvanceToEnd()
{
_currentMetaIndex = _grid._metaCells.Length;
_nestedIterator = null;
_current = null;
}
}
/// <summary>
/// Gets an enumerator for all non-default values.
/// </summary>
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;
}
/// <summary>
/// Grows this grid by a factor of 2 in each of the 3 dimensions.
/// </summary>
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<TValueType>?[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;
}
}
/// <summary>
/// Represents a 3D grid as a wide, shallow tree.
/// This is the base class for HybridGrid and IntensityHybridGrid.
/// </summary>
/// <remarks>
/// 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).
/// </remarks>
public class HybridGridBase<TValueType>(double resolution) where TValueType : struct
{
private readonly DynamicGrid<TValueType> _grid = new();
/// <summary>
/// Returns the resolution (edge length of each voxel).
/// </summary>
public double Resolution => resolution;
/// <summary>
/// Returns the value stored at 'index'.
/// </summary>
protected TValueType GetValue(Array3i index)
{
return _grid.GetValue(index);
}
/// <summary>
/// Returns a reference to the value at 'index' to allow changing it.
/// </summary>
protected ref TValueType GetMutableValue(Array3i index)
{
return ref _grid.GetMutableValue(index);
}
/// <summary>
/// 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.
/// </summary>
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));
}
/// <summary>
/// Returns one of the octants, (0, 0, 0), (1, 0, 0), ..., (1, 1, 1).
/// </summary>
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);
}
/// <summary>
/// Returns the center of the cell at 'index'.
/// </summary>
public Vector3 GetCenterOfCell(Array3i index)
{
return new Vector3(
index.X * resolution,
index.Y * resolution,
index.Z * resolution);
}
/// <summary>
/// Gets an enumerator for all non-default values.
/// </summary>
public IEnumerator<(Array3i Index, TValueType Value)> GetEnumerator()
{
return _grid.GetEnumerator();
}
}
/// <summary>
/// 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.
/// </summary>
public class HybridGrid : HybridGridBase<ushort>
{
private const ushort kUpdateMarker = (ushort)(1u << 15);
private readonly List<Array3i> _updateIndices;
/// <summary>
/// Creates a new HybridGrid with the specified resolution.
/// </summary>
public HybridGrid(double resolution) : base(resolution)
{
_updateIndices = [];
}
/// <summary>
/// Creates a HybridGrid from a proto.
/// </summary>
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);
}
}
/// <summary>
/// Sets the probability of the cell at 'index' to the given 'probability'.
/// </summary>
public void SetProbability(Array3i index, double probability)
{
var clampedProbability = ProbabilityValues.ClampProbability(probability);
var value = ProbabilityValues.ProbabilityToValue(clampedProbability);
GetMutableValue(index) = value;
}
/// <summary>
/// Finishes the update sequence by removing update markers from all updated cells.
/// </summary>
public void FinishUpdate()
{
foreach (var index in _updateIndices)
{
ref var cell = ref GetMutableValue(index);
if (cell >= kUpdateMarker)
{
cell = (ushort)(cell - kUpdateMarker);
}
}
_updateIndices.Clear();
}
/// <summary>
/// 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.
/// </summary>
public bool ApplyLookupTable(Array3i index, List<ushort> 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;
}
/// <summary>
/// Returns the probability of the cell with 'index'.
/// </summary>
public double GetProbability(Array3i index)
{
return ProbabilityValues.ValueToProbability(GetValue(index));
}
/// <summary>
/// Returns true if the probability at the specified 'index' is known.
/// </summary>
public bool IsKnown(Array3i index)
{
return GetValue(index) != 0;
}
/// <summary>
/// Converts this HybridGrid to a proto.
/// </summary>
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;
}
}
/// <summary>
/// Average intensity data structure for IntensityHybridGrid.
/// </summary>
public struct AverageIntensityData
{
public double Sum { get; set; }
public int Count { get; set; }
}
/// <summary>
/// Hybrid grid for storing intensity data (average intensity per voxel).
/// </summary>
/// <remarks>
/// Creates a new IntensityHybridGrid with the specified resolution.
/// </remarks>
public class IntensityHybridGrid(double resolution) : HybridGridBase<AverageIntensityData>(resolution)
{
/// <summary>
/// Adds intensity value to the cell at 'index'.
/// </summary>
public void AddIntensity(Array3i index, double intensity)
{
ref var cell = ref GetMutableValue(index);
cell.Count += 1;
cell.Sum += intensity;
}
/// <summary>
/// Returns the average intensity of the cell at 'index'.
/// </summary>
public double GetIntensity(Array3i index)
{
var cell = GetValue(index);
if (cell.Count == 0)
{
return 0.0;
}
return cell.Sum / cell.Count;
}
}