/* * 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 System.Collections; using RobotNet10.Shared.Numbers; namespace CartographerSharp.Sensor; /// /// A compressed representation of a point cloud consisting of a collection of /// points (Vector3) without time information. /// Internally, points are grouped by blocks. Each block encodes a bit of meta /// data (number of points in block, coordinates of the block) and encodes each /// point with a fixed bit rate in relation to the block. /// public class CompressedPointCloud : IEnumerable { private const double kPrecision = 0.001f; // in meters private const int kBitsPerCoordinate = 10; private const int kCoordinateMask = (1 << kBitsPerCoordinate) - 1; private const int kMaxBitsPerDirection = 23; private readonly List _pointData; private readonly int _numPoints; /// /// Creates an empty compressed point cloud. /// public CompressedPointCloud() { _pointData = []; _numPoints = 0; } /// /// Creates a compressed point cloud from a point cloud. /// public CompressedPointCloud(PointCloud pointCloud) { _numPoints = pointCloud.Count; // Distribute points into blocks. // Using Dictionary to simulate HybridGrid behavior var blocks = new Dictionary<(int x, int y, int z), List<(Vector3 rasterPoint, int index)>>(); for (int pointIndex = 0; pointIndex < pointCloud.Count; pointIndex++) { var point = pointCloud[pointIndex]; var absMax = Math.Max(Math.Max(Math.Abs(point.Position.X), Math.Abs(point.Position.Y)), Math.Abs(point.Position.Z)); if (absMax / kPrecision >= (1 << kMaxBitsPerDirection)) { throw new ArgumentOutOfRangeException(nameof(pointCloud), $"Point out of bounds: {point.Position}"); } var rasterPoint = new Vector3( Math.Round(point.Position.X / kPrecision), Math.Round(point.Position.Y / kPrecision), Math.Round(point.Position.Z / kPrecision) ); var blockCoordinate = ( (int)rasterPoint.X >> kBitsPerCoordinate, (int)rasterPoint.Y >> kBitsPerCoordinate, (int)rasterPoint.Z >> kBitsPerCoordinate ); var relativePoint = new Vector3( (int)rasterPoint.X & kCoordinateMask, (int)rasterPoint.Y & kCoordinateMask, (int)rasterPoint.Z & kCoordinateMask ); if (!blocks.TryGetValue(blockCoordinate, out var block)) { block = []; blocks[blockCoordinate] = block; } block.Add((relativePoint, pointIndex)); } // Encode blocks. _pointData = []; foreach (var (blockCoord, rasterPoints) in blocks) { if (rasterPoints.Count > int.MaxValue) { throw new ArgumentException("Block too large"); } _pointData.Add(rasterPoints.Count); _pointData.Add(blockCoord.x); _pointData.Add(blockCoord.y); _pointData.Add(blockCoord.z); foreach (var (rasterPoint, _) in rasterPoints) { int encoded = (int)(((((int)rasterPoint.Z << kBitsPerCoordinate) + (int)rasterPoint.Y) << kBitsPerCoordinate) + (int)rasterPoint.X); _pointData.Add(encoded); } } } /// /// Creates a compressed point cloud from a proto. /// public CompressedPointCloud(CompressedPointCloud proto) { _numPoints = proto.NumPoints; _pointData = [.. proto.PointData]; } /// /// Gets the number of points. /// public int NumPoints => _numPoints; /// /// Gets the point data. /// internal List PointData => _pointData; /// /// Checks if the point cloud is empty. /// public bool IsEmpty => _numPoints == 0; /// /// Gets the number of points. /// public int Count => _numPoints; /// /// Returns decompressed point cloud. /// public PointCloud Decompress() { var decompressed = new PointCloud(); foreach (var point in this) { decompressed.Add(point); } return decompressed; } /// /// Gets an enumerator for the points. /// public IEnumerator GetEnumerator() { return new ConstIterator(this); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Converts to proto representation. /// public Models.Sensor.CompressedPointCloud ToProto() { return new Models.Sensor.CompressedPointCloud(_numPoints, [.. _pointData]); } /// /// Creates from proto representation. /// public static CompressedPointCloud FromProto(Models.Sensor.CompressedPointCloud proto) { return new CompressedPointCloud(proto.NumPoints, proto.PointData ?? []); } /// /// Creates a compressed point cloud from proto data. /// private CompressedPointCloud(int numPoints, List pointData) { _numPoints = numPoints; _pointData = pointData ?? []; } /// /// Forward iterator for compressed point clouds. /// private class ConstIterator : IEnumerator { private readonly CompressedPointCloud _compressedPointCloud; private int _remainingPoints; private int _remainingPointsInCurrentBlock; private int _inputIndex; private Vector3 _currentPoint; private Vector3 _currentBlockCoordinates; public ConstIterator(CompressedPointCloud compressedPointCloud) { _compressedPointCloud = compressedPointCloud; _remainingPoints = compressedPointCloud._numPoints; _remainingPointsInCurrentBlock = 0; _inputIndex = 0; if (_remainingPoints > 0) { ReadNextPoint(); } } public RangefinderPoint Current { get; private set; } object IEnumerator.Current => Current; public bool MoveNext() { if (_remainingPoints <= 0) { return false; } Current = new RangefinderPoint(_currentPoint); _remainingPoints--; if (_remainingPoints > 0) { ReadNextPoint(); } return true; } public void Reset() { _remainingPoints = _compressedPointCloud._numPoints; _remainingPointsInCurrentBlock = 0; _inputIndex = 0; if (_remainingPoints > 0) { ReadNextPoint(); } } public void Dispose() { // Nothing to dispose } private void ReadNextPoint() { if (_remainingPointsInCurrentBlock == 0) { if (_inputIndex >= _compressedPointCloud._pointData.Count) { return; } _remainingPointsInCurrentBlock = _compressedPointCloud._pointData[_inputIndex++]; if (_inputIndex + 3 > _compressedPointCloud._pointData.Count) { return; } _currentBlockCoordinates = new Vector3( _compressedPointCloud._pointData[_inputIndex++] << kBitsPerCoordinate, _compressedPointCloud._pointData[_inputIndex++] << kBitsPerCoordinate, _compressedPointCloud._pointData[_inputIndex++] << kBitsPerCoordinate ); } _remainingPointsInCurrentBlock--; if (_inputIndex >= _compressedPointCloud._pointData.Count) { return; } int point = _compressedPointCloud._pointData[_inputIndex++]; const int kMask = (1 << kBitsPerCoordinate) - 1; _currentPoint = new Vector3( ((_currentBlockCoordinates.X + (point & kMask)) * kPrecision), ((_currentBlockCoordinates.Y + ((point >> kBitsPerCoordinate) & kMask)) * kPrecision), ((_currentBlockCoordinates.Z + (point >> (2 * kBitsPerCoordinate))) * kPrecision) ); } } }